Everything you want to know about ASP.NET Web API content negotiation

Β· 1787 words Β· 9 minutes to read

One of the key concepts in ASP.NET Web API, lying pretty much at the heart of it, is content negotiation - or simply conneg. I really believe that, before you start developing Web API solutions, you need to understand conneg well.

I thought it would be interesting to try to explain content negotiation in detail - what it is, what it does, and why it does that, especially as I have seen a lot of questions, misconceptions and misunderstandings around it on various boards or question sites.

What is content negotation and how it fits into ASP.NET Web API? πŸ”—

Content negotiation has been defined by W3C within an RFC 2616. According to that document, it is the process of selecting the best representation for a given response when there are multiple representations available. It then mentions that any response containing an entity-body MAY be subject to negotiation, including error responses.

Content negotiation (conneg) in ASP.NET Web API is an intrinsic server-driven (again, using the RFC 2616 wording) mechanism, or algorithm, used to determine, based on the client’s request, which media type formatter is going to be used to return an API response. HTTP client engages in content neogtiation with the Web API, and based on the information available, Web API decides which repsonse format is best. In .NET lingo, it really comes down to deciding on how to send down your CLR object to the client, over HTTP.

Before proceeding further, we need to explain two more important terms, around which the content negotation is built.

  • Media type is defined by W3C in RFC 2616, as being used “in the Content-Type (section 14.17) and Accept (section 14.1) header fields in order to provide open and extensible data typing and type negotiation.” Examples of media type can be “application/rss+xml” or “application/json”.
  • Media type formatter is a class which writes/reads a CLR object to/from the body of the HTTP response/request (depending on the information flow direction).

Where does it live? πŸ”—

Conneg mechanism itself is part of GlobalConfiguration.Services collection and can be retrieved using the GlobalConfiguration.Services.GetContentNegotiation() method.

Conneg formatters are part of the GlobalConfiguration.Formatters collection, and as such can be easily accessed from anywhere within your Web API application context.

By default Web API ships with 4 formatters, in the following order (order is important, since when no good matches can be found, Web API uses formatters in the order they appear):

  • System.Net.Http.Formatting.JsonMediaTypeFormatter, based on JSON.NET
  • System.Net.Http.Formatting.XmlMediaTypeFormatter, based on DataContractSerializer
  • System.Net.Http.Formatting.FormUrlEncodedMediaTypeFormatter, for handling HTML form URL-encoded data
  • System.Web.Http.ModelBinding.JQueryMvcFormUrlEncodedFormatter, for handling model-bound HTML form URL-encoded data

I blogged about building a custom formatter earlier. Ali has a great post about MediaTypeFormatters, so I recommended you check these out.

How content negotiation is performed? πŸ”—

In Web API, there are 4 criteria that are being taken into account in content negotiation. These are analyzed in order of precedence, and if the more important one is a positive match, the others are no longer checked.

1. Media Type Mapping
Media type mapping is intended to be the primary extensibility point for the developers willing to customize content negotiation. Web API ships with 4 different media type mappings:

  • QueryStringMapping,
  • UriPathExtensionMapping,
  • RequestHeaderMapping
  • MediaRangeMapping.

I have blogged about two of those before, so perhaps you might want to check that out later.

Each of those allow you to define specific conditions, such as a presence of a QueryString parameter or an extension in the URL that would result in a response of a specific format (or, in other words, that would warranty a specific media type return). In the linked example above, I show how to wire up “application/rss+xml” responses based on querystring and extension.

Moreover, in addition to the readily available MediaTypeMappings, you can also build your own custom MediaTypeMapping classes. These should have the following pattern:

public class MyMediaTypeMapping : MediaTypeMapping  
{  
protected override double OnTryMatchMediaType(HttpResponseMessage response) {  
//get the Request object  
HttpRequestMessage request = response.RequestMessage;  
//do stuff with the request  
//and return match as a double  
//full match is represented by 1.0  
}  
}  

If the conditions defined by the media type mappings are met, none of the other critteria is analyzed anymore - provided that the returned type can be written (serialized) by formatter selected through the mapping (see point 4 below). If not, the search for the best matched formatter continues.

2. Accept headers in the request
Accept headers issued by the client are the second most important criteria. Let’s say your client makes a request and includes the following headers entry:

GET /api/products/ HTTP/1.1  
Accept: application/json  

This request for specific media type is going to be honored by content negotiation - as long as your application does not have any MediaTypeMappings in place which would overrule this, and, again, provided that the returned type can we written by the given formatter.

3. Content type of the body
The next in line for inspection is the content type of the request. If the request incoming to the Web API is of specific content type (contains specific media type), and neither MediaTypeMappings decide otherwise or Accept headers are not present, conneg alogrithm resorts to inspecting the Content Type of the request.

GET /api/products/ HTTP/1.1  
Content-Type: application/json  

If both Accept and Content-Type are present, obviously Accept takes precedence. As usually, the udnerlying condition is that the formatter can write the given return type.

4. Checking if the formatter can serialize the response content’s model
If, for a given request, none of the mentioned above 3 criteria yields any formatter match, conneg mechanism simply starts going through all the available formatters inthe GlobalConfiguration.Formatters collection in the order they are stored there and checks whether the return type can be written by each formatter.

Upon first match, the search process is terminated and the matched formatter is used by Web API to return the resposne to the client.

Running content negotiation by hand πŸ”—

If you ever wish to do so, you can run conneg manually. This might be useful if you want to return an instance of HttpResponseMessage directly from your controller (rather than a CLR object) or if you need to obtain the result of the content negotiation mechanism to adjust your response based on that (i.e. perhaps you’d expose more data for JSON and less for XML etc.)

Below is a sample action that runs conneg mechanism by hand.

public HttpResponseMessage GetAll() {  
//get all objects of an imaginary MyType from some repository  
IEnumerable<MyType> items = _repo.getAll();  
IContentNegotiator negotiator = Configuration.Services.GetContentNegotiator();  
ContentNegotiationResult result = negotiator.Negotiate(typeof(MyType), Request, Configuration.Formatters);

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

//build up a response with them  
return new HttpResponseMessage()  
{  
Content = new ObjectContent<IEnumerable<MyType>>(  
items, bestMatchFormatter, mediaType  
)  
};  
}  

Bypassing content negotiation πŸ”—

Using the same logic we can bypass content negotiation altogether - if you ever have a reason to do that. To achieve this ,we need our controller’s action to return HttpResponseMessage, and set the formatter arbitrarily to whatever you want it to be.

public HttpResponseMessage GetAll()  
{  
//get all objects of an imaginary MyType from some repository  
IEnumerable<MyType> items = _repo.getAll();

return new HttpResponseMessage()  
{  
//use the first formatter in the formatters collection  
Content = new ObjectContent<IEnumerable<MyType>>(items, Configuration.Formatters[0])  
};  
}  

Customizing content negotiation πŸ”—

You can very easily override the existing content negotiation logic. You’d have to inherit from the System.Net.Http.Formatting.DefaultContentNegotiator class and override any of the existing virtual public methods. The source code for the entire out-of-the-box conneg is available on Codeplex.

Here is a simple example that completely removes request ContentType (criteria 3 metnioned above) from participating in content negotiation. This means that content negotiation doesn’t take the format of the request, when determining the format of the response, into account anymore.

public class MyCustomContentNegotiator : DefaultContentNegotiator  
{  
protected override MediaTypeFormatterMatch MatchRequestMediaType(HttpRequestMessage request, MediaTypeFormatter formatter)  
{  
return null;  
}  
}  

Now we just need to plug in our customized conneg, ideally in Global.asax or anywhere else that you setup your GlobalConfiguration.

GlobalConfiguration.Configuration.Services.Replace(typeof(IContentNegotiator), new MyCustomContentNegotiator());  

This was a trivial example, but hopefully illustrated how easily you can remodel pieces of conneg to suit your needs. Please note that this type of customization is relevant only for the latest Web API builds from Codeplex/Nuget nightliesM (and will be available in Web API RTM). In RC you cannot customize parts of conneg individually, the only exposed piece is the general method Negotiate, which is the entry point to conneg.

In the Web API RTM version you will be able to (or you already are if you use latest source instead of RC) customize the following:

  • protected virtual Collection ComputeFormatterMatches(Type type, HttpRequestMessage request, IEnumerable formatters), used to gather all formatters matches
  • protected virtual MediaTypeFormatterMatch SelectResponseMediaTypeFormatter(ICollection matches), used to select the best formatter match
  • protected virtual MediaTypeFormatterMatch MatchMediaTypeMapping(HttpRequestMessage request, MediaTypeFormatter formatter) used to determine match for MediaTypeMappings
  • protected virtual MediaTypeFormatterMatch MatchAcceptHeader(IEnumerable sortedAcceptValues, MediaTypeFormatter formatter), used to determine match basedrequest’s accept headers
  • protected virtual MediaTypeFormatterMatch MatchRequestMediaType(HttpRequestMessage request, MediaTypeFormatter formatter), used to determine match by inspecting the content type of the request
  • protected virtual MediaTypeFormatterMatch MatchType(Type type, MediaTypeFormatter formatter), used to determine if the formatter can even write the selected Type
  • protected virtual IEnumerable SortMediaTypeWithQualityHeaderValuesByQFactor(ICollection headerValues), used to sort charset, language and related headers by quality
  • protected virtual IEnumerable SortStringWithQualityHeaderValuesByQFactor(ICollection headerValues), used to sort accept charset
  • protected virtual Encoding SelectResponseCharacterEncoding(HttpRequestMessage request, MediaTypeFormatter formatter), used to determine encoding

Implementing custom Conneg πŸ”—

If you are still unhappy with the very deep control and customization possiblities you are already getting with DefaultContentNegotiator, you can also create your entire own content negotiation alogrithm from scratch, by implementing the IContentNegotiator interface.

Consider the following (very simple) scenario. You know that your API is going to be consumed only by clients utilizing JSON data (perhaps your other application is the only consumer of the API, or it’s an internal corporate API). If that’s the case, the entire content negotiation mechanism is simply an overhead for you. In this case, the IContentNegotiator is the perfect hook for you. See the example below.

public class MyCustomContentNegotiator : IContentNegotiator  
{  
public ContentNegotiationResult Negotiate(Type type, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)  
{  
var result = new ContentNegotiationResult(new JsonMediaTypeFormatter(), new MediaTypeHeaderValue("application/json"));  
return result;  
}  
}  

And again, it needs to be plugged in:

GlobalConfiguration.Configuration.Services.Replace(typeof(IContentNegotiator), new MyCustomContentNegotiator());  

This now forces everything you flush out of your API to be in JSON format, using JSON.NET, and the entire default content negotiation algorithm is no longer running for your Web API.

Summary πŸ”—

Content negotiation is a very important concept of ASP.NET Web API. If understood, it becomes a very powerful tool in the API developer’s hands. It simplifies a lot the API design and facilitates the handling of various media types in a very slick way.

On top of that, it’s entirely customizable and if needed, you can easily replace and of its pieces with your own stuff.

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