SyntaxStudy
Sign Up
C# Global Error Handling in ASP.NET Core
C# Beginner 1 min read

Global Error Handling in ASP.NET Core

ASP.NET Core provides multiple layers for handling exceptions globally, so individual controllers and minimal API handlers do not need repetitive try/catch blocks. The built-in exception handling middleware captures unhandled exceptions, logs them, and returns a problem-details response to the client. The `IExceptionHandler` interface (introduced in .NET 8) provides a clean, dependency-injection-friendly way to implement custom exception handling logic. You can register multiple handlers that are tried in registration order. Each handler returns `true` to indicate it handled the exception or `false` to pass it to the next one. Problem Details (RFC 7807) is the standard format for HTTP API error responses. ASP.NET Core's `IProblemDetailsService` creates consistent, machine-readable error bodies containing `type`, `title`, `status`, and `detail` fields. Clients can switch on the `type` URI to determine how to handle the error.
Example
// Global exception handling in ASP.NET Core (.NET 8 minimal API)

using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

// Register custom exception handler (IExceptionHandler, .NET 8+)
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails();

var app = builder.Build();

// Enable exception handler middleware
app.UseExceptionHandler();

// Minimal API endpoints
app.MapGet("/divide/{a}/{b}", (int a, int b) =>
{
    if (b == 0) throw new DivideByZeroException("Cannot divide by zero.");
    return Results.Ok(new { Result = a / b });
});

app.MapGet("/validate/{age}", (int age) =>
{
    if (age < 0) throw new ValidationException("age", age, "Age must be non-negative.");
    return Results.Ok(new { Age = age });
});

app.Run();

// ---- IExceptionHandler implementation ----
public sealed class GlobalExceptionHandler : IExceptionHandler
{
    private readonly ILogger<GlobalExceptionHandler> _logger;
    public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
        => _logger = logger;

    public async ValueTask<bool> TryHandleAsync(
        HttpContext ctx,
        Exception   exception,
        CancellationToken ct)
    {
        _logger.LogError(exception, "Unhandled exception: {Message}", exception.Message);

        var (status, title) = exception switch
        {
            ValidationException   => (400, "Validation Error"),
            DivideByZeroException => (400, "Bad Request"),
            _                     => (500, "Internal Server Error")
        };

        var problem = new ProblemDetails
        {
            Status = status,
            Title  = title,
            Detail = exception.Message
        };

        ctx.Response.StatusCode = status;
        await ctx.Response.WriteAsJsonAsync(problem, ct);
        return true; // handled
    }
}