Table of Contents
One of the most interesting updates of the new ASP.NET Core platform (also known as ASP.NET 5) is the fact that it merges the old MVC and Web API frameworks into one (MVC 6). This allows you to have the best of both worlds: the rich features of the former MVC Controllers - with each methods returning an ActionResult - together with the versatility of the ApiControllers, whenever we need to return pure HTTP response types such as IHttpActionResult, HttpResponseMessage and so on. This basically means being able to serve Views and/or WebServices whenever we want to, without having to change your overall approach.
What if we need to do that in a plain old ASP.NET 4 MVC-based application? As a matter of fact, we can do that there too: any standard MVC Controller can be tweaked to make it act & behave just like an ApiController: we just need to develop our very own ActionResult classes and everything will more-or-less work like we're expecting to. However, such an approach can be very hard to maintain and test: on top of that, having MVC Controllers methods returning ActionResult mixed with others returning raw/serialized/IHttpActionResult data can be very confusing from a developer perspective, expecially if you're not working alone.
Luckily enough, there's a nicer alternative: we can import (and properly configure) the core ASP.NET Web API package into our MVC-based Web Application: this way we'll get the best of both worlds, being able to use the Controller and/or the ApiController classes together, as long as we need to. To do that, we just have to manually install the required components of the Web API framework that we normally miss in a MVC4 or MVC5 project. This post will explain how we can do that in few easy steps.
Install the Web API NuGet packages
The first thing to do is to install the latest version of the ASP.NET Web API package. We can do that by opening the Visual Studio's Package Manager Console and issuing the following NuGet commands:
1 2 |
> Install-Package Microsoft.AspNet.WebApi.Core > Install-Package Microsoft.AspNet.WebApi.WebHost |
For further references regarding ASP.NET Web API package we can also read we can refer to the official documentation.
Create a sample Web API Controller
Once we imported the required packages we can add an ApiController to our existing ASP.NET MVC project. The ApiController is the base class for the ASP.NET Web API controllers: the most relevant difference between a MVC Controller and an ApiController is that the latter is specialized in returning data: just to make an example, they can transparently serializing the data into the format requested by the client. It's also worth noticing that they follow a different routing scheme, providing REST-ful API routes by convention (that can be changed using Attribute-based routing, as explained here).
Here's a sample (and working) ApiController: the syntax slightly changes depending if you're using Web API or Web API 2:
Web API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
public class SampleController : ApiController { // GET api/Sample public HttpResponseMessage Get() { return "It works!"; } // GET api/Sample/{id} public HttpResponseMessage Get(string id) { return "It works! Your id is " + id; } // POST api/Sample public void Post([FromBody]string value) { throw new NotSupportedException(); } // PUT api/Sample/{id} public void Put(string id, [FromBody]string value) { throw new NotSupportedException(); } // DELETE api/Sample/{id} public void Delete(string id) { throw new NotSupportedException(); } [HttpGet] [Route("api/Sample/Custom")] public HttpResponseMessage Custom() { // sample custom action method using attribute-based routing // TODO: my code here throw new NotImplementedException(); } } |
Web API 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
public class SampleController : ApiController { // GET api/Sample public IHttpActionResult Get() { return Ok("It works!"); } // GET api/Sample/{id} public IHttpActionResult Get(string id) { return Ok("It works! Your id is " + id); } // POST api/Sample public void Post([FromBody]string value) { throw new NotSupportedException(); } // PUT api/Sample/{id} public void Put(string id, [FromBody]string value) { throw new NotSupportedException(); } // DELETE api/Sample/{id} public void Delete(string id) { throw new NotSupportedException(); } [HttpGet] [Route("api/Sample/Custom")] public IHttpActionResult Custom() { // sample custom action method using attribute-based routing // TODO: my code here throw new NotImplementedException(); } } |
Define a Web API Routing Configuration
The next step involves implementing Web API in our existing MVC-based routing configuration. We can do that in two ways: add a Web API configuration file (the suggested way) or override the existing routing configuration of our MVC Web Application.
Adding a WebApiConfig.cs file
If we want to enforce a "separation of concerns" between MVC and Web API, This is our best choice. From Solution Explorer, right-click to the /App_Start/ folder, then add a new C# class file naming it WebApiConfig.cs with the following contents:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
using System.Web.Http; public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API routes config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } } |
This will ensure that every requesting URL starting with /api/ will be routed to our Web API controllers and handled by them.
Adding a Web API rule to RouteConfig.cs file
Alternatively, we could also add a WebApi-specific rule to the RouteConfig.cs file, which controls the MVC routing rules. We can do that in the following way:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "DefaultWebApi", url: "/api/{controller}/{id}", defaults: new { id = UrlParameter.Optional } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } |
The benefit of doing that is that we can skip the following paragraph, as we don't have anything new to register, at the cost of mixing the two. On the other hand, we'll have the two worlds intertwined into one, which can lead to confusion. Altough this is mostly a developer choice, we slightly recommend the following approach.
Register the WebAPI Routing Configuration
If we chose to add a new WebApiConfig.cs file, we need to register that on our Web Application's main configuration class. This is most likely the MvcApplication class within the Global.asax.cs file, which Application_Start method needs to be changed as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
protected void Application_Start() { // Register Web API routing support before anything else GlobalConfiguration.Configure(WebApiConfig.Register); // The rest of our file goes here // ... AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); // ... } |
If we're using the OWIN Startup template instead, we need to do the same in the Configuration method of the Startup class, which is defined withinin the Startup.cs file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public void Configuration(IAppBuilder app) { // Register Web API routing support before anything else GlobalConfiguration.Configure(WebApiConfig.Register); // The rest of our file goes here // ... AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); ConfigureAuth(app); // ... } |
Testing it up
That's it. Now we have the best of both worlds into the same Web Application. We can easily test it up by requesting the following URLs:
1 2 3 |
/api/Sample/ /api/Sample/5 /api/Sample/Custom/ |
Notice the last URL: we added it as an example to demonstrate how is possible to mix the default Web API RESTful conventions with custom action methods using attribute-based routing.
That's it for now: happy routing!