How to Trap Uncaught Exceptions and Avoid 5xx Errors in ASP.Net WebAPI


I was recently writing a WebAPI 2.x project for a customer that involved file IO, decompression, and database IO. Though the things the API does are not complex, there are many things that can go wrong in any of those types of operations that can cause exceptions to be thrown (SQL parsing errors, IO errors, connection errors, etc). I took steps to catch and deal with the exceptions that I could anticipate, but there is always the potential for some to go unhandled, which results in a 500 class error from the WebAPI. 5xx errors are very generic “Internal Server” errors and difficult to debug in addition to being just plain ugly going back to the customer.

WebAPI 1.x has Exception Filters, which cover many cases. You’ll also notice, as you go through the documentation, that it tells you how to write and register them.

WebAPI 2.x has even more, global options that allow you to cover more cases, like exceptions thrown from controller constructors or during a response stream. The new options are 2 base classes, ExceptionLogger and ExceptionHandler, that you can inherit from and override methods on to gain control of how uncaught exceptions are handled. The goal of the first class is to allow you to insert a call to your logging framework to log the exception. The goal of the second class is to allow you to send the client an error message of your choosing rather than “500 Internal Server Error”. This looked great and was what
I wanted to do, but though the doc explained the goals of global error handling, it didn’t explain how to properly write or register these options.

Of course, like most projects, it had to be done yesterday and professionally because, well, that’s my job. In order to leverage these new exception handling options, I had to spend a good deal of time asking questions, doing web queries, downloading, and spelunking through sample code.

I would like to save you that time by showing you how to implement register ExceptionLogger and ExceptionHandler.

ExceptionLogger
To implement ExceptionLogger, simply write your own class that inherits from it, as the docs show. At the time of this blog post, the docs do not show the proper override to write though. At a minimum, override the Log(ExceptionContext context) method. If you look at the source code for ExceptionLogger, you’ll see that the base implementation of LogAsync will wrap a call to your overridden Log method in a Task, so no need to override it unless you really need to do something special in the async case.

To register ExceptionLogger, add this call to the Register(HttpConfiguration config) method of your WebApiConfig class:
config.Services.Add(typeof(IExceptionLogger), new TraceExceptionLogger());

ExceptionHandler
To implement ExceptionHandler, you again write your own class that inherits from it, as the docs show, but instead you must override
the Handle(ExceptionHandlerContext context) method at a minimum.
To register ExceptionHandler, add this call to the Register(HttpConfiguration config) method of your WebApiConfig class:
config.Services.Replace(typeof(IExceptionHandler), new UncaughtExceptionHandler());

Here is a sample that I finally found that shows registration and implementation. It uses an additional package called ELMAH, but you don’t need that to implement what I’ve told you about here. The sample shows 2 very basic controllers that throw exceptions and how these are handled with an implementation of ExceptionLogger (ElmahExceptionLogger) and ExceptionHandler (GenericTextExceptionHandler). Registration of these classes is shown in WebApiConfig for the project.

References
https://docs.microsoft.com/en-us/aspnet/web-api/overview/error-handling/exception-handling
https://docs.microsoft.com/en-us/aspnet/web-api/overview/error-handling/web-api-global-error-handling
https://docs.microsoft.com/en-us/aspnet/web-api/overview/releases/whats-new-in-aspnet-web-api-21#global-error
http://aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/Elmah/ReadMe.txt

Comments (0)

Skip to main content