#csharp #dotnet #backend

Day 22: Error Handling & Global Filters

Welcome to Day 22. You don’t want to write try { ... } catch (Exception e) { return Results.Problem(e.Message); } inside every single one of your 500 endpoint methods.

It clutters logic and is easy to forget.

Global Exception Handling

Instead of catching errors in the endpoints, we let them bubble up through the pipeline and catch them at a global Middleware level.

Starting in .NET 8, they introduced IExceptionHandler which standardizes this beautifully!

using Microsoft.AspNetCore.Diagnostics;

// 1. Create a global handler
public class GlobalExceptionHandler : IExceptionHandler
{
    private readonly ILogger<GlobalExceptionHandler> _logger;

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

    public async ValueTask<bool> TryHandleAsync(
        HttpContext httpContext, Exception exception, CancellationToken token)
    {
        // Log the severe error (stack trace, etc.) to your console/server
        _logger.LogError(exception, "Unhandled Exception occurred: {Message}", exception.Message);

        var problemDetails = new ProblemDetails
        {
            Status = StatusCodes.Status500InternalServerError,
            Title = "Server Error",
            Detail = "An unexpected fault happened. Try again later."
        };

        // Write the generic standardized JSON response to the client
        httpContext.Response.StatusCode = problemDetails.Status.Value;
        await httpContext.Response.WriteAsJsonAsync(problemDetails, token);

        return true; // We handled it, don't throw it higher!
    }
}

Registering the Global Handler

Now, tell the DI Container about it, and add it to your HTTP Pipeline in Program.cs.

var builder = WebApplication.CreateBuilder(args);

// Register the custom handler
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
// Add the default ProblemDetails service
builder.Services.AddProblemDetails(); 

var app = builder.Build();

// MIDDLEWARE PIPELINE
// Add error handling to the absolute TOP of the pipeline
// so it catches EVERYTHING bubbling up from below.
app.UseExceptionHandler(); 

app.MapGet("/", () => {
    throw new Exception("Boom! Server crashed."); // Our handler will catch this!
});

app.Run();

Creating Custom Exceptions

It’s common to throw custom exceptions throughout your application logic, and then have the Exception Handler map those specific exceptions to specific HTTP Codes.

public class NotFoundException : Exception 
{
    public NotFoundException(string message) : base(message) {}
}

// In the Handler:
// if (exception is NotFoundException) 
// { 
//     status = 404; title = "Not Found"; 
// }

Challenge for Day 22

Implement the GlobalExceptionHandler from this tutorial in a fresh ASP.NET Core project. Create an endpoint that randomly throws an InvalidOperationException and verify the client receives a neat JSON response Instead of a massive HTML stack trace page.

Tomorrow: Authentication (Who are you?).