
Global Exception Handling in ASP.NET WEB API
Web API
22 Articles
Table of Contents
What we gonna do?
In this article, let's learn about Global Exception Handling in Web API in ASP.NET Core.
Why we gonna do?
Global Exception Handling allows us to handle exceptions globally in one single place inside the application rather than scattering try-catch blocks everywhere in the code base. This is a good and clean practice that simplifies code maintenance and improves readability.
Errors are inevitable in APIs. To standardize error responses, ASP.NET Core supports the Problem Details payload as defined by RFC 7807. By enabling the default implementation of IProblemDetailsService through AddProblemDetails, we can automatically generate structured error responses. This approach integrates seamlessly with middlewares like the exception handler and developer exception page, providing clean and consistent error details in both production and development environments. This simplifies debugging and enhances the API's usability.
How we gonna do?
To implement Global Exception Handling, follow these steps:
Starting .NET 8, We can achieve this using the AddProblemDetails to Get structured error response defined in RFC 7807 standard.
var builder = WebApplication.CreateBuilder(args); builder.Services .AddProblemDetails(options => options.CustomizeProblemDetails = (ctx) => { var hostEnv = ctx.HttpContext.RequestServices.GetRequiredService<IHostEnvironment>(); var logger = ctx.HttpContext.RequestServices.GetRequiredService<ILogger<ProblemDetails>>(); // exception will hold the thrown exception var exception = ctx.HttpContext.Features.Get<IExceptionHandlerFeature>()?.Error; if (ctx.ProblemDetails.Status == 500) { logger.LogCritical(exception, exception!.Message); if (exception is DomainException domainException) { ctx.ProblemDetails.Title = "An error occurred in our API. Use the trace id when contacting us."; ctx.ProblemDetails.Detail = domainException.Message; } else if (exception is Exception ex) { ctx.ProblemDetails.Title = "An unexpected fault happened. Try again later."; ctx.ProblemDetails.Detail = hostEnv.IsProduction() ? "An unexpected fault happened. Try again later." : ex.Message; } } } )
Register the middleware as the first middleware in the pipeline:
var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.UseExceptionHandler() // Use this to handle exceptions globally with problem details support. If this is enabled below line can be commented. .UseRouting() .UseEndpoints(endpoints => { endpoints.MapControllers(); });; app.Run();
The alternative is to Create a Global Exception Middleware as shown below if you want a custom response format implementation:
using System.Net; namespace ILoveDotNet.Middlewares; public sealed class ExceptionHandlerMiddleware : IMiddleware { private readonly IWebHostEnvironment _env; private readonly ILogger<ExceptionHandlerMiddleware> _logger; public ExceptionHandlerMiddleware(IWebHostEnvironment env, ILogger<ExceptionHandlerMiddleware> logger) { _env = env; _logger = logger; } public async Task InvokeAsync(HttpContext context, RequestDelegate next) { try { await next(context); } catch (DomainException ex) { await HandleDomainExceptionAsync(context, ex); } catch (Exception ex) { await HandleExceptionAsync(context, ex); } } private Task HandleDomainExceptionAsync(HttpContext context, DomainException exception) { logger.LogCritical(exception, exception.Message); string result = exception.Message; context.Response.ContentType = MediaTypeNames.Application.Json; context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; return context.Response.WriteAsync(result); } private Task HandleExceptionAsync(HttpContext context, Exception exception) { _logger.LogCritical(exception, exception.Message); var result = _env.IsProduction() ? "An unexpected fault happened. Try again later." : exception.Message; context.Response.ContentType = MediaTypeNames.Application.Json; context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; return context.Response.WriteAsync(result); } } public static class ExceptionHandlerMiddlewareExtensions { public static IApplicationBuilder UseGlobalExceptionHandler(this IApplicationBuilder builder) { return builder.UseMiddleware<ExceptionHandlerMiddleware>(); } }
This middleware inherits from IMiddleware and catches exceptions thrown from the application. It writes a general error message in production and the actual error message in non-production environments.
Register the middleware as the first middleware in the pipeline:
var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.UseGlobalExceptionHandler() .UseRouting() .UseEndpoints(endpoints => { endpoints.MapControllers(); });; app.Run();
Registering it first ensures that all exceptions are caught. If registered later, some exceptions might be missed.
Summary
In this article, we explored the simplest and easiest way to handle exceptions globally in ASP.NET Web API using a custom middleware. This approach centralizes exception handling, reduces code duplication, and improves maintainability.