guided-tour

HTTP Status Codes in ASP.NET Web API - A Guided Tour

Good morning everyone, and welcome to the new HTTP Status Codes in ASP.NET Web API tour here at Exception Not Found! My name is Reggie, and I'll be your tour guide today. Those of you who have taken our Exception Handling tour, welcome back! For those of you who are new to our facility, thank you for visiting us.

A guide leads a crowded tour of a museum. WikCon UK 2012 Science Museum tour 6 from Wikimedia, used under license

First off, I'd like to introduce my brilliant assistant Postman, who will be assisting me on this tour. Also assisting us will be Nathan, and he'll be doing whatever I need him to, and he better be grateful, this is his last chance, that little...

As I said, Nathan will be assisting us, since we have such a large group. This tour moves fast, so please keep up. Today, we're going to take a look around at the variety of status codes we can return from our Web API applications. Step right this way, stay together, and let's learn about what HTTP status codes are and how we use them in our ASP.NET Web API projects.

The Types of Status Codes

HTTP implements a wide variety of status codes, which are grouped into five categories. The five categories are distinguished by the code's first number, like so:

  • 1XX Codes: Informational codes. Rarely used in modern web apps.
  • 2XX Codes: Success codes. Tells the client that the request succeeded.
  • 3XX Codes: Redirect codes. Tells the client that they may need to redirect to another location.
  • 4XX Codes: Client Error codes. Tells the client that something was wrong with what it sent to the server.
  • 5XX Codes: Server Error codes. Tells the client that something went wrong on the server's side, so that the client may attempt the request again, possibly at a later time.

ASP.NET Web API can return any of those codes, though the more common ones have simpler return methods. Up next, we will show how we can determine what the proper code is to return from our action methods. This way, please.

Selecting the Proper Code

You should know that there is no "one true way" of selecting the proper status code; it all depends on your business rules. That said, unless your rules state otherwise, it is usually a good idea to return a more specific code rather than a more generic one.

As an example, consider a situation where your server needs to tell the client that a request that was submitted to it didn't have the proper authorization. One status code you could return is 400 Bad Request, as this implies that the request submitted was improper and not processed by the server. However, a more specific error code would be 401 Unauthorized, since that explicitly states what was wrong with the request. You should use more specific codes unless you have a business or security reason not to.

So, now we can start talking about what ASP.NET Web API does when it implements actions. If you'll just follow me to the next hallway, we can start to see code that shows what Web API will do in certain common situations.

Return Void - 204 No Content

Nathan, not that display case. Go to the other one. No, the other other one. YES THAT ONE.

Ahem! Take a look at the display case next to where Nathan is standing. Inside, we have one of our sample controllers:

public class MovieController : ApiController  
{
    [HttpPost]
    [Route("movies/add")]
    public void Add(string title, DateTime releaseDate, int runningTime)
    {
        Movie movie = new Movie()
        {
            Title = title,
            ReleaseDate = releaseDate,
            RunningTimeMinutes = runningTime
        };

        //Save movie to a data store
    }
}

You'll notice that this controller only has one action, which returns void. Postman, can you show us what the response looks like when we call this action please?

A Postman response, with the highlighted status code of 204 - No Content

When we call this action, we'll see that ASP.NET returns a status code of 204 - No Content. Since HTTP doesn't understand void, this is the appropriate status code. One of the many features we provide, at no extra charge!

But, what happens if we want to return something a little more complex? If you'll follow me right over here, we can see a sample.

Return Object - 200 OK

Nathan. NATHAN! Quit staring at that butterfly and get over here.

Excuse me. Now, take a look at this action:

[HttpGet]
[Route("movies/all")]
public List<Movie> GetAll()  
{
    List<Movie> movies = new List<Movie>()
    {
        new Movie()
        {
            Id = 1,
            Title = "Up",
            ReleaseDate = new DateTime(2009,5,29),
            RunningTimeMinutes = 96
        }
        //Create some other movies
    };

    return movies;
}

Now our action is returning an object. Postman, what kind of response do we get when calling this action?

A Postman response, with the highlighted code of 200 - OK

Now we get 200 - OK as the status code. But we didn't tell ASP.NET to return this code, so why did it?

Because if you return any serializable object from a Web API action, the response automatically becomes 200 - OK, with the object written in to the response body. Of course, if you wanted to explicitly specify that return code, all you would need to do is change the return type and return statement, like so:

[HttpGet]
[Route("movies/all")]
public IHttpActionResult PinkElephantsOnParade()  
{
    List<Movie> movies = new List<Movie>()
    {
        new Movie()
        {
            Id = 1,
            Title = "Up",
            ReleaseDate = new DateTime(2009,5,29),
            RunningTimeMinutes = 96
        }
        //Create some other movies
    };

    return Ok(movies);
}

Nathan you little animal I'm gonna....

Pardon the action name, we seem to have had a little... difficulty in setting up this display. Rest assured, whatever you name the action, you can use the shortcut method Ok() to return the status code 200 - OK.

Let's hurry along to the next display cabinet, where we will see some other shortcut methods we can use to represent commonly-returned status codes.

Shortcut Methods

I've been using the term "shortcut methods" to refer to a set of methods that return classes which implement IHttpActionResult. The next display cabinet has a few others, each of which represent common HTTP status codes:

  • BadRequest(): Returns 400 - Bad Request.
  • NotFound(): Returns 404 - Not Found.
  • InternalServerError(): Returns 500 - Internal Server Error.

There's also a catchall method, one that can return any HTTP status code:

  • StatusCode(HttpStatusCode statusCode): Returns the specified status code.

HttpStatusCode is an enumeration, whose values can be found over in the Status Codes exhibit. As you see at later points in this tour, HttpStatusCode becomes very useful when trying to return other, less common status from your API.

For now, let's continue down this hallway to see another method by which we can return status codes.

HttpResponseMessage

Though the shortcut methods are useful for common scenarios, they don't cover all possible status codes.... My apologies, please wait here a moment.

Nathan! Mrs. George does NOT need to be shown to the "bodily functions" display. No, it is not "interesting" or "transformative" it is gross and it doesn't even belong to this museum. Now come here! I swear, if your mother wasn't my sister...

As I was saying, the shortcut methods don't cover all possible status codes. For example, the red display in front of you has a method that should return 304 Not Modified:

[HttpGet]
[Route("movies/notmodified")]
public void NotModified()  
{
    //How do we return 304 Not Modified?
}

But there's no shortcut method for that return code, so how do we return it? We use HttpResponseMessage, like in the green display:

[HttpGet]
[Route("movies/notmodified")]
public void NotModified()  
{
    return new HttpResponseMessage(HttpStatusCode.NotModified);
}

HttpResponseMessage represents the response from the server to the client, and can be used with any of the status codes in the HttpStatusCode enumeration. In this way, we can return any of the standard status codes.

Nathan, that does not include 420, that's not a standard code and only one company uses it, stop changing the display!

HttpResponseMessage is perfect for returning status codes that aren't errors, but for the exception handling, there's a more appropriate solution: HttpResponseException.

HttpResponseException

Earlier, we talked about how it is important to select the appropriate status code when returning from an action in Web API projects. Consider the action in the next display:

[HttpGet]
[Route("movies/error")]
public void Error()  
{
    throw new NotImplementedException();
}

NotImplementedException is a .NET Exception that specifies that a given method is not implemented. However, HTTP does not understand .NET exceptions, so Web API will return an error code to a client that calls this method. The error code returned is 500 - Internal Server Error.

We like to say that returning 500 is the equivalent of the server throwing up its hands and saying "well, that didn't work." In other words, it's not a very useful status code. For situations where you don't want to give extra information to client (e.g. public-facing services that could be hit by malicious users) this may be exactly what you want. However, in many other scenarios there is a more appropriate status code to return: 501 Not Implemented.

We can do that by changing the action to look like this:

[Route("movies/error")]
[HttpGet]
public void Error()  
{
    throw new HttpResponseException(HttpStatusCode.NotImplemented);
}

HttpResponseException is a .NET class that translates to an HTTP status code. You can see how else we might handle exceptions with error codes on the Exception Handling tour.

Summary

In short, let's remember the following things about HTTP status codes in Web API projects:

  • Always return the appropriate status code, however you define "appropriate".
  • Use the shortcut methods (e.g. Ok(), NotFound(), etc.) when possible.
  • An action that returns void will send status code 204 No Content.
  • An action which returns an object will send 200 OK.
  • Use HttpResponseMessage for status codes that are not supported by shortcut methods.
  • Use HttpResponseException for error status codes.

