Force JSON DelegatingHandler for ASP.NET Web API
Building real world REST API-s
When you decide to build real world REST API-s like Facebook or Foursquare and many others have you must think on lot of things such as security, scoping, throttling and similar.
One of the first thing you will encounter is need to support various media formats such as JSON, XML, PNG … in easy way.
ASP.NET Web API support JSON and XML formats out of the box and provide us with easy way to support any custom mediaType format.
I will try to provide you with series of posts in which I will try to explain how I have created real world REST API using ASP.NET Web API.
Common ways to enforce custom format
There are several ways to enforce custom format in REST API :
1) You can pass mediaType in request header .
2) You can pass mediaType in URI using following syntax : http://api.mySite.com/v1/contacts/1/JSON
3) You can pass mediaType in URI using following syntax : http://api.mySite.com/v1/contacts/1.JSON
4) You can pass mediaType in URI using following syntax : http://api.mySite.com/v1/contacts/1?mediaType=JSON
My preferred solution using ASP.NET Web API
First approach – I will support
In REST API you will sure want to support first approach in which user passes mediaType in request header.
Second approach – I will not support
For second approach things are little difficult .
To support this way you will probably need to change routes like this where you pass optional parameter ext for mediaType extension :
config.Routes.MapHttpRoute( "Default", "{controller}/{id}/{ext}", new { id = RouteParameter.Optional, ext = RouteParameter.Optional } );
Problem with this approach is that your code will only work for URI like this where you try to get particular contact with id 1 in JSON format:
http://api.mySite.com/v1/contacts/1/JSON
When you try to get all contacts in JSON format using URI like this it will fail:
http://api.mySite.com/v1/contacts/JSON
Instead of trying to get all contacts in that format in will try to find contact which has id with value “JSON” .
One possible way to resolve this is to have two controllers for same model. One called ContactController another ContactsController.
I just don’t prefer this solution. If you know any other solution to resolve this feel free to comment. You can read more about this in this sites :
http://wekeroad.com/2012/02/28/someone-save-us-from-rest/
Third approach – I will not support
You may be wondering why it is so nice and clean? I don’t like dots in URI in that way. You cannot parse that QueryString safely to get that extensions behind dot.
You can try do something like this :
requestString.EndsWith(".JSON")
But then you will get another wall. In this version of ASP.NET Web API when you try to change Request.RequestUriinside DelegatingHandlers method SendAsync and pass changed RequestUri to base, ASP.NET Web API still try to goto old RequestUri. It seams like routing is performed before DelegatingHandler…So if we try to get mediaType in DelegatingHandler from RequestUri :
http://api.mySite.com/v1/contacts/1.JSON
then substring URI to become:
http://api.mySite.com/v1/contacts/1
and pass new RequestUri to base we will not be successful.
Fourth approach – I will support
I have choose to support fourth approach. For using this approach you will need to add this line of code in GlobalAsax.cs :
config.MessageHandlers.Add(new UriFormatExtensionMessageHandler());
Here is my code for UriFormatExtensionMessageHandler :
public class UriFormatExtensionMessageHandler : DelegatingHandler { private bool shouldForceJson; private bool sholudForceXml; public void CheckFormats(HttpRequestMessage request) { // uri must contain mediaType parameter to enforce that media type string query = request.RequestUri.Query; string mediaType = HttpUtility.ParseQueryString(query).Get("mediaType"); if (mediaType != null) { switch (mediaType) { case "JSON": shouldForceJson = true; break; case "XML": sholudForceXml = true; break; } } } protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { CheckFormats(request); if (shouldForceJson) { // clear the accept and replace it to use JSON. request.Headers.Accept.Clear(); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); shouldForceJson = false; } if (sholudForceXml) { request.Headers.Accept.Clear(); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml")); sholudForceXml = false; } return base.SendAsync(request, cancellationToken); } }
UPDATE :
For fourth approach Microsoft has provided solution in this version of WebApi.You should you code bellow :
config.Formatters.JsonFormatter.MediaTypeMappings.Add( new QueryStringMapping("mediaType", "json", "application/json")); config.Formatters.XmlFormatter.MediaTypeMappings.Add( new QueryStringMapping("mediaType", "xml", "application/xml"));
If you want to learn more about ASP.NET Web API I recommend a great book that I love written by real experts:
Designing Evolvable Web APIs with ASP.NET