ASP.NET Web API 2.1 introduced some significant improvements to the mechanisms that support global error handling. Before this release there were a number of different types of errors that would be handled directly by the runtime and there was no easy way to intercept these errors and add your own custom behavior. The standard guidance suggests you register these new handlers as services, but I prefer a different approach that seems more natural to me.
The pipe that all flows through
Every request that is processed by Web API goes through the HttpMessageHandler
pipeline until it is dispatched to a controller. The message handler pipeline works in a Russian doll approach where the request goes through each handler from first to last and then the response returns back through the same handlers in reverse.
A trap at the end of the pipe
If a controller cannot process a request for some reason and decides that the appropriate response is to throw an exception then my gut tells me that somewhere up the call stack should be the place where that exception is caught. If I want to have a place where I can catch all exceptions generated by message handlers, the framework dispatching code, or the controllers itself, then having a ErrorHandlingMessageHandler
at the front of the message handler pipeline seems like the most natural place.
public class ErrorHandlerMessageHandler : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken) { try { return await base.SendAsync(request, cancellationToken); } catch (Exception ex) { return GlobalErrorHandler.ConvertExceptionToHttpResponse(ex); } } }
It will not work when…
With this approach there are two types of exceptions that my message handler will not catch: the first is connectivity errors detected by the HttpServer
. For example the client closed the TCP connection whilst waiting for the response. The second is serialization errors generated when the HttpServer
finally asks the HttpContent
to serialize its bytes to a stream and for some reason it fails. I discussed a workaround to this scenario in another post about debugging serialization errors.
Turn off the default behaviour
By default if you have registered a IErrorHandler
and IErrorLogger
server those services will be called when exceptions are trapped in framework code. The expected behavior is that the registered services will create a HttpResponseMessage
and set the context object Result
property to direct the framework to return response. That response will be returned back up the pipeline and the message handlers in the pipeline will be somewhat ignorant of the failed request.
public class GlobalErrorHandlerService : IExceptionHandler { public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken) { if (context.CatchBlock != ExceptionCatchBlocks.HttpServer) { context.Result = null; } return Task.FromResult(0); } }
However, if your registered services explicitly set the Result property to Null, then the framework will take that as an indication that your service have not handled the error and will re-throw the exception that was initially raised. This will allow each message handler in the pipeline the opportunity to catch the exception and do any processing before allowing the error handling message handler to convert the exception to an actual HTTP response message. If the exception is caught up in the HttpServer
framework code, then it is too late for the message handler to catch it.
It's a style thing
I'm not going to claim any major benefits of this approach to centralizing error handling over the use of IErrorHandler
service mechanism. The advantage I see is that it is just one less interaction protocol to deal with. I already deal with the message handler pipeline, I understand how it works, and I know how to intercept requests and responses using it and there is nothing really about it that is Web API specific. With the new mechanisms introduced by WebAPI, I feel like I need to learn a new set of interfaces, how the framework interacts with those interfaces and what my responsibilities are as an interface implementer. I get especially frustrated by the pattern of passing in context objects that require me to set properties as return values. It just feels hokey.
One slightly less touchy feely difference between the two approaches relates to how and exception handling message handlers are able to intercept some of the error responses generated by the Web API framework. For example, when the routing mechanism fails to identify an appropriate action method, no exception is thrown, instead a response is created with a 404 status code and a HTTP body that contains certain property values. An error handling message handler, could intercept those responses and update the body of the response to be something more standardized. Something like http-problem for example.
Web API your way
Although this approach is not going to appeal to everyone, it does demonstrate an important aspect of the Web API framework. ASP.NET Web API was never intended to be a particularly opinionated framework. It was designed to allow people to build HTTP based applications in many different ways. There are many different extensibility points and components that can be completely swapped out and replaced. This is a key strength of the framework. You should take advantage of it.
Image Credit: Pipe https://flic.kr/p/9GuHGn
Image Credit: Wipeout https://flic.kr/p/8vfZYQ
Image Credit: Surf Style https://flic.kr/p/5GNhLA