Sunday, August 26, 2012

ASP.Net Web API Error Handling, HTTP Status Codes, and You

This is really 502 - Bad Gateway
I'm pretty sure it's not the cat's fault.

EDIT: I actually recommend against a lot of what I'm saying here now. Web API endpoints shoudl always be returning HttpResponseMessage, and those messages could always be created by Request.CreateResponse();  I'll write more on this later.


So it seems to me that not a lot of people have figured out what they should be doing when they want to throw an error from their Web API, but give something back to the client that contains some sort of information about what happened. If you're here you might be here because you've realized that simple throwing any old exception from your Web API results in a "500: Internal Server Error" with exactly nothing in the body of the response that might explain to the client what went wrong.

There are a few things at play here. The quick and dirty version is you're probably throwing the wrong type of exception, and returning the wrong type of status code. Let me explain:


You need to be throwing HttpResponseException


When you throw just any old error, ASP.Net interprets that as an error in the operation of your web application. In other words, it thinks (and rightfully so) that you've experienced an "internal server error". As such, it just sends out a 500 error. You might think, "Well great, but why doesn't it send out the message in my exception? Why can't they do that for me? Shouldn't that be done for me?" Well, actually no, you don't want to send an explanation with a 500 error, but I'll get to that.

If you throw an HttpResponseException, ASP.Net knows that you're attempting to throw an error that you'd like to communicate back to the client with specific information. This, however, does not mean that you should use a 500 error, or that there were all of a sudden be a body in with that 500 error. Because there shouldn't be.


You should be returning 4XX errors, not 5XX errors


The reason the developers of this technology opted not to send the contents of your exception out with a 500 error is because 5XX errors are meant to be sent out as a notification that an error occurred on the server, and there's nothing the client can do about it. Think of it like that annoying "engine maintenance" light on the dashboard of your car, if you're getting that, just pounding on the gas harder isn't likely to do anything to fix the issue. It's an internal issue.

400 errors are your friends. 4XX errors are there to state that an error occurred and there's something the client can do about it. There is a whole list of them, but the two you'll probably need the most are 404 and 400. 404 everyone knows... "not found". This can be returned when content isn't found. For example if a user queries from some widget by id, and the id is invalid... your app didn't find it, so 404.  400 is for a bad request. That means the server got the request okay, but there was something wrong with it and it shouldn't b e resent unless something about it changes. (I'm sure somewhere the authors of RFC 2616 want to slit their wrists after reading my grotesque butchery of their words).

For a more complete list read the definitions yourself. Just be sure to actually read the definitions, some of them have rather generic sounding names but very specific meanings. For example, "406 - Not Acceptable" which actually means that the type requested in the Accept header isn't something the server can return, so if the client requested "text/fibbertyjibbets" rather than "application/json" they're S.O.L. and they're going to get a 406. Anyhow, the point is, read the definitions before you use them. Odds are, most of the time, 400 and 404 will do.

So 4XX errors will actually return a reason along with them, as well as a body that offers some explanation. This is way better than a 500: Internal Server Error with no explanation. And it's also the more correct response.


But how to do this in ASP.Net Web API?


Since ASP.Net Web API can return a variety of content types: XML and JSON for example, I think the most appropriate thing to do is return a very simple string with the basic reason in it. It can be interpreted easily by any client, and it's simple to implement.

What I did was add the following methods to my custom ApiController base class. Then when I needed to use them, I basically call: throw Conflict("Invalid somethingerother"); or throw NotFound("Record not found"); This does everything I need it to do to return something valid to the client.

/// <summary>
/// creates an <see cref="HttpResponseException"/> with a response code of 400
/// and places the reason in the reason header and the body.
/// </summary>
/// <param name="reason">Explanation text for the client.</param>
/// <returns>A new HttpResponseException</returns>
protected HttpResponseException BadRequest(string reason)
{
    return CreateHttpResponseException(reason, HttpStatusCode.BadRequest);
}

/// <summary>
/// creates an <see cref="HttpResponseException"/> with a response code of 404
/// and places the reason in the reason header and the body.
/// </summary>
/// <param name="reason">Explanation text for the client.</param>
/// <returns>A new HttpResponseException</returns>
protected HttpResponseException NotFound(string reason)
{
    return CreateHttpResponseException(reason, HttpStatusCode.NotFound);
}

/// <summary>
/// Creates an <see cref="HttpResponseException"/> to be thrown by the api.
/// </summary>
/// <param name="reason">Explanation text, also added to the body.</param>
/// <param name="code">The HTTP status code.</param>
/// <returns>A new <see cref="HttpResponseException"/></returns>
private static HttpResponseException CreateHttpResponseException(string reason, HttpStatusCode code)
{
    var response = new HttpResponseMessage
        {
            StatusCode = code,
            ReasonPhrase = reason,
            Content = new StringContent(reason)
        };
    throw new HttpResponseException(response);
}

