Recently I've had the pleasure to upgrade our REST interface at work from Asp.Net MVC3 to WebApi so I thought a lessons learned or "watch out for this" blog post was suitable, especially since I managed to do it without the need to bump any version number on our server i.e. no breaking changes.
I think there are others out there that have been using the MVC framework as a pure REST interface with no front end, i.e. dropping the V in MVC, before webapi was available.
First of all webapi is all about registering filters and message handlers and letting requests be filtered through them. A filter can either be registered globally, per controller or per action which imo already is more flexible than MVC.
Metod override header
Webapi doesn't accept the X-HTTP-Method-Override header by default, in our installations we often see that the PUT, DELETE and HEAD verbs are blocked. So I wrote the following message handler which I register in Application_Start.
Exception handling filter
In our controllers we throw HttpResponseExceptions when a resource isn't found or if the request is bad for instance, this is quite neat when you want to short-circuit the request processing pipeline and return a http error status code to the user. The thing that caught me off guard is that when throwing from a controller action the exception filter is not run, but when throwing from a filter the handler is run. After some head scratching I found a discussion thread on codeplex where it's explained that this is intentional, so do not throw exceptions in your filters.
We have a filter which looks at the clients accept header to determine if our versions (server/client) are compatible, this was previously checked in our base controller but with webapi it felt like an obvious filtering situation and we threw Not Acceptable if we weren't compatible. This needed to be re-written to just setting the response on the action context and not calling on the base classes OnActionExecuting which imo isn't as clean design.
Request body model binding and query params
In MVC the default model binder mapped x-www-form-encoded parameters to parameters on the action if the name and type matched, this is not the case with webapi. Prepare yourself to create classes and mark the parameters on your controller with the FromBody attribute even if you only want to pass in a simple integer that is not a part of the URI. Furthermore to get hold of the query params provided in the URL you'll need to pass in the request URI to the static helper method ParseQueryString on the HttpUtility class. It's exhausting but it will work and it still doesn't break any existing implementation.
There are plenty of examples out there on how to post a file with MVC or WebApi so I'm not going to cover that. The main difference here is that the MultipartFormDataStreamProvider needs a root path on the server that specifies where to save the file. We didn't need to do this in MVC, we could simply get the filename from the HttpPostedFiledBase class. I haven't found a way to just keep the file in-memory until the controller is done. I ended up with a couple of more lines of code where I create the attachments directory if it doesn't exist, save the file and then delete it once we've sent the byte data to our services.
MediaFormatters Content-Type header
Any real world REST interface needs to have a custom content type format and by default the xml and json formatter always returns application/xml respectively appliation/json. This is not good enough, I suggest that you create custom implementations of JsonMediaTypeFormatter and XmlMediaTypeFormatter and insert them first in your formatters configuration. In your custom formatter just add your media type that includes the vendor and version to the SupportedMediaTypes collection. In our case we also append the server minor version to content type as a parameter, the easiest way to do that is by overriding the SetDefaultContentHeaders method and append whichever parameter you want to the header content-type header.
I think that covers it all, good luck migrating your REST api!
I might cover upgrading from WIF 3.5 (Microsoft.IdentityModel) to WIF 4.5 in my next post, or thinktectures startersts to identity server v2. Take a wild guess what I've been busy with at work! ;-)