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)


Comments (12) -

January 9. 2010 19:20

Sergey

It is a great series of posts. Thanks. But I think it would be perfect if there were some unit tests for your code.

Sergey

January 30. 2010 19:56

Philadelphia SEO

Great information here, thanks for post.

Philadelphia SEO

May 7. 2010 09:36

Eric

Hi Piers,

Great blog, you raise some good subjects.

I've looked at both of your approaches to method overloading and I think there's a more straightforward approach that you could try.

Unfortunately the concrete ActionMethodSelectorAttributes are sealed, however you can derive each again from this class - they are simple to implement. At that point method overloading is easy.

You can save every failed filter in a string list of verbs saved on the controller, and then overload HandleUnknownAction. When the event fires you can inspect the controller's list of failed verbs and if it is not empty you have a 405, otherwise a 404. The list can at that point be attached as the Allowed response header as well, since the event itself indicates that the mothod is not found.

You can then raise the appropriate exception and catch it in the Application_Error, redirecting it out to an error controller (which should itself probably accept any verbs).

This approach also makes it easy to perform overloading beacuse you've implemented overrides for each of the method filters. This is where Mvc does overloading as well.

Best,

Eric

Eric

May 7. 2010 18:03

Piers

@Eric - that is exactly how I was providing POST overloading previously (see the Part 9 post). I decided to move away from that approach as it would mean people using my framework would have to use a custom attribut I provided rather than the standard one provided by the MVC framework. This deeper integration allows the REST features to be "turned on" with fewer changes to the original code. Thanks for looking into this!

Piers

May 7. 2010 18:55

Eric

Thanks for the reply Piers. I looked at both before posting. I think you'll find that they are different approaches despite use of some of the same overloads. For example I made no changes to routing.

I opted to not go with your second approach because to me the changes to routing are more intrusive than changing the name of an attribute - in addition to the greater internal complexity.

Thanks again for posting your ideas.

Eric

May 7. 2010 20:57

Piers

@Eric - I'm glad the posts were of interest. My main hope is that the MVC team bake in support for RESTful web services into the main framework... they are heading in the right direction and the latest version of MVC Futures has some more good ideas.

Piers

May 7. 2010 21:34

Eric

I agree - I'm working with both futures and MVCContrib. Both have some good ideas and it would be nice to see some review of how they might help with building more RESTful and HTTP compliant sites/APIs Smile.

Eric

May 9. 2010 00:53

Eric

Unrelated to previous posts...
Why are you (and MVC Futures) using the Content-Type request header to determine the mime type of the request? This is the job of the Accept header. The Content-Type header is for POST & PUT methods only, specifically to designate the type of the post/put content. Thanks.

Eric

May 9. 2010 01:34

piers

@Eric - The Content type is only used as a last resort... if the Accept header is not present and the format has not been dictated by another means (e.g. using a querystring variable) then we fail back to trying to match the content type of the request.... on the assumption that if the request is in a particular format, the client will be able to process the result in the same format. The actual order of priority is.

1) Querystring
2) Accept header
3) Content-Type

You'll see this in the AcceptTypes property of the RestfulHttpRequestWrapper class.

piers

May 9. 2010 03:18

Eric

Thanks - I should have looked more closely at yours. That's pretty clear. I was working through MVC Futures and I was getting the wrong result because I was calling GetRequestFormat() instead of GetReponseFormat()... duh.

Eric

June 7. 2010 20:07

Psychic Predictions

Are there any unit tests planned? Just my 2 cents...

Psychic Predictions

November 21. 2010 02:33

Bakugan

Found this by chance. The code partially helps me.

Bakugan

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading