Creating a RESTful Web Service Using ASP.Net MVC Part 5a – HTTP Response Codes

October 16, 2008 12:26 by admin

Roy Fielding’s original dissertation describes how a uniform interface is one of the key features of a REST architecture. When illustrating a uniform interface, many people describe the verbs that the interface exposes (e.g. in the case of HTTP these are GET, PUT, POST etc.). However, we should also consider the responses as part of that interface. One of the reasons HTTP fits the RESTful style so well, is that its limited and uniform interface includes the standard HTTP response codes.

In this post I’ll look how the web service we’ve been developing can return the full range of response codes available.

ASP.Net MVC is primarily centred around serving web pages for consumption by browsers. On the web we tend to be lazy and only use a small sub set of the available response codes. Rather than use the full range of response codes, we often rely on the content returned with the response to contain detailed error information. As the usual consumer of this data is a human, who can read and understand the content, this works fine. However, we’re writing a web service that is likely to be consumed by a computer rather than a human. Computers are not so good at interpreting content... they are good at understanding codes and reacting accordingly.

In my previous posts we developed a basic RESTful web service that so far allows a client to request between 1 and 100 GUIDs. A request for more or less GUIDs resulted in response code 200 – OK and either 1 or 100 GUIDs being returned. For the next iteration I want to return an error status code if the client asks for more or less GUIDs. This is better than returning a default number of GUIDs as it means badly written client code should be corrected early in development… and its a nice simple example for me to experiment with.

So what if I change the action’s code to this:

public ActionResult Get(int? count)

{

    ActionResult actionResult;

 

    // Provide a default count and restrict to the range 1 - 100

    int actualCount = count ?? 10;

 

    if ((actualCount < 1) || (actualCount > 100))

    {

        throw new ArgumentException("count", "Count must be between 1 and 100");

    }

    else

    {

        // Generate the requested GUIDs

        GuidGeneratorResult guidGeneratorResult = GuidGenerator.GenerateGuids(actualCount);

 

        // Guids should be different every time so don't cache

        HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);

        System.Threading.Thread.Sleep(5000);

 

        actionResult = SelectActionResult(guidGeneratorResult);

    }

 

    return actionResult;

}

Requesting 101 GUIDs from the above action would result in an ArgumentException being thrown. The default response of the MVC to an unhandled exception is to return an Internal Server Error:

500 - Internal Server Error

Cache-Control: private

Content-Type: text/html; charset=utf-8

Server: Microsoft-IIS/7.0

X-AspNet-Version: 2.0.50727

X-Powered-By: ASP.NET

Date: Fri, 10 Oct 2008 23:01:20 GMT

Content-Length: 6457

 

<html></p>

   <head></p>

      <title>count<br>Parameter name: Count must be between 1 and 100</title>

      ...

Depending on configuration, the response’s content will contain details of the error in XHTML format. At first glance this may appear fine. However, the status code indicates there was an internal server error, which is not correct. The actual problem was a bad request. Perhaps the best fit response would be 400 – Bad Request

A second approach might be to return a different ActionResult from the code above. What if rather than throw an error we had the following:

if ((actualCount < 1) || (actualCount > 100))

{

     Response.StatusCode = (int)HttpStatusCode.BadRequest;

     actionResult = this.Content("The requested count must be between 1 and 100");

}

This gives us a better response code and more easily consumed content:

400 - Bad Request

Cache-Control: private

Content-Type: text/html; charset=utf-8

Server: Microsoft-IIS/7.0

X-AspNet-Version: 2.0.50727

X-Powered-By: ASP.NET

Date: Sat, 11 Oct 2008 19:04:45 GMT

Content-Length: 45

 

The requested count must be between 1 and 100

I could have left this post there, but I wasn’t happy with this solution… what about different representations? A client expecting Xml would not be able to consume this content. Also, the code above could do with refactoring if it is going to be used throughout the web service.

I decided to provide another overload of my SelectActionResult method which accepted a HttpStatusCode. I also introduced an ErrorDetail class. Together with ErrorDetail.aspx and ErrorDetailInternalToExternal.xslt files this gave me a simple way to return the most relevant HTTP Response Code and have the content returned in the correct representation. The code in the Action is now:

if ((actualCount < 1) || (actualCount > 100))

{

     actionResult = SelectActionResult("ErrorDetail",

         new ErrorDetail() { Description = "The requested count must ... "},

         HttpStatusCode.BadRequest);

}

In the SelectActionResult method, the Response’s StatusCode property is set before the existing processing selects which type of ActionResult to return:

protected internal virtual ActionResult SelectActionResult(string name, object model,

                                              HttpStatusCode? httpStatusCode)

