When building APIs, one of the most common mistakes developers make is scattering try-catch blocks across controllers. Not only does this make the code messy, but it also leads to inconsistent error responses for clients.
The solution? Global Exception Handling using middleware in .NET Web API.
Why Global Exception Handling?
- Centralized error management: All exceptions go through one pipeline.
- Consistent responses: Clients always receive structured error messages.
- Cleaner controllers: No need for repetitive try-catch blocks.
- Better logging: Easier to log all unhandled exceptions.
Step 1: Create Exception Handling Middleware
public class GlobalExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<GlobalExceptionMiddleware> _logger;
public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled exception occurred.");
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var response = new
{
StatusCode = context.Response.StatusCode,
Message = "Internal Server Error. Please try again later.",
Detail = exception.Message // Optional: remove in production
};
return context.Response.WriteAsJsonAsync(response);
}
}
Step 2: Register Middleware in Program.cs
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Add global exception middleware
app.UseMiddleware<GlobalExceptionMiddleware>();
app.MapControllers();
app.Run();
Step 3: Testing the Middleware
Add a controller that throws an exception:
[ApiController]
[Route("api/[controller]")]
public class TestController : ControllerBase
{
[HttpGet("error")]
public IActionResult GetError()
{
throw new InvalidOperationException("This is a test exception.");
}
}
Call GET /api/test/error
and you should get a JSON response like:
{
"StatusCode": 500,
"Message": "Internal Server Error. Please try again later.",
"Detail": "This is a test exception."
}
Step 4: Optional Enhancements
-
Environment-aware messages: Hide
Detail
in production. - Custom exception types: Handle validation exceptions differently.
- Structured logging: Include correlation IDs for tracing.
- Problem Details RFC 7807: Return standardized error responses.
Wrapping Up
Global exception handling keeps your Web API clean, consistent, and easier to maintain. By centralizing error management, you free controllers from repetitive try-catch blocks, make logging easier, and ensure clients always receive a predictable response.
This pattern is a must-have for any production-ready .NET Web API.
Top comments (0)