Extending ASP.NET Web API Content Negotiation

The ASP.NET team released the beta version of the ASP.NET Web API, previously known as WCF Web API, as part of the beta release of ASP.NET MVC 4. Having experience implementing web APIs with Restfulie, I was curious and decided to check how the ASP.NET Web API works to compare it with Restfulie.

The first thing I noticed was a difference in the Content Negotiation implementation. I don’t intend to do a full comparison here, but to describe how to use one of the extension points in the Web API to add the behavior that I wanted.

If you are not interested in in the reasons why the Content Negotiation implementation differs, and why I prefer the Restfulie one and just want to see DA CODEZ, feel free to jump to the “Message Handlers to the Rescue” part.

Content Negotiation

Content Negotiation, simply put, is the process of figuring out what is the media-type that needs to be used in the Resource Representation that is going to be returned in a HTTP Response. This negotiation between client and server happens through the interpretation of the HTTP Accept Header values.

When a client makes a request, if the client wants to specify a set of media-types that it accepts the resource to be formatted into, then these media types should be included in the Accept Header. All the semantics and options regarding the usage of the Accept header can be found on section 14.1 of RFC 2616 (HTTP specification). In the Accept Header definition there is a part that reads:

If no Accept header field is present, then it is assumed that the client accepts all media types. If an Accept header field is present, and if the server cannot send a response which is acceptable according to the combined Accept field value, then the server SHOULD send a 406 (not acceptable) response.

The SHOULD key word in the RFC 2616, according to section 1.2 of the RFC, is to be interpreted as described in the RFC 2119. The description of SHOULD in this RFC is:

SHOULD   This word, or the adjective “RECOMMENDED”, mean that there may exist valid reasons in particular circumstances to ignore a particular item, but the full implications must be understood and carefully weighed before choosing a different course.

With this definition, I interpret the Accept Header behavior as: unless you have a specific reason not to, if the HTTP Request contains an Accept Header and the server does not support the requested media-type, a 406 – Not Acceptable response should be returned.  Returning other Status Code might be OK, as it’s not mandatory to return 406, but this should be the exception, not the default behavior.

ASP.NET Web API Content Negotiation Implementation

The namespace System.Net.Http.Formatting has a MediaTypeFormatter class whose instances are used by the Web API (I’m going to omit the ASP.NET part from now on) to format the resource representation to a specific media-type.

The Web API ships with JsonMediaTypeFormatter and XmlMediaTypeFormatter implementations. And it defaults to the JSON formatter when no specific media-type is requested. So, if the Accept Header is not included in the HTTP Request, Web API will use the JSON formatter to format the Resource Representation that is going to be returned.

You can add your own MediaType formatter by adding your formatter implementation to the MediaTypeFormatter collection of the HTTPConfiguration object in the GlobalConfiguration class.

It seems like something is not right here

While I was playing with the Web API, I decided to test the behavior of the Content Negotiation implementation in the scenario where the media-type in the Accept Header is not accepted by the server. That is, when the HTTP Request Accept Header specifies a media-type that the Web API doesn’t have a MediaTypeFormatter implementation that can handle it. By the definition described in the Content Negotiation section above, I expected to receive a 406 response back. To my surprise, I got a 200 – OK response back with a Resource Representation with text/json contant-type. Web API fell back to the default MediaTypeFormatter instead of returning 406.

This was a surprise to me, so I filled a bug report for this in the Web API Forum. Henrik Frystyk Nielsen replied saying:

It’s a reasonable to respond either with a 200 or with a 406 status code in this case. At the moment we err on the side of responding with a 2xx but I completely agree that there are sceanarios where 406 makes a lot of sense. Ultimately we need some kind of switch for making it possible to respond with 406 but it’s not there yet.

Fine. The 406 response is not mandatory, the Web API is still in beta. I understand the 406 handling not being there yet. And, the last thing I want to do is to get into a discussion of RFC semantics with one of the authors of the specification  :P

Message Handlers to the Rescue

When a HTTP Request is made to a Web API application, that request is passed  to an instance of the HttpServer class. This class derives from HttpMessageHandler and it will delegate the processing of the HTTP request to the next handler present in the HttpConfiguration.MessageHandlers collection. All handlers in the collection will be called to process the request, the last one being the HttpControllerDispatcher that, as you can tell, will dispatch the call to an Action in a Controller. FubuMVC Behavior Chains implements an approach similar to this. (Fubu guys, I know it’s not the same thing, but y’all get my point :))

With that said, in order to return a response with 406 status code, what I need to do is implement a HttpMessageHandler that does that.

With the handler created, the next step is to configure Web API to include this handler in the HTTP Request processing pipeline. To do so, in the Application_Start method in the Global.asax , add an instance of the new handler to the MessageHandlers collection.

With the application configured to use the custom handler, when that same request is made, a response with 406 Status Code is now returned.

The power of Russian Dolls

The Web API HTTP Request pipeline provides a powerful extension point to introduce specialized, SRP adherent, handlers keeping the controller action implementation simple and focused.

  • FubuMVC would have responded with a 406 in the case of not recognizing any possible mimetype or the presence of “*/*”.

    I think almost every web API framework in the .Net space is using some sort of implementation of the Russian Doll model just due to convergent evolution — but I assume you know which one has the cleanest implementation of the pattern.

    • pedroreys

      Yeah, I remember talking to you about this when you were doing the Rest stuff on Fubu.

  • Nice job! I agree with Henrik that it would be nice to have the built in switch to enable it. However, I am very glad to see our message handlers exist and can be put to good use as you did here!

    • pedroreys

      Thanks, Glenn. Definitely the message handlers provides a great way to extend the http request processing. Being able to short circuit the processing and return the 406 as soon as I detect that I can’t handle the requested media type is great.

  • Dmitry M

    From RFC: “If no Accept header field is present, then it is assumed that the client accepts all media types.” So it would probably make sense to call base.SendAsync if no Accept values have been supplied by client. Otherwise you are replying with 406 even thought the client said ‘I accept everything’.

  • Dmitry M

    When checking whether clients explicitly accepts all media types should we also check for quality? When clients sends */*; q=0.0 it is saying something like “I cannot process anything other than the media types listed in the Accept header”. I forked your sample code on Github.

    • Luke McGregor

      I really liked your fork so I’ve improved it (
      https://gist.github.com/2991443 ) to better cope with wildcard types ie application/*. Webapi surfaces wildcards through the MediaTypeMappings collection on the formatter and you can call .TryMatchMediaType to see if any match. this also covers */*.

  • Mr Person

    I found the same issue as Dmitry: You need an (acceptHeader.Count > 0) && before your if(!acceptHeader.Any to make sure that no accept header is permitted.

    Furthermore, this doesn’t take into account .CanWriteType. A formatter may claim to support the indicated media type, but if CanWriteType returns false later, then it will fall back to a default formatter instead of throwing 406.

  • Pingback: [BLOCKED BY STBV] ASP.NET MVC4 Killer Feature: Auto Conneg | eric.polerecky()

  • Sebastian

    Great article, really helped me out!