Creating a custom ErrorHandlerMiddleware function
Tags: aspnetcore, errors
Published: 2020-03-22 17:38:17

---
title: Creating a custom ErrorHandlerMiddleware function
published: true
date: 2020-03-03 10:00:00 UTC
tags: ASP.NET Core,Error Handling
canonical_url: https://andrewlock.net/creating-a-custom-error-handler-middleware-function/
---

 ![Creating a custom ErrorHandlerMiddleware function](https://andrewlock.net/content/images/2020/ex_handler_banner.png)

In this post I show how to customise the `ExceptionHandlerMiddleware` to create custom responses when an error occurs in your middleware pipeline, instead of providing a path to "re-execute" the pipeline.

## Exception handling in Razor Pages

All .NET applications generate errors, and unfortunately throw exceptions, and it's important you handle those in your ASP.NET middleware pipeline. Server-side rendered applications like Razor Pages typically want to catch those exceptions and redirect to an error page.

For example, if you create a new web application that uses Razor Pages (`dotnet new webapp`), you'll see the following middleware configuration in `Startup.Configure`:

```csharp
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
    }

    // .. other middleware not shown
}
```

When running in the `Development` environment, the application will catch any exceptions thrown when handling a request, and display them as a web page using the very useful `DeveloperExceptionMiddleware`:

![The developer exception page](https://andrewlock.net/content/images/2020/ex_handler_01.png)

This is incredibly useful during local development, as it lets you quickly examine the stack trace, request Headers, routing details, and other things.

Of course that's all sensitive information that you don't want to expose in production. So when not in development, we use a different exception handler, the `ExceptionHandlerMiddleware`. This middleware allows you to provide a request path, `"/Error"` by default, and uses it to "re-execute" the middleware pipeline, to generate the final response:

<picture>
    <source srcset="https://andrewlock.net/content/images/2020/ex_handler_02.svg" type="image/svg+xml">
    <img src="https://andrewlock.net/content/images/2020/ex_handler_02.png" alt="Re-executing the pipeline using ">
</source></picture>

The end result for a Razor Pages app is that the _Error.cshtml_ Razor Page is returned whenever an exception occurs in production:

![The exception page in production](https://andrewlock.net/content/images/2020/ex_handler_03.png)

That covers the exception handling for razor pages, but what about for Web APIs?

## Exception handling for Web APIs

The default exception handling in the web API template (`dotnet new webapi`) is similar to that used by Razor Pages, with an important difference:

```csharp
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    // .. other middleware not shown
}
```

As you can see the `DeveloperExceptionMiddleware` is still added when in the `Development` environment, but there's no error handling added at all in production! That's not as bad as it sounds: even though there's no exception handling middleware, ASP.NET Core will catch the exception in its infrastructure, log it, and return a blank `500` response to clients:

![An exception](https://andrewlock.net/content/images/2020/ex_handler_04.png)

If you're using the `[ApiController]` attribute (you probably should be), and the error comes from your Web API controller, then you'll get a `ProblemDetails` result by default, or you can customize it further.

That's actually not too bad for Web API clients. Consumers of your API should be able to handle error responses, so end users won't be seeing the "broken" page above. However, it's often not as simple as that.

For example, maybe you are using a standard format for your errors, such as the [ProblemDetails](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.problemdetails) format. If your client is expecting all errors to have that format, then the empty response generated in some cases may well cause the client to break. Similarly, in the `Development` environment, returning an HTML developer exception page when the client is expecting JSON will likely cause issues!

One solution to this is described in the [official documentation](https://docs.microsoft.com/en-us/aspnet/core/web-api/handle-errors?view=aspnetcore-3.1#exception-handler), in which it's suggested you create an `ErrorController` with two endpoints:

```csharp
[ApiController]
public class ErrorController : ControllerBase
{
    [Route("/error-local-development")]
    public IActionResult ErrorLocalDevelopment() => Problem(); // Add extra details here

    [Route("/error")]
    public IActionResult Error() => Problem();
}
```

And then use the same "re-execute" functionality used in the Razor Pages app to generate the response:

```csharp
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseExceptionHandler("/error-local-development");
    }
    else
    {
        app.UseExceptionHandler("/error");
    }

    // .. other middleware
}
```

This works fine, but there's something that's always bugged me about using the same infrastructure that generated an exception (e.g. Razor Pages or MVC) to generate the exception message. Too many times I've been bitten by a failure to generate the _error_ response due to the exception being thrown a second time! For that reason I like to take a slightly different approach.

## Using ExceptionHandler instead of ExceptionHandlingPath

When I first started using ASP.NET Core, my approach to tackling this problem was to write my own custom ExceptionHandler middleware to generate the responses directly. "It can't be that hard to handle an exception, right"?

Turns out it's a bit more complicated than that (shocking, I know). There's various edge cases you need to handle like:

- If the response had already started sending when the exception occurred, you can't intercept it.
- If the `EndpointMiddleware` had executed when the exception occurred, you need to do some juggling with the selected endpoints
- You don't want to cache the error response

The [`ExceptionHandlerMiddleware` handles all these cases](https://github.com/dotnet/aspnetcore/blob/6255c1ed960f5277d2e96ac2d0968c2c7e844ce2/src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerMiddleware.cs), so re-writing your own version is not the way to go. Luckily, although providing a path for the middleware to re-execute is the commonly shown approach, there's another option - provide a handling function directly.

The `ExceptionHandlerMiddleware` takes an `ExceptionHandlerOptions` as a parameter. [This option object has two properties](https://github.com/dotnet/aspnetcore/blob/6255c1ed960f5277d2e96ac2d0968c2c7e844ce2/src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerOptions.cs):

```csharp
public class ExceptionHandlerOptions
{
    public PathString ExceptionHandlingPath { get; set; }
    public RequestDelegate ExceptionHandler { get; set; }
}
```

When you provide the re-execute path to the `UseExceptionHandler(path)` method, you're actually setting the `ExceptionHandlingPath` on the options object. Instead, you can set the `ExceptionHandler` property and pass an instance of `ExceptionHandlerOptions` in directly to the middleware using `UseExceptionHandler()` if you wish:

```csharp
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseExceptionHandler(new ExceptionHandlerOptions
    {
        ExceptionHandler = // .. to implement
    });

    // .. othe middleware
}
```

Alternatively, you can use a different overload of `UseExceptionHandler()` and configure a mini middleware pipeline to generate your response:

```csharp
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseExceptionHandler(err => err.UseCustomErrors(env)); // .. to implement

    // .. othe middleware
}
```

Both approaches are equivalent so it's more a question of taste. In this post I'm going to use the second approach, and implement the `UseCustomErrors()` function.

## Creating a custom exception handler function

For this example, I'm going to assume that we want to generate a `ProblemDetails` object when we get an exception in the middleware pipeline. I'm also going to assume that our API only supports JSON. That avoids us having to worry about XML content negotiation and the like. In development, the `ProblemDetails` response will contain the full exception stack trace, and in production it will just show a generic error message.

> `ProblemDetails` is [an industry standard](https://tools.ietf.org/html/rfc7807) way of returning machine-readable details of errors in a HTTP response. It's the generally supported way of return error messages from Web APIs in ASP.NET Core 3.x (and to an extent, in version 2.2).

We'll start by defining the `UseCustomErrors` function in a static helper class. This helper class adds a single piece of response-generating middleware to the provided `IApplicationBuilder`. In development, it ultimately calls the `WriteResponse` method, and sets `includeDetails: true`. In other environments, `includeDetails` is set to false.

```csharp
using System;
using System.Diagnostics;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;

public static class CustomErrorHandlerHelper
{
    public static void UseCustomErrors(this IApplicationBuilder app, IHostEnvironment environment)
    {
        if (environment.IsDevelopment())
        {
            app.Use(WriteDevelopmentResponse);
        }
        else
        {
            app.Use(WriteProductionResponse);
        }
    }

    private static Task WriteDevelopmentResponse(HttpContext httpContext, Func<Task> next)
        => WriteResponse(httpContext, includeDetails: true);

    private static Task WriteProductionResponse(HttpContext httpContext, Func<Task> next)
        => WriteResponse(httpContext, includeDetails: false);

    private static async Task WriteResponse(HttpContext httpContext, bool includeDetails)
    {
        // .. to implement
    }
}
```

All that remains is to implement the `WriteResponse` function to generate our response. This retrieves the exception from the `ExceptionHandlerMiddleware` (via the `IExceptionHandlerFeature`) and builds a `ProblemDetails` object containing the details to display. It then uses the `System.Text.Json` serializer to write the object to the Response stream.

```csharp
private static async Task WriteResponse(HttpContext httpContext, bool includeDetails)
{
    // Try and retrieve the error from the ExceptionHandler middleware
    var exceptionDetails = httpContext.Features.Get<IExceptionHandlerFeature>();
    var ex = exceptionDetails?.Error;

    // Should always exist, but best to be safe!
    if (ex != null)
    {
        // ProblemDetails has it's own content type
        httpContext.Response.ContentType = "application/problem+json";

        // Get the details to display, depending on whether we want to expose the raw exception
        var title = includeDetails ? "An error occured: " + ex.Message : "An error occured";
        var details = includeDetails ? ex.ToString() : null;

        var problem = new ProblemDetails
        {
            Status = 500,
            Title = title,
            Detail = details
        };

        // This is often very handy information for tracing the specific request
        var traceId = Activity.Current?.Id ?? httpContext?.TraceIdentifier;
        if (traceId != null)
        {
            problem.Extensions["traceId"] = traceId;
        }

        //Serialize the problem details object to the Response as JSON (using System.Text.Json)
        var stream = httpContext.Response.Body;
        await JsonSerializer.SerializeAsync(stream, problem);
    }
}
```

You can record any other values you like on the `ProblemDetails` object that an be retrieved from the `HttpContext` before it's serialized

> Be aware that the `ExceptionHandlerMiddleware` [clears out the route values](https://github.com/dotnet/aspnetcore/blob/6255c1ed960f5277d2e96ac2d0968c2c7e844ce2/src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerMiddleware.cs#L107) before calling your exception handler method so those are not available.

If your application now throws an exception in the `Development` environment, you'll get the full exception returned as JSON in the response:

![ProblemDetails response in Development](https://andrewlock.net/content/images/2020/ex_handler_05.png)

While in production, you'll still get a ProblemDetails response, but with the details elided:

![ProblemDetails response in Production](https://andrewlock.net/content/images/2020/ex_handler_06.png)

This approach obviously has some limitations compared to the MVC/re-execute path approach, namely that you don't easily get model binding, content-negotiation, easy serialization, or localization (depending on your approach).

If you need any of these (e.g. maybe you serialize from MVC using PascalCase instead of camelCase), then using this approach may be more hassle than its worth. If so, then [the Controller approach described](https://docs.microsoft.com/en-us/aspnet/core/web-api/handle-errors?view=aspnetcore-3.1#exception-handler) is probably the sensible route to take.

If none of that is a concern for you, then the simple handler approach shown in this post may be the better option. Either way, don't try and implement your own version of the `ExceptionHandlerMiddleware` - use the extension points available! 🙂

## Summary

In this post I described the default exception handling middleware approach for Razor Pages and for Web APIs. I highlighted a problem with the default Web API template configuration, especially if clients are expecting valid JSON, even for errors.

I then showed the suggested approach [from the official documentation](https://docs.microsoft.com/en-us/aspnet/core/web-api/handle-errors?view=aspnetcore-3.1#exception-handler) that uses an MVC controller to generate a `ProblemDetails` response for APIs. This approach works well, except if the problem was an issue with your MVC configuration itself, in which trying to execute the `ErrorController` will fail.

As an alternative, I showed how you could provide a custom exception handling function to the `ExceptionHandlerMiddleware` that is used to generate a response instead. I finally showed an example handler that serializes a `ProblemDetails` object to JSON, including details in the `Development` environment, and excluding them in other environments.