Global Exception Handling in ASP.NET Core Web API

Posted on Wednesday, 20 Sep 2017

Note: Whilst this post is targeted towards Web API, it's not unique to Web API and can be applied to any framework running on the ASP.NET Core pipeline.

For anyone that uses a command-dispatcher library such as MediatR, Magneto or Brighter (to name a few), you'll know that the pattern encourages you to push your domain logic down into a domain library via a handler, encapsulating your app or API's behaviours such as retrieving an event, like so:

public async Task<IActionResult> Get(int id)
{
    var result = await _mediator.Send(new EventDetail.Query(id));
    return Ok(result);
}

Continuing with the event theme above, within your handler, or in the pipeline before it you'll take care of all of your validation, throwing an exception if the argument is invalid (in this case, an ArgumentException).

Now when it comes to handling that exception you're left having to explicitly catch it from each action method:

public async Task<string> Get(int id)
{
    try {
        var result = await _mediator.Send(new EventDetail.Query(id));
        return Ok(result);
    } catch (ArgumentException){
        return BadRequest();
    }
}

Whilst this is perfectly acceptable, I'm always looking at ways I can reduce boiler plate code, and what happens if an exception is thrown somewhere else in the HTTP pipeline This is why I created Global Exception Handler for ASP.NET Core.

What is Global Exception Handler?

Available via NuGet or GitHub, Global Exception Handler lets you configure an exception handling convention within your Startup.cs file, which will catch any of the exceptions specified, outputting the appropriate error response and status code.

Not just for Web API or MVC

Whilst it's possible to use Global Exception Handler with Web API or MVC, it's actually framework agnostic meaning as long as it runs, or can run on the ASP.NET Core pipeline (such as BotWin or Nancy) then it should work.

Let's take a look at how we can use it alongside Web API (though the configuration will be the same regardless of framework).

How do I use Global Exception Handler for an ASP.NET Core Web API project?

To configure Global Exception Handler, you call it via the the UseWebApiGlobalExceptionHandler extension method in your Configure method, specifying the exception(s) you wish to handle and the resulting status code. In this instance a ArgumentException should translate to a 400 (Bad Request) status code:

public class Startup
{
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseWebApiGlobalExceptionHandler(x =>
        {
            x.ForException<ArgumentException>().ReturnStatusCode(HttpStatusCode.BadRequest);
        });

        app.UseMvc();
    }
}

Now when our MediatR pipeline throws an ArgumentException we no longer need to explicitly catch and handle it in every controller action:

public async Task<IActionResult> Get(int id)
{
    // This throws a ArgumentException
    var result = await _mediator.Send(new EventDetail.Query(id));
    ...
}

Instead our global exception handler will catch the exception and handle it according to our convention, resulting in the following JSON output:

{
    "error": {
        "status": 400,
        "message": "Invalid arguments supplied"
    }
}

This saves us in the following three scenarios:

  • You no longer have to explicitly catch exceptions per method
  • Those times you forgot to add exception handling will be caught
  • Enables you to catch any exceptions further up the HTTP pipeline and propagate to a configured result

Not happy with the error format?

If you're not happy with the default error format then it can be changed in one of two places.

First you can set a global error format via the MessageFormatter method:

Global formatter

public class Startup
{
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseWebApiGlobalExceptionHandler(x =>
        {
            x.ForException<ArgumentException>().ReturnStatusCode(HttpStatusCode.BadRequest);
            x.MessageFormatter(exception => JsonConvert.SerializeObject(new {
                error = new {
                    message = "Something went wrong",
                    statusCode = exception.HttpStatusCode
                }
            }));
        });

        app.UseMvc();
    }
}

Exception specific formatter

Alternatively you can specify a custom message per exception caught, which will override the global one demoed above:

app.UseWebApiGlobalExceptionHandler(x =>
{
    x.ContentType = "text/xml";
    x.ForException<ArgumentException>().ReturnStatusCode(HttpStatusCode.BadRequest).UsingMessageFormatter(
        exception => JsonConvert.SerializeObject(new
        {
            error = new
            {
                message = "Oops, something went wrong"
            }
        }));
    x.MessageFormatter(exception => "This formatter will be overridden when an ArgumentException is thrown");
});

Resulting in the following 400 response:

{
    "error": {
        "message": "Oops, something went wrong"
    }
}

Content type

By default Global Exception Handler is set to output application/json content type, however this can be overridden for those that may prefer to use XML or an alternative format. This can be done via the the ContentType property:

app.UseWebApiGlobalExceptionHandler(x =>
{
    x.ContentType = "text/xml";
    x.ForException<ArgumentException>().ReturnStatusCode(HttpStatusCode.BadRequest)
    x.MessageFormatter(exception => {
        // serialise your XML in here
    });
});

Moving forward

Having used this for a little while now, one suggestion was to implement problem+json as the default content type, standardising the default output which I'm seriously considering. I'm also in the process of building ASP.NET Core MVC compatibility so exceptions can result in views being rendered or requests being redirect to routes (such as a 404 page not found view).

For more information feel free to check out the GitHub page or try it out via Nuget.

Back