A few days ago, a Rest for ASP.NET MVC SDK was released by the WCF team onto CodePlex. I spotted this very low key release after it was mentioned on Phil Haack’s blog. He asks for feedback on the ideas in the SDK and samples, as many of them are being considered for future releases of the main ASP.Net MVC. So after an initial tyre kicking, I thought I’d look at how hard it would be to migrate my web service to this SDK, what would be the benefits and would I have to make any compromises.
So what am I looking for in an SDK or framework that makes creating a RESTful web service easier to develop on top of ASP.Net. Well the things I worked on in the last 16 posts were (not in priority order):
- Multiple representations
- Injecting “Connectedness” into representations
- Overloading of POST
- Uniform interface (both method calls and responses)
The following describes what I found and the changes I made to the SDK to get closer to my goals. In the source code, all my edits are marked with a comment starting // Piers Lawson.
The approach we have taken up to now for supporting multiple representations is to derive from the MVC Controller class a RestfulController that returned different ActionResults depending on the format requested. Instead of an action method calling View(), it would call SelectActionResult(). SelectActionResult contained a switch statement that would return either an XsltResult, a JsonConverterResult or the standard ViewResult.
The SDK takes another approach. The developer decorates a normal controller with a WebApiEnabledAttribute. This attribute derives from ActionFilterAttribute and IExceptionFilter. When ASP.Net MVC finds a controller that is decorated with an ActionFilterAttribute, one of the methods it calls is OnActionExecuted. This allows the attribute to make changes to the ActionResult returned by the controller. WebApiEnabledAttribute overrides this method and takes the opportunity to return a MultiFormatActionResult instead of the existing ActionResult. The MultiFormatActionResult is provided in the SDK and its purpose is to allow different representations, other than the standard ASPX based HTML pages, to be returned.
I like the SDK’s approach as it means developers simply decorate a normal controller class with an attribute in order to support multiple representations. My approach is more invasive, code wise, in that the developer must derive their controller from a different base class and call a different method, SelectActionResult, compared to normal ASP.Net MVC code.
Having said that I like the approach, I found there were some limitations in the implementation that meant I needed to make changes to the defaults and the SDK code in order to support my goals fully:
- By default the SDK inspects the “Accept” header of the request to decide which representation to serve… but as I mentioned previously, I prefer to be able to pass this in the URI. The SDK will inspect the URI as well, but only if it believes the request is from a browser;
- When deciding which format has been used to call the web service, the SDK only considers the “Content-Type” header. If that header is not found it follows Hypertext Transfer Protocol (HTTP/1.1) RFC 2616 7.2.1 and defaults to "application/octet-stream".
So my first edit of the SDK was within the HttpHelper class. In the method TryGetFromUri() I removed all the testing of whether the client appeared to be a browser. This means the SDK will always check the querystring part of the URI for a format parameter. So if the client requests:
the SDK will pick up the “xml” and try to translate the friendly “xml” into a more explicit Accept type (in this case it would be “application/xml”).
My second edit was also in the HttpHelper class. Within the GetRequestFormat() method, if the content type is not present in the request, I added a check of the URI’s querystring for a format parameter. This allows a simple client to send data to the web service without having to edit the request headers. RFC 2616 7.2.1 does allow for this, stating:
If and only if the media type is not given by a Content-Type field, the recipient MAY attempt to guess the media type via inspection of its content and/or the name extension(s) of the URI used to identify the resource.
The changes described so far were my original work arounds for the issues I found. I’ll come back to them later to describe my final approach!
Within the SDK, serialisation and deserialisation of data is performed by FormatHandlers. Out of the box, the SDK provides two FormatHandlers… one for JSON and one for XML representations. Unfortunately, neither provides a way to transform the outgoing or incoming data. This means there is no way to inject connectedness into the data. To my mind, this means the View part of MVC is not possible for XML and JSON representations.
Our web service does support both XML and JSON representations being transformed… allowing connectedness to be injected and any other changes to be made (for instance adding namespaces) before data is returned to the client or on receipt of data from the client.
FormatManager.Current = new RestfulFormatManager();
In order to support simple clients it is useful for a web service to support overloading of the POST method to allow them to call PUT, DELETE etc, as described in my earlier post. Our web service uses an attribute called AcceptVerbsAndOverloadsAttribute that is derived from ActionMethodSelectorAttribute. We use this in place the AcceptVerbsAttribute. The AcceptVerbsAttribute is used to specify which HTTP Verbs can be used to call a particular method on the Controller. ActionMethodSelectorAttribute extends this by looking for overloading of the POST method and uses the replacement HTTP Verb instead of POST when deciding if the method can be used.
The SDK uses a different approach, that doesn’t require a custom attribute. The SDK provides the ResourceControllerFactory (which derives from IControllerFactory) to replace the standard ASP.Net MVC DefaultControllerFactory. This can be registered at application startup in Global.ascx.cs:
ResourceControllerFactory factory = new ResourceControllerFactory();
Within the ResourceControllerFactory’s constructor it creates a reference to the original ControllerFactory. When CreateController() is called, the original ControllerFactory is used to find the correct Controller. ResourceControllerFactory then checks the controller for the WebApiEnabledAttribute. If it finds the attribute, it substitutes its own ControllerActionInvoker called ResourceControllerActionInvoker. When FindAction() is called on this class, it inspects the Controller’s methods for one with the appropriate AcceptVerbsAttribute attached. It is this class that allows for an overloaded POST HTTP Request, checking for either a "X-Http-Method-Override" header or, if the client is a browser, a FORM variable with an Id of "X-Http-Method-Override".
So the advantage of the SDK approach is that again, developers don’t have to use custom attributes in their controllers. However, I found a few disadvantages to the actual SDK implementation the most important being:
- The ResourceControllerActionInvoker will only look for controller methods based on the HTTP Verb if the route currently being handled does not have an “action”. I guess because the HTTP Verb could be considered the Action. However, this precludes anything but simple routing scenarios. Because the SDK code only looks for the AcceptVerbsAttribute to decide which controller methods could be used, if your routes mean a single Controller has to handle the same HTTP Verb more than once, an exception is thrown. In our web service, the Products controller legitimately contains several methods that accept the same HTTP Verbs.
- Overloading of POST relies on the "X-Http-Method-Override" header for anything other than browsers. Even for browsers, the code only looks within the Form variables for an override, ignoring the Querystring as an alternative.
If the ResourceControllerActionInvoker finds an “action” is present in the route, it simply asks the original ControllerFactory to handle the FindAction(). I started working at replacing the ControllerContext passed into that FindAction() with my own that overrode the:
property with my own implementation. My implementation checked for an overloaded POST before returning the true HTTP Verb (including checking the Querystring). I got this working, but then thought: if we are replacing the HTTP Verb, surely the best place to do that is as early as possible and as close to source as we are able?
Basically, both our current approach and the SDK approach to overloading POST are necessary because the underlying System.Web.HttpRequest used throughout ASP.Net is fundamentally read only. We can’t change the value its HttpMethod property returns.
However, ASP.Net MVC does not use HttpRequest directly, it is always passed around within a wrapper derived from HttpRequestBase. This is how all the unit tests are able to run outside of a real web server. The default implementation of HttpRequestBase used within ASP.Net MVC is HttpRequestWrapper.
So, if we derived a class from HttpRequestBase and have ASP.Net MVC use it instead of HttpRequestWrapper, we could intercept calls to the HttpMethod property. In our implementation we could provide overloading of POST. The benefit of this approach is that the rest of the application will have no idea that the overloading is in place!
Creating our own version of HttpRequestBase is the easy part. Just derive from HttpRequestWrapper and override the HttpMethod property. But how do we get ASP.Net MVC to use our wrapper instead of the default? There isn’t any configuration option available. It turns out:
- HttpRequestWrapper is only created within the HttpContextWrapper.Request property;
- HttpContextWrapper is created within the MvcHandler.ProcessRequest() method or passed into MvcHandler’s constructor as part of the RequestContext;
- MvcHandler is created by the MvcRouteHandler.GetHttpHandler() method;
- MvcRouteHandler is created each time we register a route using the MapRoute extension (in Globalascx.cs)… registering a route manually allows you to provide your own IRouteHandler.
So, to inject our own HttpRequestBase class, we also need to provide our own versions of the HttpContextWrapper, MvcHandler and MvcRouteHandler classes. By deriving from these classes and only overriding the constructors, methods and properties that we have to, this is fairly simple. But how do we persuade a developer to register our route handler rather than the default? I created a new set of extensions to mirror the MapRoute extensions. By substituting MapRestfulRoute for MapRoute, the developer gets the same simple route registration but with POST overloading built in.
Note: The new HttpRestfulRequestWrapper borrows ideas from the SDK about how to override POST. I did add the ability to set the name of the parameters the code looks for when trying to detect overloading.
As we’re already providing our own wrapper for HttpRequest, to allow overloading of POST… why not also provide overrides for the ContentType and AcceptTypes properties. This would centralise the work outlined in the Multiple Representations section above (I said I’d come back to this!). It was fairly simple to implement. There is one slight downside, HttpContext’s ContentType and AcceptTypes properties wrap around the headers collection… which is read only. So whilst our wrapper can alter the values returned by the properties, any code that goes direct to the Headers collection will not see the overrides… then again, this is no worse than HttpContext. HttpContext allows ContentType to be set, but doesn’t actually modify the underlying headers collection either.
Another goal of our web service is to provide a uniform interface. As described in my two earlier blog entries on Response Codes (and Response Codes Part b) the Uniform Interface does not just cover the verbs that a web service supports, but also the response codes it returns. I created some extension methods for the Controller class that extend the View() methods to also take an HTTP Status Code.
I found however that the SDK always resets the HTTP Status Code to 200 OK regardless. Another minor edit to the SDK code rectified this.
In my Supporting Incoming XHTML post, we allowed the client to send XHTML to the web service. Whether this is a good idea or not (rather than using FORM variables) I’m not sure… but only using FORM variables means supporting complex objects can be difficult. Outgoing XHTML is generated by the ASPX views, but to handle incoming XHTML, I created a third FormatHandler. The XhtmlFormatHandler only derives from IRequestFormatHandler (not IResponseFormatHandler) and is only registered with the FormatManager to handle incoming data. I had to make a minor change to the SDK so that the FormatManager would look for Friendly Format Names in both its list of Response Handlers and Request Handlers (not just its Response Handlers). This required a small change to the FormatManager and the IRequestFormatHandler interface.
The XhtmlFormatHandler will handle Media types of "text/html" and "application/xhtml+xml". To the HttpRestfulRequestWrapper I added the ability to inject a default Content Type for requests that have none. For this web service I set the default to "text/html" meaning any requests that arrive without a content type will get routed via the XhtmlFormatHandler. With this in place, requests with XHTML in the body can be handled.
One other aspect I started looking at was Model Binding. The SDK provides its own implementation of IModelBinder. For values that can be read from the routing information this falls back to the default binder. Otherwise it uses the FormatManager to try to deserialize the object. I don’t know enough about this area yet, But I believe this means FormatHandlers should register errors with the ModelState using AddModelError. I think a call to SetModelValue is also required (not 100% on this as documentation is scant). Perhaps the SDK should be calling SetModelValue?
So at the end of this little investigation what have we done? We have
- Adopted the SDK’s WebApiEnabledAttribute, ModelBinder and FormatHandler approach;
- We moved the overloading of POST, injection of ContentType and injection of AcceptTypes as close to source as possible.
This means our Controllers can now be written in a completely standard pattern. No deriving from a custom class or using a custom method to create an ActionResult. Minor changes were needed to the SDK to support our goals, but not many. Some areas of the SDK have been ignored altogether:
- The ResourceControllerFactory is superfluous as overriding of POST is performed by the HttpRestfulRequestWrapper;
- The out of the box FormatHandlers are too simplistic
- The injection of formats using the URI is not necessary with the overrides in HttpRestfulRequestWrapper
- The overriding of POST and formats only being available to browser clients has been circumvented.
What have we lost? For now I have dropped support for returning an Allowed header containing the HTTP Verbs that a resource supports due to a lack of time. Hopefully a future post could reintroduce them.
All in all I think this went well. I like the WebApiEnabledAttribute and FormatHandler approach introduced by the SDK. Also, I have picked up some other improvements by looking at the source of the SDK. Thank you to the authors!
For my next version I may replicate the some of the approaches used in the SDK (those I found useful) into my own assembly and try to address Model Binding properly.
Here is the code: RESTfulMVCWebService17.zip (135.64 kb)