Content Negotiation in ASP.NET MVC4 Web API Beta – Part 1
This is my first blog post…Yay! .
In this blog post, I am going to describe about the Default Content Negotiation Algorithm that gets shipped as part of ASP.NET MVC4 Beta. The Web API provides a lot of nice features with which you can build RESTful services. For more details, you can look at the release notes here.
Throughout the rest of this post, I will refer to Content-Negotiation simply as Conneg.
I use the tool called Fiddler to capture the requests and responses. I find it very useful for debugging purposes.
1. If a Request message is sent with an Accept header, Conneg algorithm will use the Accept header to decide about the Response media type and the MediaTypeFormatter to write.
This should make sense to you as here the client is asking for a response in a specific format.
Example:
Request:
GET https://kirandev:9090/DefaultConNegAlgorithmTests/GetData HTTP/1.1
Accept: application/xml Host: kirandev10:9090
Connection: Keep-AliveResponse:
HTTP/1.1 200 OK
Content-Length: 60
Content-Type: application/xml; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Sat, 25 Feb 2012 21:03:47 GMT<?xml version="1.0" encoding="utf-8"?><string>Hello</string>
2. If a Request message is sent with NO Accept header and with a Content-Type header (let’s say when you are using POST to post content to the server), Conneg algorithm will use the Content-Type header to decide about the Response media type and the MediaTypeFormatter to write.
Here the Conneg algorithm uses the best-effort approach to judge which format the client could understand. Since here the client was able to post content in a particular format(let’s say ‘application/xyz’), Conneg algorithm deduces that the Client could probably understand the Response also in the same format(‘application/xyz’).
Example:
Request:
POST https://kirandev:9090/DefaultConNegAlgorithmTests/PostData HTTP/1.1
Content-Type: application/xml Host: kirandev10:9090
Content-Length: 60
Expect: 100-continue
Connection: Keep-Alive<?xml version="1.0" encoding="utf-8"?><string>Hello</string>
Response:
HTTP/1.1 200 OK
Content-Length: 60
Content-Type: application/xml; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Sat, 25 Feb 2012 21:12:34 GMT<?xml version="1.0" encoding="utf-8"?><string>Hello</string>
3. If a Request message is sent with an Accept header and also a Content-Type header, Conneg algorithm will use the Accept header to decide about the Response media type and the MediaTypeFormatter to write.
This should make sense to you as here the Client is asking for a Response in a specific format.
Example:
Request:
POST https://kirandev10:9090/DefaultConNegAlgorithmTests/PostData HTTP/1.1
Accept: application/xml
Content-Type: application/json
Host: kirandev10:9090
Content-Length: 7
Expect: 100-continue
Connection: Keep-Alive"Hello"
Response:
HTTP/1.1 200 OK
Content-Length: 60
Content-Type: application/xml; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Sat, 25 Feb 2012 21:09:59 GMT<?xml version="1.0" encoding="utf-8"?><string>Hello</string>
4. A variation to “Point 3.” above: If a Request message is sent with an Accept header and also a Content-Type header, then Conneg algorithm will use the Accept header to decide about the Response media type and the MediaTypeFormatter to write.
Now, if the Conneg algorithm wasn’t able to find a formatter for the supplied media type in the Accept header, then it uses the best-effort approach and uses the Content-Type header to decide about the Response media type and the MediaTypeFormatter to write.
Example:
Request:
POST https://kirandev:9090/DefaultConNegAlgorithmTests/PostData HTTP/1.1
Accept: application/nonexisting
Content-Type: application/xml
Host: kirandev10:9090
Content-Length: 60
Expect: 100-continue
Connection: Keep-Alive<?xml version="1.0" encoding="utf-8"?><string>Hello</string>
Response:
HTTP/1.1 200 OK
Content-Length: 60
Content-Type: application/xml; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Sat, 25 Feb 2012 21:15:50 GMT<?xml version="1.0" encoding="utf-8"?><string>Hello</string>
5. For ALL the above scenarios from 1 to 4, if the Conneg algorithm wasn’t able to find a suitable formatter to write the Response, then by default it would send back content using the 1st formatter in the collection of MediaTypeFormatters present on the Configuration object.
Example:
Request:
GET https://kirandev10:9090/DefaultConNegAlgorithmTests/GetData HTTP/1.1
Accept: application/nonexisting
Host: kirandev10:9090
Connection: Keep-AliveResponse(Here the 1st formatter in my list is a JsonMediaTypeFormatter and hence the response is in Json format):
HTTP/1.1 200 OK
Content-Length: 7
Content-Type: application/json; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Sat, 25 Feb 2012 21:25:40 GMT"Hello"
6. If for a particular reason, a user needs to always send a Response in a specific format and NOT caring about the Content Negotiation, we have support for it. For example, in the following piece of code, the Action called “GetOrder” is specifically setting the media type to be “application/xml”. So, Web API honors this and will not let the Conneg algorithm to overwrite it irrespective of the Request’s Accept header media type.
public class OrdersController : ApiController { public HttpResponseMessage GetOrder() { Order order = new Order(); order.Id = "A100"; order.OrderedDate = DateTime.Now; order.OrderTotal = 150.00; HttpResponseMessage<Order> response = new HttpResponseMessage<Order>(order, new MediaTypeHeaderValue("application/xml")); return response; } }
Example:
Request:
GET https://kirandev:9090/DefaultConNegAlgorithmTests/GetOrder HTTP/1.1
Accept: application/json
Host: kirandev10:9090
Connection: Keep-Alive
Response:
HTTP/1.1 200 OK
Content-Length: 253
Content-Type: application/xml; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Sat, 25 Feb 2012 22:36:22 GMT
<?xml version="1.0" encoding="utf-8"?><Order xmlns:xsd="https://www.w3.org/2001/XMLSchema" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"><Id>A100</Id><OrderedDate>2012-02-25T14:36:21.4774615-08:00</OrderedDate><OrderTotal>150</OrderTotal></Order>
I hope you guys find this post useful. I will be posting more content in near future. Keep playing with our bits!
Also there is a forum regarding ASP.NET Web API over here. It’s very active and post any questions that you might have.
Comments
Anonymous
February 25, 2012
What role does the actual data being serialized play in conneg? I've noticed that sending Accept: application/xml will result in the default (JSON) formatter if the data being serialized contains collection (IEnumerable<T>, for example).Anonymous
February 26, 2012
Good question Jesse. I was planning to add these details in my next post. As part of the con-neg algorithm run, media type formatters are asked if they would be able to write a particular type or not(the CanWriteType property on the MediaTypeFormatter is for this purpose). If no formatters were found which can write the data, as a last resort it tries to use the first formatter in the list of formatters to write it. In the case you mentioned, if a particular type is not serializable by the XmlMediaTypeFormatter, it doesn't participate in writing the response. Now since it couldn't find any formatters to write, as a last resort it tries to use the 1st formatter in the list (here JsonMediaTypeFormatter). Hence, you are seeing data in Json format. if let's say JsonMediaTypeFormatter also wasn't able to write the type, then a 500 Internal Server Error response is sent back to the client. I would like to know your opinion about this behavior. I am assuming in your scenario, a better idea would be to send a 406 response with Json data i suppose..?Anonymous
March 02, 2012
I think a better question here is: why can't the XML formatter serialize enumerables? Also, the JSON formatter doesn't serialize dictionaries in a very JSON way. It serializes them as { "Key" : "TheKey, "Value" : "TheValue" } whereas for JSON I would expect { "Key" : "TheValue" }.Anonymous
March 05, 2012
Thank you very much for tip #6. This problem has been killing me for hours now. I made a MediaTypeFormatter to handle image/jpg. In Fiddler everything is fine because I can set the Accept header to image/jpg. But the normal html img request was sending Accept: / so my media formatter was not getting control. With your solution, I am now able to force my media formatter to run. Thanks! Was I doing something wrong the other way? Is there a way to tell html img to set Accept header? Using Fiddler to monitor normal web traffic I notice that sometimes img request included Accept image/* (or other specific type) and sometimes it is just /.Anonymous
March 05, 2012
@Art Dumas : I am glad it worked for you. I am not sure how to force html img to set the Accept header. Which browser are you using? BTW, if you want your media type formatter to handle even the "image/" and "/" requests, then you could do by adding a MediaRangeMapping to the formatter. Example: jpegFormatter.AddMediaRangeMapping(new MediaTypeHeaderValue("image/", new MediaTypeHeaderValue("image/jpeg"));Anonymous
March 05, 2012
OK. I did not know about MediaRangeMapping for /. That probably would have solved my problem too without having to return HttpResponseMessage. Will give it a try tomorrow. i am using IE 9. I can see that for some web sites, img load sends image/jpg, etc. but for my test case i am seeing /. This is when I set img src attr to the url of my controller which takes one parameter (the unique id of my record being fetched through CSLA based business object layer). Thanks!Anonymous
March 06, 2012
One question - in all other examples the actions are named after the HTTP verbs but in #6 you have an action named GetOrders - what does this do?Anonymous
June 23, 2015
Very useful, especially #6. Also the comment on IEnumerable. Newer apps should default to JSON and move away from XML as its more verbose. I wish there was support for JSON in older clients.Anonymous
November 05, 2015
Simple and wonderful explanation about Content Negotiation. Thanks