Migrating from ASP.NET Web API to MVC 6 – exploring Web API Compatibility Shim

Β· 1732 words Β· 9 minutes to read

Migrating an MVC 5 project to ASP.NET 5 and MVC 6 is a big challenge given that both of the latter are complete rewrites of their predecessors. As a result, even if on the surface things seem similar (we have controllers, filters, actions etc), as you go deeper under the hood you realize that most, if not all, of your pipeline customizations will be incompatible with the new framework.

This pain is even more amplified if you try to migrate Web API 2 project to MVC 6 - because Web API had a bunch of its own unique concepts and specialized classes, all of which only complicate the migration attempts.

ASP.NET team provides an extra convention set on top of MVC 6, called “Web API Compatibility Shim”, which can be enabled make the process of migration from Web API 2 a bit easier. Let’s explore what’s in there.

Introduction πŸ”—

If you create a new MVC 6 project from the default starter template, it will contain the following code in the Startup class, under ConfigureServices method:

// Uncomment the following line to add Web API servcies which makes it easier to port Web API 2 controllers.  
// You need to add Microsoft.AspNet.Mvc.WebApiCompatShim package to project.json  
// services.AddWebApiConventions();  

This pretty much explains it all - the Compatibility Shim is included in an external package, Microsoft.AspNet.Mvc.WebApiCompatShim and by default is switched off for new MVC projects. Once added and enabled, you can also have a look at the UseMvc method, under Configure. This is where central Web API routes can be defined:

app.UseMvc(routes =>  
{  
routes.MapRoute(  
name: "default",  
template: "{controller}/{action}/{id?}",  
defaults: new { controller = "Home", action = "Index" });

// Uncomment the following line to add a route for porting Web API 2 controllers.  
// routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}");  
});  

Interestingly, Web API Compatibility Shim will store all Web API related routes under a dedicated, predefined area, called simply “api”.

This is all that’s needed to get you started.

Inheriting from ApiController πŸ”—

Since the base class for Web API controllers was not Controller but ApiController, the shim introduces a type of the same name into MVC 6.

While it is obviously not 100% identical to the ApiController from Web API, it contains the majority of public proeprties and methods that you might have gotten used to - the Request property, the User property or a bunch of IHttpActionResult helpers.

You explore it in details here on Github.

Returning HttpResponseMessage πŸ”—

The shim introduces the ability to work with HttpResponseMessage in MVC 6 projects. How is this achieved? First of all, the Microsoft.AspNet.WebApi.Client package is referenced, and that brings in the familiar types - HttpResponseMessage and HttpRequestMessage.

On top of that, an extra formatter (remember, we discussed MVC 6 formatters here before) is injected into your application - HttpResponseMessageOutputFormatter. This allows you to return HttpResponseMessage from your actions, just like you were used to doing in Web API projects!

How does it work under the hood? Remember, in Web API, returning an instance of HttpResponseMessage bypassed content negotiation and simply forwarded the instance all the way to the hosting layer, which was responsible to convert it to a response that was relevant for a given host (HttpResponse under System.Web, OWIN dictionary under OWIN and so on).

In the case of MVC 6, the new formatter will grab your HttpResponseMessage and copy its headers and contents onto the Microsoft.AspNet.Http.HttpResponse which is the new abstraction for HTTP response in ASP.NET 5.

As a result such type of an action as the one shown below, is possible in MVC 6, and as a consequence it should be much simpler to migrate your Web API 2 projects.

public HttpResponseMessage Post()  
{  
return new HttpResponseMessage(HttpSattusCode.NoContent);  
}  

Binding HttpRequestMessage πŸ”—

In Web API it was possible to bind HttpRequestMessage in your actions. For example this was easily doable:

[Route("test/{id:int}")]  
public string Get(int id, HttpRequestMessage req)  
{  
return id + " " + req.RequestUri;  
}

[Route("testA")]  
public async Task<TestItem> Post(HttpRequestMessage req)  
{  
return await req.Content.ReadAsAsync<TestItem>();  
}  

The shim introduces an HttpRequestMessageModelBinder which allows the same thing to be done under MVC 6. As a result, if you relied on HttpRequestMessage binding in Web API, your code will migrate to MVC 6 fine.

How does it work? The shim will use an intermediary type, HttpRequestMessageFeature, to create an instance of HttpRequestMessage from the ASP.NET 5 HttpContext.

HttpRequestMessage extensions πŸ”—

Since it was very common in the Web API world to use HttpResponseMessage as an action return type, there was a need for a mechanism that allowed easy creation of its instances. This was typically achieved by using the extension methods on the HttpRequestMessage, as they would perform content negotiation for you.

For example:

public HttpResponseMessage Post(Item item)  
{  
return Request.CreateResponse(HttpSattusCode.NoContent, item);  
}  

The Web API Compatibility Shim introduces these extension methods into MVC 6 as well. Mind you, these extension methods are hanging off the HttpRequestMessage - so to use them you will have to inherit from the ApiController, in order to have the Request property available for you on the base class in the first place.

There is a number of the overloaded versions of those CreateResponse method (just like it was in Web API), as well as the CreateErrorResponse method. You can explore them in detail here on Github.

HttpError πŸ”—

