How to Create an exception handler in ASP.NET Core 8

By FoxLearn 1/2/2025 9:55:51 AM   40
Microsoft’s .NET 8 release introduced the IExceptionHandler interface in ASP.NET Core 8, improving how exceptions are handled in web applications.

The article explains how to leverage IExceptionHandler to provide users with meaningful error responses, enhancing the overall error-handling experience in ASP.NET Core 8 applications.

Understanding the Need for an Exception Handler

In ASP.NET Core, an exception handler is a component that manages unhandled exceptions globally, providing a centralized mechanism for error handling. It ensures that exceptions are caught, errors are logged and processed, and meaningful error responses are generated, allowing the application to fail gracefully.

using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Net;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseExceptionHandler(option => { });
app.MapGet("/GenerateError", () =>
{
    throw new NotImplementedException();
});
app.Run();

When accessing the /GenerateError endpoint, the error response displayed in the browser is unformatted, making it difficult to read and understand the error metadata.

Introducing the IExceptionHandler interface

ASP.NET Core 8 enhances this with the introduction of the IExceptionHandler interface, allowing developers to create a centralized class for handling exceptions in their applications.

public interface IExceptionHandler
{
    ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken);
}

The IExceptionHandler interface includes the TryHandleAsync method, which takes three parameters: HttpContext, Exception, and CancellationToken, and returns a ValueTask to indicate whether the exception was handled. When implementing this method, you must return true if the exception is handled, or false if not.

How to Create a custom exception handler in ASP.NET Core

To implement the IExceptionHandler interface, create a class called GlobalExceptionHandler in a file named GlobalExceptionHandler.cs and add the appropriate code to it.

public class GlobalExceptionHandler(IHostEnvironment hostEnvironment, ILogger<GlobalExceptionHandler> logger)
    : IExceptionHandler
{
    public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
    {
        return true;
    }
}

Your custom error handling logic should be placed in the TryHandleAsync method, where you can handle exceptions asynchronously, as shown in the following code snippet.

private const string ExceptionMessage = "An unhandled exception has occurred while executing the request.";

public async ValueTask TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
{
    logger.LogError(exception, exception is Exception ? exception.Message : ExceptionMessage);
    var problemDetails = CreateProblemDetails(httpContext, exception);
    await httpContext.Response.WriteAsJsonAsync(problemDetails, cancellationToken);
    return true;
}

The CreateProblemDetails method creates a ProblemDetails object containing error metadata, as shown below:

private ProblemDetails CreateProblemDetails(HttpContext httpContext, Exception exception)
{
    httpContext.Response.ContentType = "application/json";

    // Determine the appropriate HTTP status code based on the exception type
    switch (exception)
    {
        case UnauthorizedAccessException unauthorizedAccessException:
            httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            break;

        case ArgumentNullException argumentNullException:
            httpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
            break;

        default:
            httpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
            break;
    }

    // Create a ProblemDetails object with error metadata
    return new ProblemDetails
    {
        Status = httpContext.Response.StatusCode,
        Type = exception.GetType().Name,
        Title = "An error occurred processing your request",
        Detail = exception.Message,
        Instance = $"{httpContext.Request.Method} {httpContext.Request.Path}"
    };
}

Full Source Code for Custom Exception Handler

The following code provides the complete source code for the GlobalExceptionHandler class.

public class GlobalExceptionHandler : IExceptionHandler
{
    private const string ExceptionMessage = "An unhandled exception has occurred while executing the request.";

    private readonly ILogger<GlobalExceptionHandler> _logger;

    public GlobalExceptionHandler(IHostEnvironment hostEnvironment, ILogger<GlobalExceptionHandler> logger)
    {
        _logger = logger;
    }

    public async ValueTask TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
    {
        // Log the exception
        _logger.LogError(exception, exception.Message);

        // Create the problem details response
        var problemDetails = CreateProblemDetails(httpContext, exception);

        // Write the response as JSON
        await httpContext.Response.WriteAsJsonAsync(problemDetails, cancellationToken);
        return true;
    }

    private ProblemDetails CreateProblemDetails(HttpContext httpContext, Exception exception)
    {
        httpContext.Response.ContentType = "application/json";

        // Set status code based on exception type
        httpContext.Response.StatusCode = exception switch
        {
            NotImplementedException => (int)HttpStatusCode.BadRequest,
            _ => (int)HttpStatusCode.InternalServerError
        };

        // Return structured problem details
        return new ProblemDetails
        {
            Status = httpContext.Response.StatusCode,
            Type = exception.GetType().Name,
            Title = "An unexpected error occurred",
            Detail = exception.Message,
            Instance = $"{httpContext.Request.Method} {httpContext.Request.Path}"
        };
    }
}

Registering the Exception Handler in the Pipeline

To use the custom exception handler in your application, register it in the request processing pipeline by adding builder.Services.AddExceptionHandler<GlobalExceptionHandler>(); in the Program.cs file.

Then, create an error-handling endpoint using app.MapGet("/GenerateError", () => { throw new ValidationException(); }); to simulate an error.

Next, configure the pipeline to use the exception handler with app.UseExceptionHandler(opt => { });. When accessing the /GenerateError endpoint, the application will display a user-friendly error response, as defined by your custom handler.

For example:

builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
app.MapGet("/GenerateError", () =>
{
    throw new ValidationException();
});
app.UseExceptionHandler(option => { });

Implementing a custom exception handler ensures that error responses meet your specific format and requirements, giving you more control over how exceptions are managed in your application.