Building Microservices with Asp.Net WebAPI, Owin, Ninject, NHibernate, and Azure -- Part 2: Error Responses and Exception
Last time we looked at setting up a microservice based on WebAPI with dependency injection an persistance. In this article, we’ll look at global exception handling and error responses.
The Error Response
We return standard error message formats for 500 errors. In WebAPI, Microsoft uses
HttpError
internally within the framework. For consistency, we do the same. That way, whether the error was generated in the pipeline, or by our business logic, the message is the same. Under the hood, HttpError inherits Dictionary
. You can add custom key/value pairs for anything you’d like to return.
The response the looks something like…
{
"message":"An error has occurred",
"exceptionMessage":"Object not set to an instance of an object."
}
The dictionary keys to look up standard error information are available on the HttpErrorKeys type.
Using it, we can generate a
IHttpActionResult
to return…public class ExceptionResult : IHttpActionResult
{
readonly ExceptionHandlerContext context;
public ExceptionResult(ExceptionHandlerContext context)
{
this.context = context;
}
public Task<IHttpActionResult> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.Run(()=>{
HttpError error = new HttpError(context.Exception, includeDetail: true);
return context.Request.CreateErrorResponse(HttpStatusCode.InternalServerError, error);
},cancellationToken);
}
}
That’s the simplified version. For public facing APIs we disable sending the stacktrace (setting
includeDetail: false
) in the constructor for HttpError
. Setting includeDetail
to false
will not include any Exception
related information in the dictionary.Setting up a Global ExceptionHandler
Rather than putting a try/catch in every controller action, we use a custom implementation of
System.Web.Http.ExceptionHandling.ExceptionHandler
.public class GlobalExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
if (ShouldHandle(context))
context.Result = new ExceptionResult(context);
}
}
Then, it’s just a matter of using our custom
ExceptionHandler
instead of the default one. In our Startup.cs…var config = new HttpConfiguration();
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
That’s really all there is to it. As I mentioned the last article, we use Azure Application Insights for our monitoring and logging. This gets us all the request tracing and logging of these error messages for free. Notice that our
Handle()
method doesn’t call HandleException()
. This is do the exception will continue to be processed by the rest of the pipeline and be automatically logged in Application Insights.Exception Logger
If you’re not using and APM solution like Application Insights, NewRelic, or AWS X-Ray (and I highly recommend you do), you also implement a custom
ExceptionLogger
to log the exception.public class GlobalExceptionLogger : ExceptionLogger
{
public override void Log(ExceptionLoggerContext context)
{
//TODO: Implement your custom logging here
}
}
Then, add it to your Startup.cs…
var config = new HttpConfiguration();
config.Services.Add(typeof(IExceptionLogger), new GlobalExceptionLogger());
Recap
So far we’ve looked at:
- Creating a clean, bare-bones WebAPI project
- Bootstrapping Dependency Injection using Ninject
- Configuring persistance using NHibernate
- Configuring transient fault handling for the persistance layer
- Setting up a global
ExceptionHandler
to deal with exceptions in a unified, consistent way
Comments
Post a Comment