Once again, thank you to Postman for your help on this tour (and go download it if you haven't already). For more information, see ASP.NET Web API 2: Building a Restful Service from Start to Finish by Jamie Kurtz and Brian Wortman, specifically Chapter 2, "What is RESTful?". You can find that in the bookshop, it's to your right on the way out of the building.

We hope you have a pleasant rest of your day, and please, come see us again!

Nathan! For the last time, get OUT of the restroom! I know you're not using it for its intended purpose, and the smell never comes out! Your mother will hear of this! You'll be sorry, young man!

Exception Handling in ASP.NET Web API - A Guided Tour

Good afternoon everyone, and welcome to the Exception Handling in ASP.NET Web API Tour here at Exception Not Found. My name is Matthew and I'll be your tour guide today! Step right this way, please. Did everybody have a pleasant morning? Excellent!

Climb on board our brand-new Web API Elevator, and we'll get this tour started. First, let me introduce my brilliant assistant Postman; he'll be working with me on this tour. Please hold your questions for the end of the tour; there will be plenty of time to get to them all. With that, onward to the first floor! All aboard!

Two elevator operators in a store in Japan usher guests into the elevator Lift Ladies by Turelio, used under license

Level 1 - HttpResponseException

First floor: method level exceptions, Request.CreateResponse, and women's fragrances, as I am sure you can tell by now given the looks on your faces.

Let's try to ignore the pungent scents wafting from the sales counters and imagine that we have the following action in our controller:

[Route("CheckId/{id}")]
[HttpGet]
public IHttpActionResult CheckId(int id)  
{
    if(id > 100)
    {
        throw new ArgumentOutOfRangeException();
    }
    return Ok(id);
}

Postman, can you show us what the response from this method looks like with a valid ID?

A Postman response from CheckId, showing a status code 200 OK

Thank you. As you can see, our return status code is 200 - OK. That's exactly what we expect, since 4 is a valid ID. What happens if we submit an invalid ID?

A Postman response from CheckId, showing a status code 500 - Internal Server Error

Now our status code is 500 - Internal Server Error. So what does that mean? To the consumer, this is the equivalent of saying "an error occurred" and not getting any further explanation. Imagine how frustrating that would be (a scenario which we've all encountered, I am sure).

Instead of just throwing a naked exception, let's return a message to the consumer with a better explanation of what happened. In our case, since we used an invalid ID, let's return 400 - Bad Request to better describe the error. We can do this by using the HttpResponseException class:

[Route("CheckId/{id}")]
[HttpGet]
public IHttpActionResult CheckId(int id)  
{
    if (id > 100)
    {
        var message = new HttpResponseMessage(HttpStatusCode.BadRequest)
        {
            Content = new StringContent("We cannot use IDs greater than 100.")
        };
        throw new HttpResponseException(message);
    }
    return Ok(id);
}

Now the response looks like this:

A Postman response, showing the status code 400 Bad Request

For the do-it-yourself people out there, you could also just build the request in the action itself:

[Route("HttpError")]
[HttpGet]
public HttpResponseMessage HttpError()  
{
    return Request.CreateResponse(HttpStatusCode.Forbidden, "You cannot access this method at this time.");
}

Those of you who prefer simple living should know that there are also some shortcut methods for commonly-used status codes:

[Route("Forbidden")]
[HttpGet]
public IHttpActionResult Forbidden()  
{
    return Forbidden();
}

[Route("OK")]
[HttpGet]
public IHttpActionResult OK()  
{
    return Ok();
}

[Route("NotFound")]
[HttpGet]
public IHttpActionResult NotFound()  
{
    return NotFound();
}

Now before any of the eagle-eyed perfume salespeople hustle over here and try to block us from leaving, hurry back on board and we'll scoot on up to the second floor.

Level 2 - Exception Filters

Second floor: exception filters, attributes, and kids clothing!

This floor deals with exceptions at a slightly higher level, if you catch my drift. HttpResponseException and the shortcut methods are great for handling action-level exceptions, but what if we wanted to deal with many exceptions that could be thrown by many different actions? Let's see another controller action.

[Route("ItemNotFound/{id}")]
[HttpPost]
[ItemNotFoundExceptionFilter]
public IHttpActionResult ItemNotFound(int id)  
{
    _service.ThrowItemNotFoundException();
    return Ok();
}

The ThrowItemNotFoundException() method is a custom Exception, defined and used like so:

public class CustomExceptionService  
{
    public void ThrowItemNotFoundException()
    {
        throw new ItemNotFoundException("This is a custom exception.");
    }
}

public class ItemNotFoundException : Exception  
{
    public ItemNotFoundException(string message) : base(message) { }
    public ItemNotFoundException(string message, Exception ex) : base(message, ex) { }
}

ItemNotFoundExceptionFilter inherits from ExceptionFilterAttribute:

public class ItemNotFoundExceptionFilterAttribute : ExceptionFilterAttribute  
{
    public override void OnException(HttpActionExecutedContext context)
    {
        if (context.Exception is ItemNotFoundException)
        {
            var resp = new HttpResponseMessage(HttpStatusCode.NotFound)
            {
                Content = new StringContent(context.Exception.Message),
                ReasonPhrase = "ItemNotFound"
            };
            throw new HttpResponseException(resp);
        }
    }
}

Exception Filters are used whenever a controller action throws an unhandled exception that is not an HttpResponseException. Since they are attributes, we can decorate both controllers and actions with them. In this case, if we call the ItemNotFound action, we should see an HttpResponseException item thrown. Postman, what does this response look like?

A Postman response, showing a status code of 404 Item Not Found

Notice the "ItemNotFound" phrase next to the 404 status code. That phrase is the ReasonPhrase we set in the OnException method.

If the second floor doesn't quite fulfill all your exception handling needs, slide back into our elevator and continue on with me to the third floor. Doors closing, watch your fingers please!

Level 3 - Logging

Third floor: logging and leggings!

On this floor, you can use the built-in ExceptionLogger class that logs any exception in the application. It looks like this:

public class UnhandledExceptionLogger : ExceptionLogger  
{
    public override void Log(ExceptionLoggerContext context)
    {
        var log = context.Exception.ToString();
        //Do whatever logging you need to do here.
    }
}

To use this class in our Web API service, we need to register it. In the WebApiConfig file (or whatever file you are using to configure the service) we need to replace the default ExceptionLogger which Web API automatically adds to the services with our own implementation:

config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger());  

Now, any time an unhandled exception is thrown anywhere in the service, the logger will catch it, and you can write whatever logging code you wish to use.

Ladies, please no trying on the leggings unless you are willing to pay for them. Everybody who would like to visit the top floor, please step back onto the elevator. It's getting a little crowded in here, what will all the exceptions we seem to be catching, so please, make room wherever possible and we'll all get there in one piece.

Level 4 - Exception Handlers

Top floor, last stop! Everybody out!

The last step in our exception handling pipeline is an Exception Handler. Exception Handlers are called after Exception Filters and Exception Loggers, and only if the exception has not already been handled. Here's our Exception Handler:

public class GlobalExceptionHandler : ExceptionHandler  
{
    public override void Handle(ExceptionHandlerContext context)
    {
        if (context.Exception is ArgumentNullException)
        {
            var result = new HttpResponseMessage(HttpStatusCode.BadRequest)
            {
                Content = new StringContent(context.Exception.Message),
                ReasonPhrase = "ArgumentNullException"
            };

            context.Result = new ArgumentNullResult(context.Request, result);
        }
        else
        {
            // Handle other exceptions, do other things
        }
    }

    public class ArgumentNullResult : IHttpActionResult
    {
        private HttpRequestMessage _request;
        private HttpResponseMessage _httpResponseMessage;


        public ArgumentNullResult(HttpRequestMessage request, HttpResponseMessage httpResponseMessage)
        {
            _request = request;
            _httpResponseMessage = httpResponseMessage;
        }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            return Task.FromResult(_httpResponseMessage);
        }
    }
}

The handler, like the logger, must be registered in the Web API configuration. Note that we can only have one Exception Handler per application.

config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());  

Now, let's add a new controller action:

[Route("ArgumentNull/{id}")]
[HttpPost]
public IHttpActionResult ArgumentNull(int id)  
{
    _service.ThrowArgumentNullException();
    return Ok();
}

If Postman calls this action, the response looks like this:

A Postman response, showing the status 400 ArgumentNullExcepton

This (the 404 ArgumentNullException status code and message) is, once again, what we expect. Now we are returning more descriptive errors to our clients, so they have a better idea of what is wrong and how to fix it. That's the excellent customer service we provide!

Summary

Web API provides us a great deal of flexibility in terms of exception handling. To recap:

  • Use HttpResponseException or the shortcut methods to deal with unhandled exceptions at the action level.
  • Use Exception Filters to deal with particular unhandled exceptions on multiple actions and controllers.
  • Use ExceptionLogger to log any unhandled exception.
  • Use Exception Handlers (one per application) to deal with any unhandled exception application-wide.

Don't forget to check out the demonstration display; it's right over there, in the GitHub corner. Also, please give a big round of applause to Postman, my wonderful assistant (and go download it if you haven't already).

If you'd like to take the stairs down, they are just around the corner; those of you who are going down please stay to the right and those of you who decide to come back up please stay to the left (we've had a lot of bumping incidents in the stairwells as of late).

If anybody has any questions, now's the time to ask them. Use the comments below. If not, thanks for coming on the tour, and have a great rest of your day.

Happy Coding!