If you use/used the CreateErrorResponse method mentioned above, you will end up relying on the HttpError class which is another ghost of the Web API past rejuvenated by the compatibility shim.

HttpError was traditionally used by Web API to serve up error information to the client in a (kind of) standardized way. It contained properties such as ModelState, MessageDetail or StackTrace.

It was used by not just the CreateErrorResponse extension method but also by a bunch of IHttpActionResults - InvalidModelStateResult, ExceptionResult and BadRequestErrorMessageResult. As a result, HttpError is back to facilitate all of these types.

More Web API-style parameter binding πŸ”—

A while ago, Badrinarayanan had a nice post explaining the differences between parameter binding in MVC 6 and Web API 2. In short, the approach in MVC 6 is (and that’s unerstandable) much more like MVC 5, and those of us used to Web API style of binding, could easily end up with lots of problems (mainly hidden problems that wouldn’t show up at compile time, which is even worse). Even the official tutorials from Microsoft show that, for example, binding from the body of the request, which was one of the cornerstones of Web API parameter binding, needs to be explicit now (see the action CreateTodoItem in that example) - through a use of a dedicated attribute.

Web API Compatibility Shim attempts to close this wide gap between MVC 6 and Web API 2 style of parameter binding.

The class called WebApiParameterConventionsApplicationModelConvention introduces the model of binding familiar to the Web API developers:

    • simple types are bound from URI
    • complex types are bound from the body

Additionally action overloading was different between MVC and Web API (for example, Web API parameters are all required by default, whereas MVC ones aren’t). This action resolving mechanism is also taken care of by the compatibility shim.

Finally, FromUriAttribute, does not exist in MVC 6 (due to the nature of parameter binding in MVC). This type is, however, introduced again in the compatibility shim.

Support for HttpResponseException πŸ”—

In Web API, it was common to throw HttpResponseException to control the flow of your action and short circuit a relevant HTTP response to the client. For example:

public Item Get(int id)  
{  
var item = _repo.FindById(id);  
if (item == null) throw new HttpResponseException(HttpStatusCode.NotFound);  
return item;  
}  

HttpResponseException is re-introduced into MVC 6 by the compatibility shim, and allows you to use the code shown above in the new world.

How does it work? Well, the shim introduces and extra filter, HttpResponseExceptionActionFilter that catches all exceptions from your action. If the exception happens to be of type HttpResponseException, it will be marked as handled and Result on the ActionExecutedContext will be properly set as ObjectResult, based on the contents of that exception. If the exception is of different type, the filter just lets it pass through to be handled elsewhere.

Support for DefaultContentNegotiator πŸ”—

Web API content negotiation has been discussed quite widely on this blog before. Compatibility Shim introduces the concept of the IContentNegotiator into MVC 6 to facilitate its own extension methods, such the already discussed CreateResponse or CreateErrorResponse.

Additionally, the IContentNegotiator is registered as a service so you can obtain an instance of it using a simple call off the new ASP.NET 5 HttpContext:

var contentNegotiator = context.RequestServices.GetRequiredService<IContentNegotiator>();  

This makes it relatively easy to port pieces of code where you’d deal with the negotiator manually (such as the example below), as you’d only need to change the way the negotiator instance is obtained.

[Route("items/{id:int}")]  
public HttpResponseMessage Get(int id)  
{  
var item = new Item  
{  
Id = id,  
Name = "I'm manually content negotiatied!"  
};  
var negotiator = Configuration.Services.GetContentNegotiator();  
var result = negotiator.Negotiate(typeof(Item), Request, Configuration.Formatters);

var bestMatchFormatter = result.Formatter;  
var mediaType = result.MediaType.MediaType;

return new HttpResponseMessage(HttpStatusCode.OK)  
{  
Content = new ObjectContent<Item>(item, bestMatchFormatter, mediaType)  
};  
}  

Extra action results πŸ”—

Web API Compatibility Shims also introduces a set of action results that you might be used to from Web API as IHttpActionResults. Those are currently implemented as ObjectResults.

Here is the list:

  • BadRequestErrorMessageResult
  • ConflictResult
  • ExceptionResult
  • InternalServerErrorResult
  • InvalidModelStateResult
  • NegotiatedContentResult
  • OkNegotiatedContentResult
  • OkResult
  • ResponseMessageResult

Regardless of how you used them in your Web API project - newed up directly in the action, or as a call to a method on the base ApiController, your code should now be much more straight forward to port to MVC 6 (or at least to get it to a stage that it actually compiles under MVC 6).

Summary πŸ”—

While the Web API Compatibility Shim will not solve all of the problems you might encounter when trying to port a Web API 2 project to MVC 6, it will at least make your life much easier. The majority of the high level concepts - the code used in your actions and controllers - should migrate almost 1:1, and the only things you will be left with having to tackle would be the deep pipeline customizations, so things such as custom routing conventions, customizations to action or controller selectors, customizations to tracers or error handlers and so on.

Either way, enabling the shim is a must to even get started with thinking to migrate a Web API project to MVC 6.

About


Hi! I'm Filip W., a cloud architect from ZΓΌrich πŸ‡¨πŸ‡­. I like Toronto Maple Leafs πŸ‡¨πŸ‡¦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁒󠁳󠁣󠁴󠁿.

You can find me on Github and on Mastodon.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP