Isaac.

Global Exception Handling

Implement centralized error handling in your application.

By EMEPublished: February 20, 2025
exception handlingmiddlewareerrorslogging

A Simple Analogy

Global exception handling is like a safety net. Any errors fall through and are caught at the bottom.


Why Global Handling?

  • Consistency: Same response format everywhere
  • Logging: Centralized error tracking
  • Security: Don't expose stack traces
  • UX: User-friendly error messages
  • Maintenance: One place to fix error handling

Middleware Approach

public class ExceptionHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ExceptionHandlingMiddleware> _logger;
    
    public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }
    
    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Unhandled exception");
            await HandleExceptionAsync(context, ex);
        }
    }
    
    private static Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        context.Response.ContentType = "application/json";
        
        var response = exception switch
        {
            ValidationException ve => new ErrorResponse
            {
                StatusCode = StatusCodes.Status400BadRequest,
                Message = "Validation failed",
                Errors = ve.Errors
            },
            NotFoundException ne => new ErrorResponse
            {
                StatusCode = StatusCodes.Status404NotFound,
                Message = ne.Message
            },
            _ => new ErrorResponse
            {
                StatusCode = StatusCodes.Status500InternalServerError,
                Message = "An unexpected error occurred"
            }
        };
        
        context.Response.StatusCode = response.StatusCode;
        return context.Response.WriteAsJsonAsync(response);
    }
}

// Register
app.UseMiddleware<ExceptionHandlingMiddleware>();

Custom Exception Classes

public class ValidationException : Exception
{
    public List<string> Errors { get; }
    
    public ValidationException(List<string> errors)
    {
        Errors = errors;
    }
}

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

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

Error Response Format

public class ErrorResponse
{
    public int StatusCode { get; set; }
    public string Message { get; set; }
    public List<string> Errors { get; set; }
    public string TraceId { get; set; }
}

// Usage
var errorResponse = new ErrorResponse
{
    StatusCode = 400,
    Message = "Invalid request",
    Errors = new[] { "Name is required", "Email is invalid" },
    TraceId = context.TraceIdentifier
};

Best Practices

  1. Specific exceptions: Create custom exception types
  2. Logging: Always log exceptions
  3. Status codes: Use appropriate HTTP codes
  4. Security: Don't expose sensitive details
  5. Consistency: Same format for all errors

Related Concepts

  • Try-catch blocks
  • Error monitoring
  • Logging frameworks
  • Recovery strategies

Summary

Implement global exception handling with middleware to catch errors, log them, and return consistent responses.