{

    ActionResult result = null;

 

    if (httpStatusCode != null)

    {

        Response.StatusCode = (int)httpStatusCode;

    }

 

    if (model != null)

    {

        base.ViewData.Model = model;

    }

 

    // Get the (lowercase) render mode from the querystring

    var method = HttpContext.Request.QueryString["format"] ?? string.Empty;

 

    // Decide which method to use

    switch (method.ToLowerInvariant())

    {

        case("xml"):

            result = Xslt(name, model);

            break;

 

        case("json"):

            result = Json(model);

            break;

 

        default:

            result = View(name, model);

            break;

    }

 

    return result;

}

Now, if the client requests an Xml representation of 102 GUIDs it would receive this response:

400 - Bad Request

Cache-Control: private

Content-Type: application/xml; charset=utf-8

Server: Microsoft-IIS/7.0

X-AspNet-Version: 2.0.50727

X-Powered-By: ASP.NET

Date: Sat, 11 Oct 2008 19:04:45 GMT

Content-Length: 210

 

<?xml version="1.0" encoding="utf-16"?>

<Error xmlns="http://schemas.shouldersofgiants.co.uk/RESTfulMVCWebService/2008/09">

    <Description>The requested count must be between 1 and 100</Description>

</Error>

You may be wondering why I didn’t create an ErrorResult class derived from ActionResult. With a RESTful web service you will often return response codes that don’t represent errors. For instance, in response to a POST request you will probably need to return 201 – Created and perhaps a representation of the newly created object. Therefore, I needed a more generic solution that wasn’t tied to error conditions.

So far we have a framework for returning Response Codes when we want to… but what about when the unexpected happens? If an exception is raised we’ll be back to the default 500 – Internal Server Error. If we want to produce an error suitable for the requested representation we need to trap those exceptions.

Within the MVC framework there are a few ways to trap errors, here are some starting with the most fine grained and moving on to the least:

  • Try / catch blocks could be put in each Action method. This is OK if you know what exceptions you are looking for, however as a “Catch All” approach this would create a lot of similar code throughout the web service;
  • The controller could be decorated with the HandleErrorAttribute, but that only creates a ViewResult. Therefore the XHTML would be the only possible representation of the error details;
  • Create a new class derived from FilterAttribute that mimics HandleErrorAttribute but supports other representations. This would still have to be applied to every Controller in the web service.
  • Override the OnException method of each controller. This would allow a response tailored to each Controller, but would require each controller to be modified.
  • Override the OnException method of a BaseController from which all your controllers are derived.

Since we already have a BaseController, I chose this last option and added the following code to it:

protected override void OnException(ExceptionContext filterContext)

{

    if (!filterContext.ExceptionHandled)

    {

        filterContext.ExceptionHandled = true;

        filterContext.Result = SelectActionResult("ErrorDetail",

           new ErrorDetail() { Description = "An error occurred ..." },

           HttpStatusCode.InternalServerError);

    }

}

This code responds to all exceptions that are not caught whilst the Action is executing. It means a 500 Internal Server Error is returned with the correct representation of the content. If a particular controller needs to return more specific error information, it can still override OnException.

I also provided an override of HandleUnknownAction for good measure. This is called if a controller has been found, but it doesn’t expose a matching action. For instance this URI would trigger HandleUnknownAction in our web service:

  • http://localhost/ShouldersOfGiants/RESTfulMVCWebService/Version02/Guid/somethingmadeup

Inside HandleUnknownAction an ErrorDetail is passed to SelectActionResult along with a 404 – Not Found Response Code. This will select the correct ActionResult for the current representation which is then executed.

Unfortunately, that still isn’t the end of the story. The OnException method above can only catch exceptions thrown during the processing of an action. There are other places a problem can occur during the overall processing of a request (e.g. a bad URI that cannot be handled by the routing table, or a missing resource). I will cover trapping those errors in posting 5b! I’ll also include the source code so far with that post.

kick it on DotNetKicks.com


Comments (2) -

October 20. 2008 09:17

Ben

Hi,

It's a shame MVC does not supply an RestActionResult for this, i.e., one that represents a rest response.

So for example, if SelectActionResult was building a RestActionResult it could simply set properties rather than be tied to the Response object.

I am tending towards making controllers totally independent of ControllerContext -- and testing the state of ActionResults helps.

Ben

October 20. 2008 13:06

Piers

Yes, perhaps if ContentResult and JsonResult derived from a common base which then exposed a ResponseCode property, everything required would be covered... but then again there will always be someone who wants one more header to be exposed. For instance, I'm going to add the Accepts header in Part 6 of this series!

Piers

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading