Tutorials ASP.NET Core Web API

Global Exception Handling Middleware

On this page

Global Exception Handling Middleware

Wrapping every single Controller action in a try { ... } catch { return StatusCode(500); } block is an egregious anti-pattern. Not only does it bloat your codebase with redundant code, but it also risks missing errors thrown outside controllers. The architect's solution is a Global Exception Handling Middleware.

1. The Concept of Middleware

Middleware is a pipeline. Every HTTP request enters the pipeline, passes through various middlewares (like Authentication), hits the Controller, and the Response bubbles back out through the identical pipeline. If we put a massive try-catch at the very entrance of the pipeline, it will perfectly catch any exception thrown anywhere in the entire application.

2. Creating the Custom Middleware

We create a class that intercepts the HTTP context, invokes the rest of the application (await _next(context)), and catches any resulting explosions.

using System.Net;
using System.Text.Json;

public class GlobalExceptionMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<GlobalExceptionMiddleware> _logger;
    private readonly IHostEnvironment _env;

    // _next represents the rest of the application pipeline
    public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger, IHostEnvironment env)
    {
        _next = next;
        _logger = logger;
        _env = env;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            // PROCEED: Let the request traverse the rest of the API
            await _next(context);
        }
        catch (Exception ex)
        {
            // BOOM! An unhandled exception was thrown somewhere!
            _logger.LogError(ex, "An unhandled exception occurred.");
            await HandleExceptionAsync(context, ex);
        }
    }

    private async Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        // Standardize the API response to always be JSON
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

        // Custom Error Object (If in Production, NEVER show the stack trace)
        var response = new 
        {
            StatusCode = context.Response.StatusCode,
            Message = _env.IsDevelopment() ? exception.Message : "An unexpected internal server error occurred.",
            Details = _env.IsDevelopment() ? exception.StackTrace : null
        };

        var json = JsonSerializer.Serialize(response);
        await context.Response.WriteAsync(json);
    }
}

3. Wiring it up in Program.cs

Location in the pipeline is critical. The exception handler MUST be the very first middleware registered so it completely wraps everything below it.

var app = builder.Build();

// 1. MUST BE FIRST IN THE PIPELINE
app.UseMiddleware<GlobalExceptionMiddleware>();

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();

4. The Modern .NET 8 Alternative: IExceptionHandler

In newer .NET versions, Microsoft introduced an interface-based approach that is slightly cleaner than writing raw middleware.

public class CustomExceptionHandler : IExceptionHandler
{
    public async ValueTask<bool> TryHandleAsync(HttpContext context, Exception exception, CancellationToken cancellationToken)
    {
        // Log it, modify the response, and return true to indicate the exception was handled.
        context.Response.StatusCode = 500;
        await context.Response.WriteAsJsonAsync(new { Error = "Fatal Server Error." }, cancellationToken);
        return true; 
    }
}

// In Program.cs
builder.Services.AddExceptionHandler<CustomExceptionHandler>();
app.UseExceptionHandler(_ => { }); // Activates it in the pipeline

5. Interview Mastery

Q: "If an unhandled exception occurs in a background task (IHostedService), will the Global Exception Handling Middleware catch it?"

Architect Answer: "No. Middleware only exists within the context of an executing HTTP Request pipeline. An `IHostedService` (like a background timer running every 5 minutes) executes entirely outside of the HTTP pipeline. If an unhandled exception occurs inside a background service, the Middleware will never see it. Instead, you must wrap your background task execution logic in its own `try/catch` block. Furthermore, starting in .NET 6, unhandled exceptions in background services will violently crash the entire host process by default (terminating the web server completely) unless the `HostOptions.BackgroundServiceExceptionBehavior` is explicitly configured to Ignore them."

Questions on this lesson 0

Sign in to ask a question or upvote helpful answers.

No questions yet — be the first to ask!

ASP.NET Core Web API
Course syllabus
1. Fundamentals & HTTP
2. Request Handling
3. Data Access & Architecture
4. Data Transfer & Validation
5. Advanced Concepts
6. Security & Authorization
7. Documentation & Testing
8. Microservices & Deployment
Toolliyo Assistant
Ask about tutorials, ebooks, training, pricing, mentor services, and support. I use public site content only—not admin or internal tools.

care@toolliyo.com

Need callback? Share your details