First of all, you might be wondering why would I create a REST service with MVC when I can use WCF 4.0, WCF Starter Kit Preview 2 (deprecated), or even the new WCF Web APIs. The short answer is, because websites are, in practice, RESTful. So MVC has some interesting built in support for REST. So when thinking about MVC as a framework for REST services, we could actually ask ourselves “why not?”. As for the rest of the frameworks, each of them have also good reasons to be used, and I would say that you should use which ever fits best for your particular scenario.
Having said that, we must not overlook the fact that MVC has been released for websites (primarily), and as such we have excellent support for UI stuff, and we lack some support for other stuff we might need when building services. The lack of support for content negotiation, and media type based serialization is what I will discuss in this post.
For the following snipets, I will be using this very simple interface for serializing/deserializing based on a content-type.
Deserializing based on content type
Let’s look at one example
MVC will not deserialize parameters based on the content-type header of the HTTP request. This means that if we want to send an HTTP request with content-type “application/xml” and send an XML representation of the order, MVC will not know what to do with it. On the other hand, we must take into account, that the id parameter will most likely be bound to a Uri parameter using the routing service, and we don’t want to mess that up.
Another thing we don’t want to overlook, is the fact that when deserializing the body, we can only deserialize one parameter, the rest should come through other means (uri, headers, cookies, etc).
MVC does give us some interesting extensibility points where to accomplish this. When trying to deserialize the body of the request, we could use the help of a model binder. Each parameter of your Controller method, is a Model in MVC. And every model is “binded” (deserialized) using a Model binder. The most common binder we usually use (probably wihout knowing) is a binder which binds the parameters using the requested Uri based on the registered routes. Model binders in MVC are registered on the Global.asax file using the ModelBinders.Binders collection. One of the annoying things about registering a new IModelBinder is that you must register a type for which this binder will be used. This is not ideal, since in our case, we want to deserialize whatever we have in the body.
The other alternative we have is to add a CustomModelBinderAttribute on each attribute we want to deserialize using our media type based binder (take a look at this post for this approach).
In my case, I only want to use two types of parameters, Uri parameters based on routes, and the body parameter (when POSTing or PUTing). This means I will take a slightly different approach and assume that if the parameter is not in the route data, then it must come from the body. The easiest way to create a binder that is not associated to any particular type, and without loosing the out of the box binding functionality, is to inherit from DefaultModelBinder, and register our custom binder as the default. The code is very simple and uses the content-type header in the request to pick a formatter and do the deserialization.
If the parameter is in the routed data, then we use the default binder. If it’s not, we use the right formatter based on the content type.
Serializing based on Accept header
Another thing about MVC is that it will pick up the right view for you and render the response using that view. In our case, we don’t need a conventional view, but rather a serialization of the response. This can be done using another extensibility point from MVC called ActionFilters. Action filters are attributes with which you can decorate the methods you want, or which you can register globally to run in every request. Action filters run before and after the controller action is invoked, so you can intercept the View that the controller returned, and perform your serialization on the View’s model.
The code is very similar to the Binder, except this time we are looking in the HTTP accept header for serialization.
Just so that you can have the entire implementation, I leave you the Xml and Json formatters.
Here’s how to set it up in the Global.asax.cs file.
So know you can make your controller actions media-type agnostic, and let something else worry about how to handle serialization.