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