ASP.NET Web API MediaTypeFormatters With MediaTypeMappings

We will see how Content-Negotiation (Conneg) Algorithm works on ASP.NET Web API with MediaTypeFormatters and MediaTypeMappings
2012-03-03 11:38
Tugberk Ugurlu


If you have read my post on Getting Started With ASP.NET Web API, you probably saw me talking about exposing your data to the world with various types of formats. This feature has been made possible by formatters. Formatters handles serializing and deserializing strongly-typed objects.

For two days, I have been really looking into formatters and explored a lot of useful stuff and I thought that sharing those would be great.

Before diving deeply, ASP.NET Web API team moderating ASP.NET Web API Forum pretty often and provide you a way to solve your problems. Also, community is heavily involved. Hence the framework is so new, I was stuck at a few places but team and the community helped me out a lot.

One other place you should be aware of is ASP.NET Web API Reference on MSDN.

ASP.NET Web API beta was shiped with 3 different formatters: JsonMediaTypeFormatter, XmlMediaTypeFormatter, FormUrlEncodedMediaTypeFormatter. All these classes are derived from MediaTypeFormatter abstract class. As you might guess, it is fairly easy to create one and hook it up but in this post, I won’t talk about custom MediaTypeFormatters. I would like to talk about how they are being chosen and assigned to process the request by the framework, especially on MediaTypeMappings.

ASP.NET Web API decides which formatter to process request with according to its Content-Negotiation (Conneg) Algorithm. Kiran Challa has two great blog posts on this:

On these posts, you will find how the Conneg algorithm works inside the framework. It has various options and as default it looks at the http headers to decide the most suitable format.

For this post, I have created a very simple Web API project. I did that by creating an empty ASP.NET Web Application, installing AspNetWebApi nuget package (had to install System.Json package separately). Then I registered my route:

protected void Application_Start(object sender, EventArgs e) {

    GlobalConfiguration.Configuration.Routes.MapHttpRoute(
        "defaultHttpRoute",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );
}

Finally, I created a simple API:

public class CarsController : ApiController {

    public string[] Get() {

        return new string[] { 
            "BMW",
            "Ferrari",
            "FIAT"
        };
    }
}

When I fire up the development web server IIS Express and navigate to /api/cars, I get the list of cars as expected. This is not the clearest way of explaining it, is it? Let’s see the headers:

Request:

GET http://localhost:4446/api/cars HTTP/1.1
User-Agent: Fiddler
Host: localhost:4446

Response:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcRHJvcGJveFxBcHBzXEFTUE5FVFdlYkFQSVNhbXBsZXNcQ29ubmVnQWxnb3JpdGhtU2FtcGxlXHNyY1xDb25uZWdBbGdvcml0aG1TYW1wbGVcYXBpXGNhcnM=?=
X-Powered-By: ASP.NET
Date: Sat, 03 Mar 2012 10:52:49 GMT

18
["BMW","Ferrari","FIAT"]
0

As you see, we have the response back as json because it is the first formatter registered (yes, order matters) by default and we didn’t specify which format we are interested in. When you add "Accept: application/xml" to your request, you will see that you will be getting the response back as xml. Approve it or not, this is the RESTFul way of negotiating between client and server. But sometimes we would like to decide the format according to QueryString. If so, you have an OOB support for this.

Intro to MediaTypeMappings

By default, Accept and Request Content-Type headers play role on deciding which format you serve. One other way of involving a formatter to process your request is MediaTypeMapping.

MediaTypeMapping provides a way for us to participate the Conneg algorithm decision making process and decide if we would like the formatter to take part in writing the response. There are several built in MediaTypeMappings (actually 4) supported out of the box. These are QueryStringMapping, RequestHeaderMapping, UriPathExtensionMapping, MediaRangeMapping. All these classes are derived from MediaTypeMapping abstract class (yes, creating a custom one is tedious and I plan on writing a post on that as well). We have these mappings and the other great stuff is that all default formatters has a hook up point in order to register mappings.

Let’s assume that we would like to decide the format of response based on a query string value as well. As we have QuesryStringMapping, we can use this and can provide our data on json format if request comes with ?format=json quesry string and xml format if it is ?format=xml. Here is the configuration in order to enable this:

protected void Application_Start(object sender, EventArgs e) {

    GlobalConfiguration.Configuration.Routes.MapHttpRoute(
        "defaultHttpRoute",
        routeTemplate: "api/{controller}"
    );

    GlobalConfiguration.Configuration.Formatters.JsonFormatter.
        MediaTypeMappings.Add(
            new QueryStringMapping(
                "format", "json", "application/json"
        )
    );

    GlobalConfiguration.Configuration.Formatters.XmlFormatter.
        MediaTypeMappings.Add(
            new QueryStringMapping(
                "format", "xml", "application/xml"
        )
    );
}

When we make a request with accept header and format query string, we will see that framework honors our mapping registrations:

Request:

GET http://localhost:4446/api/cars?format=xml HTTP/1.1
User-Agent: Fiddler
Host: localhost:4446
Accept: appication/json

Response:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Transfer-Encoding: chunked
Content-Type:
application/xml
Expires: -1
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcRHJvcGJveFxBcHBzXEFTUE5FVFdlYkFQSVNhbXBsZXNcQ29ubmVnQWxnb3JpdGhtU2FtcGxlXHNyY1xDb25uZWdBbGdvcml0aG1TYW1wbGVcYXBpXGNhcnM=?=
X-Powered-By: ASP.NET
Date: Sat, 03 Mar 2012 11:36:04 GMT

e9
<?xml version="1.0" encoding="utf-8"?><ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><string>BMW</string><string>Ferrari</string><string>FIAT</string></ArrayOfString>
0

Pretty powerful stuff. Enjoy Smile



Comments

John
by John on Monday, Mar 05 2012 23:28:10 +02:00

how do I use xml string in post method?. I can't map it to a class since the format is unknown. webapi fails with mediaformat error. I tried using StringContent in my post method and removed the jsonformatter but doen't help.

ram
by ram on Saturday, Nov 22 2014 14:19:20 +00:00
How can we access HTTPRequestMessage in MediaTypeFormatter Method? I need querystring values in WriteToStreamAsync method. (ex; /api/MYController.json?select=a,b,c) public class MyCustomFormatter : MediaTypeFormatter { public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext) { //Need HttpRequestMessage object to get query string values //HttpContext.Current is always null and "GetPerRequestFormatterInstance" is calling only once not for each call. } }

New Comment