Creating a RESTful Web Service Using ASP.Net MVC Part 17 – Reviewing the Rest for ASP.NET MVC SDK

September 5, 2009 00:37 by admin

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.

Multiple Representations

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:

      http:\\XYZ.com\SomeResource?format=xml

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!

Connectedness

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.

Nicely, the SDK does allow custom FormatHandlers to be registered. I created two new FormatHandlers that reused the serializers we have already created. These serialisers are described in earlier posts. One looks for a matching XSLT in the the View directories and applies it (using extensions to allow URIs to be added dynamically). The second looks for JavaScriptConverters registered in the view directories which can perform custom JSON serialisation. The new FormatHandlers are registered in Global.ascx.cs using a new class derived from FormatManager:

   FormatManager.Current = new RestfulFormatManager();

There was one change I had to make to the SDK to support these the new FormatHandlers... they need to know the view name so they can find the correct XSLT or JavaScriptConverter. Therefore the Serialize() method of IResponseFormatHandler had to have one additional parameter. So injecting connectedness meant using one of the SDKs extension points, and minor code changes to the SDK.

Aside: Our JSON serializer uses a JavaScriptSerializer rather than a DataContractJsonSerializer, as it is more extensible. The downside is that JavaScriptSerializer has a generic interface. If you don’t know the Type of object being deserialized at compile time, it cannot deserialize to the correct type. One way around this is to use reflection to access its internal methods that work with Type parameters… quite a brittle approach! Looking at how the JavaScriptSerializer  works, it is possible for the JSON stream to contain type information in the form of a variable named “__type”. The value of this variable is passed to a JavaScriptTypeResolver instance which can return a Type dynamically. So, an alternative workaround I came up with was to use a JavaScriptTypeResolver which was loaded with the type information of the class we want to deserialise and inject a “__type “ variable into the JSON. Again… not nice but it works. If only Microsoft would make the internal Deserialize() method public!

Overloading POST

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();

   ControllerBuilder.Current.SetControllerFactory(factory);

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:

    ControllerContext.RequestContext.HttpContext.Request.HttpMethod

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?

Re-Wrapping HttpContext

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.

Uniform Interface

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.

Incoming XHTML

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.

Model Binding

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?

Summary

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)

kick it on DotNetKicks.com


Comments (6) -

November 2. 2009 23:00

Bob Uva

Piers,

Your series on REST with MVC has been great. I just discovered it as I'm exploring which technologies to use. I'm building a server environment that will support a web app as well as a web service layer. The web services will be primarily used by customers of my client to automate some registration and deployment steps, as well as handle data uploads from a variety of devices (laptops, phones). The RESTful MVC approach seems like it would work but I'm concerned about whether I'm tying the web site and web services too closely together. In a robust server environment with sporadic traffic spikes, do you think it would be possible to manage load between the web site and the web services if they're running in the same application?

Thanks, Bob

Bob Uva

November 3. 2009 13:46

Piers

Are you thinking the same MVC application will provide both the public facing web site and the RESTful web service? I'd probably avoid that approach because:

1) You can manage the infrastructure (e.g. load balancing) better if they are seperate

2) Whilst my web service can serve XHTML and CSS prettifies it somewhat, the XHTML is still really meant to be a web service (with the advantage that it can be manipulated through a browser.... which provides a developer a nice way to discover its functionality). Adding extra tags and more complex CSS to provide a nice public facing user interface does not sit well with a minimal web service meant for consumption by another server.

On managing the load... the RESTful web service should be inherently stateless, so should scale well if you need to introduce additional servers to cope with your traffic.

Piers

November 14. 2009 19:52

Sebastien Lambla

Nice work.

You should give openrasta a try, see how it compares!

Seb

Sebastien Lambla

October 15. 2010 18:22

Zähne Bleichen

I am really enjoying and at the same time studying all parts of this series. Looks like I still need to learn quite a lot.

Zähne Bleichen

February 18. 2011 22:35

olta bayrak

teşekkürler

olta bayrak

February 18. 2011 22:36

flama bayrak

iyi

flama bayrak

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading