Creating a RESTful Web Service Using ASP.Net MVC Part 19 – Baking in Method and Content Type Overloading

December 20, 2009 14:08 by admin

A little while back I reviewed the Rest for ASP.NET MVC SDK that was released by Microsoft WCF team. I liked some of the ideas it contained so promised to bring some of those into our code. Well, I’ve finally started to do that.

Overloading POST

The first feature I wanted to bring in was not actually part of the SDK, but was an idea I had when looking at the limitations of both our and the SDK’s approach to overloading POST. Our web service has up till now required an attribute called AcceptVerbsAndOverloadsAttribute to be used on any Actions that needed to handle overloaded POST (this was derived from the MVC attribute AcceptVerbsAttribute). The SDK intercepts the FindAction method and applies overloading at that point. In my previous post, I decided that perhaps the best approach was to provide my own wrapper for HttpContext and therefore HTTP Verb at (almost) source.

One class, HttpRestfulRequestWrapper, is used as a direct replacement for HttpRequestWrapper (the default class used by ASP.Net MVC) to wrap the HttpContext’s Request property and add support for overloading of POST. The wrapper still supports passing the overload information in the querystring, but from the SDK I took the idea of also looking for an X-Http-Method-Override header and a FORM variable.

Of course some of the web service code has to change in order to tell ASP.Net MVC to use the RESTful wrapper instead of the default wrapper. The code to register the default HttpRequestWrapper is usually hidden away in the MapRoute extension methods used to register new routes. There does not appear to be a simple method available for injecting our HttpRestfulRequestWrapper directly. The process required is:

  • The web service instantiates an MvcRestfulRouteHandler instead of an MvcRouteHandler;
  • MvcRestfulRouteHandler overrides the GetHttpHandler method of MvcRouteHandler to return an MvcRestfulHandler instead of an MvcHandler;
  • MvcRestfulHandler intercepts the calls to its constructor and the ProcessRequest method so it can inject an HttpRestfulContextWrapper instead of the default HttpContextWrapper;
  • HttpRestfulContextWrapper overrides the Request property so it can return an HttpRestfulRequestWrapper instead of the default HttpRequestWrapper;
  • Finally, it is the HttpRestfulRequestWrapper that provides the POST overloading functionality by overriding the HttpMethod property.

The most convenient solution for the web service developer was to provide our own extension method which is a drop in replacement for MapRoute:

 
public static Route MapRestfulRoute(this RouteCollection routes, string name,
                                    string url, object defaults, object constraints,
                                    string[] namespaces)
{
    if (routes == null)
    {
        throw new ArgumentNullException("routes");
    }
    if (url == null)
    {
        throw new ArgumentNullException("url");
    }

    Route route = new Route(url, new MvcRestfulRouteHandler());
    route.Defaults = new RouteValueDictionary(defaults);
    route.Constraints = new RouteValueDictionary(constraints);
    if ((namespaces != null) && (namespaces.Length > 0))
    {
        route.DataTokens = new RouteValueDictionary();
        route.DataTokens["Namespaces"] = namespaces;
    }

    routes.Add(name, route);

    return route;
}
 

The body of MapRestfulRoute is identical to MapRoute, except that it instantiates an MvcRestfulRouteHandler instead of an MvcRouteHandler. So all the web service developer has to do to make use of the RESTful functionality is to replace MapRoute with MapRestfulRoute:

 
routes.MapRestfulRoute(
    "ProductsItem",
    "Products/{idString}",
    new { controller = "Products", action = "Item", categoryString = string.Empty },
    new { idString = @"^[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]…
                       {4}\-[0-9a-fA-F]{12}$" }
    );

Using Accept Types

As I was already providing a RESTful wrapper to handle overloading of POST, it seemed sensible to use the same wrapper to allow overloading of the Request and Response formats. Again I extended the basic format handling from our web service with alternative methods of supplying format data (an idea taken from the SDK). The Wrapper supports the client defining which representations it can accept by:

  • Passing a format parameter in the querystring (my preferred approach, see my previous Multiple Representations post);
  • Adding an Accept header to the request dictating one or more Content Types it can accept;
  • Providing a content type for the request data.

HttpRestfulRequestWrapper overloads the AcceptTypes property of HttpRequestWrapper and tries to establish the representations acceptable to the client. This approach means that the rest of the application is none the wiser that we have manipulated the details of the request. I had to change our RestfulController to look iterate over the AcceptTypes array instead of looking in the querystring itself, but there is no further change for the web service developer.

Flexible Representation Support

The previous version of our web service was hardcoded to deal with JSON, XML and XHTML representations. One feature I liked about the SDK was the ability to add or remove the representations that the web service can accept or return. I started to adopt the concept of a FormatManager and one or more IRequestFormatHandler and IResponseFormatHandler derived classes. There is a default list of format handlers created by the FormatManager, but the web service developer can easily add or remove handlers from this list.

So far, I have not fully migrated to this approach. Currently, the handlers are only used to:

  • Translate between friendly names for formats (e.g. passing format=xml on the querystring is translated to a content type of application/xml);
  • Deserialise incoming representations;
  • Create ActionResult objects that can serialise responses into the correct format.

Coding by Subtraction

So what needs to be changed in a web service if you want to use this latest version of the RESTful features?

  • Change all [AcceptVerbsAndOverloads] back to the standard [AcceptVerbs]
  • Replace MapRoute with MapRestfulRoute for routes that should allow overloading of POST and formats
  • In Global.asax.cs the Application_Error function needs to use a HttpRestfulContextWrapper instead of a standard HttpContextWrapper to ensure errors are still returned in a RESTful manner
  • In application start, set some default values indicating how the RESTful features should behave e.g.
     protected void Application_Start()
     {
        // Change from the default parameter used to override POST
        HttpRestfulRequestWrapper.HttpMethodOverrideQueryStringId = "_method";
    
        // Change from the default Content Type to use when the client doesn't supply one
        HttpRestfulRequestWrapper.DefaultContentType = "application/xhtml+xml";
    
        RegisterRoutes(RouteTable.Routes);
     }

So hopefully, adopting the new DLL will mainly be a case of (borrowing a phrase from Scott Hanselman) “Coding by subtraction!”

Next Steps

My next step will be to remove the need for the web service developer to derive from RestfulController. Instead, I’d like to take another idea from the SDK and use an attribute on an ordinary controller to indicate multiple representations are to be supported… or some other, less obtrusive approach.

In the mean time let me know what you think of this version: RESTfulMVCWebService19.zip (106.25 kb)


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Comments

Add comment


(Will show your Gravatar icon)  

  Country flag

biuquote
  • Comment
  • Preview
Loading