Web API Content Negotiation

Inside Web API Content Negotiation

“Content negotiation” is often used to describe the process of inspecting the structure of an incoming HTTP request to figure out the formats in which the client wishes to receive responses. Technically, though, content negotiation is the process in which client and server determine the best possible representation format to use in their interactions. Inspecting the request typically means looking into a couple of HTTP headers such as Accept and Content-Type. Content-Type, in particular, is used on the server for processing POST and PUT requests and on the client for choosing the formatter for HTTP responses. Content-Type is not used for GET requests.

The internal machinery of content negotiation, however, is much more sophisticated. The aforementioned scenario is the most typical—because of default conventions and implementations—but it isn’t the only one possible.

The component that governs the negotiation process in Web API is the class called DefaultContentNegotiator. It implements a public interface (IContentNegotiator), so you can replace it entirely if needed. Internally, the default negotiator applies several distinct criteria in order to figure out the ideal format for the response.

The negotiator works with a list of registered media type formatters—the components that actually turn objects into a specific format. The negotiator goes through the list of formatters and stops at the first match. A formatter has a couple of ways to let the negotiator know it can serialize the response for the current request.

The first check occurs on the content of the MediaTypeMappings collection, which is empty by default in all predefined media type formatters. A media type mapping indicates a condition that, if verified, entitles the formatter to serialize the response for the ongoing request. There are a few predefined media type mappings. One looks at a particular parameter in the query string. For example, you can enable XML serialization by simply requiring that an xml=true expression is added to the query string used to invoke Web API. For this to happen, you need to have the following code in the constructor of  your custom XML media type formatter:

MediaTypeMappings.Add(new QueryStringMapping("xml", "true", "text/xml"));

In a similar way, you can have callers express their preferences by adding an extension to the URL or by adding a custom HTTP header:

MediaTypeMappings.Add(new UriPathExtensionMapping("xml", "text/xml"));
MediaTypeMappings.Add(new RequestHeaderMapping("xml", "true",
  StringComparison.InvariantCultureIgnoreCase, false,"text/xml"));

For URL path extension, it means the following URL will map to the XML formatter:

http://server/api/news.xml

Note that for URL path extensions to work you need to have an ad hoc route such as:

config.Routes.MapHttpRoute(
  name: "Url extension",
  routeTemplate: "api/{controller}/{action}.{ext}/{id}",
  defaults: new { id = RouteParameter.Optional }
);

For custom HTTP headers, the constructor of the RequestHeaderMapping class accepts the name of the header, its expected value and a couple of extra parameters. One optional parameter indicates the desired string comparison mode, and the other is a Boolean that indicates if the comparison is on the entire string. If the negotiator can’t find a match on the formatter using the media type mapping information, it looks at standard HTTP headers such as Accept and Content-Type. If no match is found, it again goes through the list of registered formatters and checks whether the return type of the request can be serialized by one of the formatters.

To add a custom formatter, insert something like the following code in the startup of the application (for example, in the Application_Start method):

config.Formatters.Add(xmlIndex, new NewsXmlFormatter());

Customizing the Negotiation Process

Most of the time, media type mappings let you easily fulfill any special requirements for serialization. However, you can always replace the default content negotiator by writing a derived class and overriding the MatchRequestMediaType method:

protected override MediaTypeFormatterMatch MatchRequestMediaType(
  HttpRequestMessage request, MediaTypeFormatter formatter)
{
  ...
}

You can create a completely custom content negotiator with a new class that implements the IContentNegotiator interface. Once you have a handmade negotiator, you register it with the Web API runtime:

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

The preceding code usually goes in global.asax or in one of those handy config handlers that Visual Studio creates for you in the ASP.NET MVC Web API project template.

Controlling Content Formatting from the Client

The most common scenario for content negotiation in Web API is when the Accept header is used. This approach makes content formatting completely transparent to your Web API code. The caller sets the Accept header appropriately (for example, to text/xml) and the Web API infrastructure handles it accordingly. The following code shows how to set the Accept header in a jQuery call to a Web API endpoint to get back some XML:

$.ajax({
  url: "/api/news/all",
  type: "GET",
  headers: { Accept: "text/xml; charset=utf-8" }
});

In C# code, you set the Accept header like this:

var client = new HttpClient();
client.Headers.Add("Accept", "text/xml; charset=utf-8");

Any HTTP API in any programming environment lets you set HTTP headers. And if you foresee that you can have callers where this might be an issue, a best practice is to also add a media type mapping so the URL contains all the required information about content formatting.

Bear in mind that the response strictly depends on the structure of the HTTP request. Try requesting a Web API URL from the address bar of Internet Explorer 10 and Chrome. Don’t be surprised to see you get JSON in one case and XML in the other. The default Accept headers might be different in various browsers. In general, if the API will be publicly used by third parties, you should have a URL-based mechanism to select the output format.

Scenarios for Using Web API

Architecturally speaking, Web API is a big step forward. It’s becoming even more important with the recent Open Web Interface for .NET (OWIN) NuGet package (Microsoft.AspNet.Web­­Api.Owin) and Project Katana, which facilitate hosting the API in external apps through a standard set of interfaces. If you’re building solutions other than ASP.NET MVC applications, using Web API is a no-brainer. But what’s the point of using Web API within a Web solution based on ASP.NET MVC?

With plain ASP.NET MVC, you can easily build an HTTP façade without learning new things. You can negotiate content fairly easily with just a bit of code in some controller base class or in any method that needs it (or by creating a negotiated ActionResult). It’s as easy as having an extra parameter in the action method signature, checking it and then serializing the response to XML or JSON accordingly. This solution is practical as long as you limit yourself to using XML or JSON. But if you have more formats to take into account, you’ll probably want to use Web API.

As previously mentioned, Web API can be hosted outside of IIS—for example, in a Windows service. Clearly, if the API lives within an ASP.NET MVC application, you’re bound to IIS. The type of hosting therefore depends on the goals of the API layer you’re creating. If it’s meant to be consumed only by the surrounding ASP.NET MVC site, then you probably don’t need Web API. If your created API layer is really a “service” for exposing the API of some business context, then Web API used within ASP.NET MVC makes good sense.