Sign in to track progress and bookmarks.
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.
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.
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);
}
}
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();
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
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."
Quizzes linked to this course—pass to earn certificates.
On this page
1. The Concept of Middleware 2. Creating the Custom Middleware 3. Wiring it up in Program.cs 4. The Modern .NET 8 Alternative: IExceptionHandler 5. Interview Mastery