Exception handling has its own middleware in .NET 8

Published: Monday 15 January 2024

We used to have to write our own custom middleware to handle exception handling in C#.

But with .NET 8, there is a major update that dramatically improves the way we handle errors.

Without any error handling

In an ASP.NET Core Web API, we can set up a basic controller with an endpoint that throws an exception.

// ErrorController.cs
[Route("api/[controller]")]
[ApiController]
public class ErrorController : ControllerBase
{
	[HttpGet]
	public IActionResult Index()
	{
		throw new Exception("This is not going to work");
	}
}

When we run the endpoint, the exception is thrown and we have the debug information so we help resolve the error.

Debug information for exception handling in C#

Debug information for exception handling in C#

This is great for development, but not so good for production. We are exposing vulnerabilities with the codebase which can pose as a major security risk, particularly if it ends up in the wrong hands.

Introducing custom middleware

To reduce the security risk, we can override the HTTP response with a more friendly message. This can be done by writing our own custom middleware, or using the UseExceptionHandler extension method in an ASP.NET Core Program.cs file that appears in the WebApplication type.

// Program.cs
var builder = WebApplication.CreateBuilder(args);

...

var app = builder.Build();

app.UseExceptionHandler(builder =>
{
    builder.Run(async context =>
    {
		// Overrides the HTTP response
        context.Response.StatusCode = 500;

        context.Response.ContentType = Text.Html;

        await context.Response.WriteAsync("<p>An error has occured</p>");
    });
});

...

app.Run();

In this instance, we are setting the status code as 500, the content type as text/html and the response body as <p>An error has occured</p>.

When we run the endpoint now and the exception occurs, rather than having all the debug information, we now have our friendly message returned as part of the response body.

Friendly error message with exception handling in C#

Friendly error message with exception handling in C#

This way will continue to work fine in .NET 8 projects. However, there is a better way of handling exceptions.

Introducing the exception handling middleware

.NET 8 has launched the exception handling middleware meaning we can create classes and register them to the application.

For this to happen, we have to implement the IExceptionHandler interface and the TryHandleAsync method in a new class.

// MyExceptionHandler.cs
public class MyExceptionHandler : IExceptionHandler
{
	public async ValueTask<bool> TryHandleAsync(
		HttpContext httpContext,
		Exception exception,
		CancellationToken cancellationToken)
	{
		httpContext.Response.StatusCode = 500;

		httpContext.Response.ContentType = Text.Html;

		await httpContext.Response.WriteAsync("<p>An error has occured</p>");

		return true;
	}
}

With the exception handler created, we need to register it in Program.cs by calling the AddExceptionHandler extension method that appears in the IServiceCollection instance, adding the class name as the generic type.

As a result, we can remove the code that appears inside the UseExceptionHandler extension method in the WebApplication instance.

// Program.cs
var builder = WebApplication.CreateBuilder(args);

...

// Register the exception handler middleware to the application.
builder.Services.AddExceptionHandler<MyExceptionHandler>();

var app = builder.Build();

app.UseExceptionHandler(builder =>
{

});

...

app.Run();

Just a note that the development exception page will take precedence over any exception handler middleware. As a result, if app.UseDeveloperExceptionPage(); is used in Program.cs, it will need to be removed otherwise it will not work.

When running the application, the friendly error still gets shown as part of the HTTP response body but it is now going through the MyExceptionHandler class.

More than one exception handling middleware

When writing a exception handler middleware class, the TryHandleAsync method returns a boolean. If this is set to true, no other exception handling middleware that is registered to the application will run.

However, if it is set to false, it will try the next exception handling middleware.

The order is determined by which one is registered first in the Program.cs file.

In this instance, we have MyExceptionHandler class registered before MyException2Handler. If the TryHandleAsync method returns false in MyExceptionHandler, it will run the MyException2Handler to handle the error.

// Program.cs
var builder = WebApplication.CreateBuilder(args);

...

// If there is an exception, this will run first
builder.Services.AddExceptionHandler<MyExceptionHandler>(); 

// This will only run if the previous handler is not able to handle the exception
builder.Services.AddExceptionHandler<MyException2Handler>(); 

var app = builder.Build();

app.UseExceptionHandler(builder =>
{

});

...

app.Run();

Watch our video

Watch our video where we try out the new exception handling middleware and show what happens when we have more than one exception handler registered to an ASP.NET Core Web API.