ASP.NET Core 8 Web API -

Global Exception Handling

Global exception handling is essential in ASP.NET Core 8 Web API to ensure that your application can gracefully handle unexpected errors and provide meaningful responses to clients. This guide covers the basics and advanced concepts of exception handling, complete with examples, comparisons, and best practices.


1. Introduction to Global Exception Handling

Global exception handling in ASP.NET Core can be achieved using middleware, which allows you to catch and handle exceptions at a single point in the request pipeline. This ensures that all unhandled exceptions are caught, logged, and transformed into appropriate HTTP responses.


2. Basic Exception Handling Middleware

Example: Simple Exception Handling Middleware

Create Middleware
        
            
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, "An unhandled exception has 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 { message = "An unexpected error occurred.", detail = exception.Message };
        var jsonResponse = JsonSerializer.Serialize(response);

        return context.Response.WriteAsync(jsonResponse);
    }
}

        
    
Register Middleware
        
            
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseMiddleware<ExceptionHandlingMiddleware>();

    // Other middleware registrations
    app.UseRouting();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

        
    

3. Advanced Exception Handling

Advanced exception handling includes logging, custom error responses, and differentiating between different types of exceptions.

Example: Advanced Exception Handling Middleware

Create Advanced Middleware
        
            
public class AdvancedExceptionHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<AdvancedExceptionHandlingMiddleware> _logger;

    public AdvancedExceptionHandlingMiddleware(RequestDelegate next, ILogger<AdvancedExceptionHandlingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (CustomNotFoundException ex)
        {
            await HandleExceptionAsync(context, ex, HttpStatusCode.NotFound);
        }
        catch (CustomBadRequestException ex)
        {
            await HandleExceptionAsync(context, ex, HttpStatusCode.BadRequest);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "An unhandled exception has occurred.");
            await HandleExceptionAsync(context, ex, HttpStatusCode.InternalServerError);
        }
    }

    private static Task HandleExceptionAsync(HttpContext context, Exception exception, HttpStatusCode statusCode)
    {
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = (int)statusCode;

        var response = new { message = exception.Message, detail = exception.StackTrace };
        var jsonResponse = JsonSerializer.Serialize(response);

        return context.Response.WriteAsync(jsonResponse);
    }
}

        
    
Register Middleware
        
            
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseMiddleware<AdvancedExceptionHandlingMiddleware>();

    // Other middleware registrations
    app.UseRouting();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

        
    

4. Custom Exception Classes

Creating custom exception classes helps in differentiating between various types of exceptions and handling them accordingly.

Example: Custom Exception Classes

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

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

        
    

5. Result Model

A result model standardizes the structure of your API responses, providing a consistent format for both success and error responses.

Example: Result Model

        
            
public class ApiResponse<T>
{
    public bool Success { get; set; }
    public T Data { get; set; }
    public string Message { get; set; }
    public List<string> Errors { get; set; }
}

        
    

6. Response Model

The response model helps in structuring error responses and can include additional details like error codes and validation errors.

Example: Response Model

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

    public ErrorResponse(string message)
    {
        Message = message;
        Errors = new List<string>();
    }

    public ErrorResponse(string message, List<string> errors)
    {
        Message = message;
        Errors = errors;
    }
}

        
    

7. Customizing Error Responses

You can customize error responses based on the type of exception and provide additional details like error codes.

Example: Custom Error Responses

Update Middleware to Use ErrorResponse
        
            
private static Task HandleExceptionAsync(HttpContext context, Exception exception, HttpStatusCode statusCode)
{
    context.Response.ContentType = "application/json";
    context.Response.StatusCode = (int)statusCode;

    ErrorResponse errorResponse;
    if (exception is CustomNotFoundException)
    {
        errorResponse = new ErrorResponse("Not Found", new List<string> { exception.Message });
    }
    else if (exception is CustomBadRequestException)
    {
        errorResponse = new ErrorResponse("Bad Request", new List<string> { exception.Message });
    }
    else
    {
        errorResponse = new ErrorResponse("Internal Server Error", new List<string> { exception.Message });
    }

    var jsonResponse = JsonSerializer.Serialize(errorResponse);

    return context.Response.WriteAsync(jsonResponse);
}

        
    

8. Comparing Exception Handling Approaches

Approach Pros Cons
Simple Middleware Easy to implement, single point of exception handling Limited customization, does not differentiate between exception types
Advanced Middleware Handles different exception types, custom error responses More complex, requires additional classes
Result Model Standardizes API responses, consistent format Requires additional coding for response generation
Response Model Detailed error information, customizable Additional overhead in defining and managing error responses


9. Best Practices

Example: Suppressing Model State Validation

        
            
builder.Services.Configure<ApiBehaviorOptions>(options =>
{
    options.SuppressModelStateInvalidFilter = true;
});

        
    

10. Conclusion

Global exception handling in ASP.NET Core 8 Web API is a powerful feature that ensures your application can gracefully handle and respond to unexpected errors. By understanding and implementing middleware for exception handling, creating custom exception classes, using result and response models, and following best practices, you can build robust and maintainable web APIs. This comprehensive guide provides the tools and knowledge to master global exception handling in ASP.NET Core 8.