Improving Error Handling in ASP.NET Web API 2.1 with OWIN

Since its initial conception, one of the core philosophies in ASP.NET Web API has been to provide extension hooks for anything and everything – error handling is no exception (sorry…). However, there’s still room for improvement.

In this article, I highlight a couple of the things I don’t like with how this works today, give an example workaround based on some code I wrote in a real system recently, and suggest how I would like this to work in future versions of ASP.NET Web API. If you want to skip all the detail, there’s a sample implementation of my proposed best-practice over at github.

Update 2016-01-13: When putting the code sample together, I noticed a couple of things I could do even better. I’ve updated the code samples below to match where I think it mattered (most importantly in the passthrough handler).

The current situation

In ASP.NET Web API versions before 2.1, the default error handling mechanism was Exception filters. This had several drawbacks, the main one being that there are a number of cases that exception filters can’t handle, for example exceptions thrown from controller constructors or message handlers, during routing or response content serialization.

The solution, introduced in Web API 2.1, was to introduce two interfaces: IExceptionLogger and IExceptionHandler. These provide a mechanism to hook into the error handling at a deeper level, and handle the problematic cases mentioned above too. Of course, the framework provides default implementations of these interfaces; the implementation of IExceptionHandler, for example, creates a response with the appropriate content type (json/xml/etc…) with some error details, which is certainly good enough in simple projects or internal API:s where the exact format of the error messages doesn’t matter much.

Effectively, this means that to hook into the error handling mechanisms provided by the framework, you implement one of the interfaces, and hook it up in the application configuration. For an app hosted on the OWIN-pipeline (whether self-hosted or in IIS), this means adding something like this to your startup class:

Now, all your exceptions occurring somewhere in your Web API will be caught and sent to the handler.

What can’t this handle?

This is all pretty nice as long as all your (interesting) code is implemented within the bounds of the API. However, when hosting with the OWIN pipeline there are a number of use cases where it’s very useful not to organize your code that way, but put logic for cross-cutting concerns in middleware. A few examples of such cross-cutting concerns are validating or manipulating HTTP headers on incoming requests and responses, custom logic for authentication and/or authorization, request logging or metering.

In the OWIN stack, ASP.NET Web API sits on top of the stack, and the real action doesn’t kick in until all the other middlewares have already had the chance to do a lot of work – and throw a lot of exceptions. Thus, the IExceptionHandler we added above can’t help us here.

Consider this example app:

A request to any controller here will, regardless of what error handling I’ve hooked up with WebAPI, result in an exception that leaks out of my code, and up to the hosting platform. If hosted on IIS, this means a Yellow Screen Of Death; if self-hosted in a naïve console application, it might even result in a crash from which the service is unable to recover.

What can we do about it?

handle all the errors

The first thing to do, is to write a middleware that will handle all exceptions, which we’ll register at the bottom of the stack, letting it handle all errors that occur anywhere:

Now, the error in our buggy middleware will be caught and handled.

However, this doesn’t go all the way; we now have one mechanism for handling errors in middlewares, and another (in principle entirely independent) for handling errors within the Web API.

Reducing duplication

One approach to harmonizing error handling across the entire stack would be to offload as much as possible of the actual work (logging etc) into service classes which can be called both from the IExceptionHandler implementation and from the middleware. However, this becomes more difficult than it could be, because we have access to the HTTP request and response through different, incompatible, API:s in the two places.

The best workaround I’ve been able to come up with so far is to have the IExceptionHandler implementation forward the exception down the stack, and let the middleware do all the actual work. This resulted in the following implementation:

In a previous version, I threw a new exception here with the context.Exception wrapped. Thanks to an anonymous user on StackOverflow I learned about the ExceptionDispatchInfo class which clearly was built with the sole purpose of improving this blog post.

Note that we don’t just throw context.Exception as it is, as this would re-write the stack trace and discard important information about what went wrong. This way, we keep the original stack trace so that the logging mechanisms in the middleware can give us as much information as possible.

Making it perfect

The workaround above still has some drawbacks. For example, I’ve had troubles with the debugger, which won’t break on exceptions where they first happen, but instead break in the IExceptionHandler. To be able to inspect the state of the program when it crashes, I have to set a breakpoint in the handler, inspect the stack trace of context.Exception there, set a new breakpoint and reproduce the error a second time. Clearly a debugging experience sub-par to what I’ve come to expect in Visual Studio.

A different approach, and one that I would have preferred, would be to simply turn off the exception handling mechanisms of the Web API stack, and let uncaught exceptions just propagate down the stack of middlewares until we handle it like any other exception. Unfortunately, despite numerous attempts, I haven’t been able to do this – it seems that it’s only possible to replace the default IExceptionHandler with a custom one, but not to remove it entirely (I asked about this on StackOverflow, but despite a couple of hundred views and a handsome bounty, no-one was able to suggest a way to do this).

Do you have a better solution to this problem? Sound off in the comments section!