Here's an example of one of the above methods in use:

public Widget GetById(int id) 
{
    using(var context = new MyDataContext()) 
    {
        var widget = context.Widgets.FirstOrDefault(x.Id == id);
        //if we don't have the widget, throw our HTTP error.
        if(widget == null) throw NotFound("Could not find widget: " + id);
        return widget;
    |
}



You can of course implement this same basic idea however you like, but the principle would remain the same. It's good to have a better understanding of HTTP status codes and what they mean, and it's good to use them appropriately.


What you DO NOT want to do


You do not want to return the guts of a whole Exception object in the body of your error response. You don't need to, or want to be sending the whole stack trace and other sensitive information to any old client that makes a bad request. It's just not a good idea. Stack traces and other such things should be recorded for you by a good logging solution, and they should never, ever be returned to clients. Particularly unknown clients.

You do not want to return the wrong HTTP status codes. Will it break anything? Probably not. Will it make you look like a chump that didn't read the RFC before you used the code? Yes. Don't do it. Don't be a chump.

EDIT: I've changed this slightly. It was really late and I was tired when I was writing this. I meant 400 error, not 409. 409 is when you have a state conflict. Like for example a double submit of some data, or data in an unexpected state that is causing an issue. It's could be valid for nearly any error I suppose, but it seems a little more specific tied to the state of some data than 400.

12 comments:

  1. Good post, Benjermainejacksonfive, (which is short for Ben). You are not wrong. IF ONLY people would listen...

    There are MAJOR AMERICAN AUTO MANUFACTURERS that do not know about status codes and could totally benefit from reading this. (I'm looking at you, Ford).

    ReplyDelete
  2. Haha... I'd be a little disturbed if my car returned HTTP status codes to me. I guess a 404 would be if it was stolen?

    ReplyDelete
  3. Thanks for this info!

    ReplyDelete
    Replies
    1. Ugh, I've actually changed my approach to this a lot. I now favor just using Request.CreateResponse() or Request.CreateErrorResponse() I should probably update this old post.

      Delete
    2. ... well that is in situations where the return type of the action is HttpResponseMessage, I guess. So, it's situational. The stuff I do above still applies in situations where I'm returning concrete types from the method.

      Delete
    3. Benjamin, most articles I find like yours that suggest this method are from 2012. Now it's a year later, and I'm working in MVC4 Web API in VS 2010. I can set the status code, but the reason phrase does not change, and the content isn't there. In other words, if I set the code to Forbidden and phrase and content to "Invalid login," the client sees a code of 403, the phrase is "Forbidden," and there's no content. I don't know if the problem is something has changed in how this all works in the last year, or something needs to exist in my project that's missing. Any ideas?

      Delete
    4. Well, I'd suggest getting some of your code together and posting a question on StackOverflow. Be sure to include some code and what you want as the desired outcome. I plan on blogging about how my approach to this has changed since this post, but the crux of it is I only return HttpResponseMessage from my Actions now, and I'm creating them with Request.CreateMessage() or Request.CreateErrorMessage(), which generally allows me to fully control the output.

      Delete
  4. What do you think about managing some standard response code in a dedicated action filter ? That way you can rely on standard exception and return the appropriate response code.

    You can then register this filter globally to let web api handle those exception by default.

    Eg :
    public class ProperHttpCodeActionFilterAttribute : ActionFilterAttribute
    {
    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
    if (actionExecutedContext.Exception != null)
    {
    if (actionExecutedContext.Exception is NotImplementedException)
    {
    actionExecutedContext.Response = actionExecutedContext.Request.CreateErrorResponse(HttpStatusCode.NotImplemented, actionExecutedContext.Exception);
    }
    }

    base.OnActionExecuted(actionExecutedContext);
    }
    }

    ReplyDelete
    Replies
    1. I tend to develop iteratively these days. That means I usually stay away from generic solutions like this. It would work, but any more I find myself just handling each case individually in the Action.

      Also, I really need to redo this blog entry, I disagree with a lot of my approach above.

      Delete
    2. Thanks for the heads-up.
      Well, I need to keep strongly typed response, not HttpResponseMessage. I'm also not really confortable with throwing exception to indicate "404 - NotFound" or 201 for successfull POST calls.

      About the iterative development part, I can always move that filter from the global scope and apply it individually.

      Delete
  5. How do you treat an SMTP email send exception? My SMTP client is being called from a WEBAPI post method?

    ReplyDelete
    Replies
    1. As in what status code should you use? It all depends... If it something the user/client can do something about, it should be an error in the 400-range (Probably just a 400). If it's something that's happening on the server that's out of the client/user's control, it should be in the 500 range (probably just 500). If you google HTTP Status Codes, it should give you more guidance.

      Delete

This form allows some basic HTML. It will only create links if you wrap the URL in an anchor tag (Sorry, it's the Blogger default)