13 Comments

  1. David

    I see your reminder (“[add link]”) ;-) I’d like to take a look at your code – thanks!

  2. Tomas Lycken

    Ah, sorry, David – fixed now! I learned a couple of things in the process, so now the workaround is a little less bad, too :)

  3. Piotr

    Great! Now I don’t even need to create a custom exception as a wrapper. Couple of minor corrections to make it compile:
    1) you should override HandleAsync and make it async since you’re not returning a Task :).
    2) ExceptionDispatchInfo doesn’t have a public constructor. Gotta use the Capture() method.
    So my proposition:
    public override async Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
    {
    ExceptionDispatchInfo info = ExceptionDispatchInfo.Capture(context.Exception);
    info.Throw();
    }

  4. smithcole

    Hi, Nice post on how to improve the error handling in Asp.net web 2.1. In this article, a couple of the things write with how this works today, give an example work around based on some code, and suggest how it would work in future versions of ASP.NET Web API and so much useful information available to know. I had a little bit idea about Asp.net when I hosted my business through Myasp.net. Thanks a lot for sharing with us.

  5. Ted Jardine

    Thank you for this thorough writeup. I have left a comment on your original Stack Overflow post, but posting here as well since I’d love to see if anyone else is encountering a similar issue with this configuration whereby CORS headers aren’t set in the response? Works fine via non-CORS app (e.g. Paws/Fiddler), but when hitting an API, the underlying exception is thrown and properly handled, but then the response is overwritten by a subsequent dreaded CORS error: “No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘localhost:3000’; is therefore not allowed access. The response had HTTP status code 500.”

    And yes, CORS is configured properly and works fine everywhere except for when globally handled exceptions are involved (including working without a hitch when returning error responses from within a controller or action filters).

    • Tomas Lycken

      Excellent question!

      CORS is another example of things that the framework does quite well, but only for itself. Just like for error handling, managing CORS headers outside of the Web API pipeline has to be added in a middleware component – again, sort-of defeating the purpose of doing it inside the WebAPI pipeline at all.

      Fortunately, the requirements on such a component are quite simple, so it should be pretty straightforward to implement yourself. And when you’ve done that, you can just as well disable all CORS configuration in Web API, because now you’re handing responsibility over to your own pipeline.

      • Ted Jardine

        Thanks for the quick response! It turns out the CORS implementation doesn’t necessarily set the context.CatchBlock.IsTopLevel, and therefore, exceptions thrown in a controller/filter (well, anywhere still in WebApi) don’t get handled! Sigh. The solution is as simple as overriding ExceptionHandler.ShouldHandle to always return true.

        For a wonderfully thorough discussion with solution/s, see http://stackoverflow.com/questions/24189315/exceptions-in-asp-net-web-api-custom-exception-handler-never-reach-top-level-whe.

        public class GlobalExceptionHandler : ExceptionHandler
        {
        public override void Handle(ExceptionHandlerContext context)
        {
        // handle here…
        }
        public override bool ShouldHandle(ExceptionHandlerContext context)
        {
        return true;
        // if inheriting from System.Web.Http.ExceptionHandling.ExceptionHandler, override ShouldHandle method and return true always
        // because CatchBlock.IsTopLevel might never have true value when enabling CORS.
        //return context.CatchBlock.IsTopLevel;
        }
        }

  6. Robert

    Hi. Thanks for the article. The one thing I’m missing is how to change the response.

    Where you have “// log error, rewrite http response etc” the context.Response and context.Response.content are read only. I’m not sure how to change the response content to my json object.

    • Tomas Lycken

      Thanks!

      The response property itself is read-only, so you can’t replace it, but it’s not immutable; in the sample implementation on GitHub you can see an example of how I change the response status code to 500 and write an error message to the response stream.

      Note that for this to work, you must not have written anything to the response stream before (if you have, it may have already been transmitted to the client). This is usually fine, though, since most applications read everything they need from the request, then do stuff with it – and that’s where things usually fail – and finally write to the response stream. The one thing I’ve found difficult to do something about is serialization errors; if something blows up while the framework is serializing the response (e.g. if the response object graph has circular references), changing the status code here will fail. But that’s really a limitation in the .NET networking stack, not a problem specifically with this middleware approach…

  7. I had this doubt.. and asked same in stackoverflow.. thanks for clearing it out.

  8. Obi

    I thought that this might work for capturing routing errors but unfortunately I didn’t get lucky there. It seems that route errors must be thrown further down in the stack. Do you have any ideas about how you can handle routing errors such as trying to access a non existent controller in WebAPI 2.1? I have already used the Web.Config solution but it isn’t suitable for our purposes, it looks like this:

    In our case we are a pure WebAPI and so we do not want to return any html pages, by specifiying the customErrors section as above, we can get it to redirect the request to an error controller that returns the correct json response we expect with a 404 response code. This is fine for most tools and browsers, but when you observe the behaviour using fiddler you can see a 302 redirect and a 401 caused by the redirect not containing the auth header. Is there any way we can handle errors like this without doing a redirect?

    • Tomas Lycken

      When hosted in an OWIN pipeline, ASP.NET Web API 2 actually acts just like any other middleware component. As such, if you want the error handling middleware from this post to handle routing errors, all you need to accomplish is to make ASP.NET Web API 2 _not_ handle the errors at all, and just throw them (and rely on something down the stack, i.e. the middleware, to handle them).

      It’s been a while since I used ASP.NET Web API 2 (I’m working almost exclusively with .NET Core apps nowadays) but this post hopefully has some useful info: https://docs.microsoft.com/en-us/aspnet/web-api/overview/error-handling/web-api-global-error-handling

Leave a Reply