IConfiguration vs IOptions NET
Synchronous and Asynchronous in .NET Core
Model Binding and Validation in ASP.NET Core
ControllerBase vs Controller in ASP.NET Core
ConfigureServices and Configure methods
IHostedService interface in .NET Core
ASP.NET Core request processing
| ControllerBase vs Controller in ASP.NET Core | REST-vs-GraphQL | |
Global Exception Handling in ASP.NET Core |
Implementing global exception handling in ASP.NET Core is essential for building resilient and maintainable applications. You’ve got multiple strategies depending on your version and architecture preferences.
Exception handling ensures that your application can gracefully recover from unexpected errors, provide meaningful feedback to users, and log issues for diagnostics. Global filters centralize this logic, making your code cleaner and more maintainable.
In ASP.NET Core, global exception handling is usually done via middleware so that any unhandled exceptions that bubble up the request pipeline are caught in one place. This ensures consistency (structured error responses, logging, etc.) instead of scattering try–catch blocks across controllers. Here are the main approaches:
UseExceptionHandler
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Error Controller:
[ApiController]
public class ErrorController : ControllerBase
{
[Route("error")]
public IActionResult HandleError() =>
Problem();
}
For more control (logging, custom JSON format, etc.), create your own 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 Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled exception");
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = "application/json";
var response = new
{
error = "An unexpected error occurred",
details = ex.Message
};
await context.Response.WriteAsJsonAsync(response);
}
}
}
Register it in the pipeline:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMiddleware<ExceptionHandlingMiddleware>();
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapControllers());
}
IExceptionFilter Abstraction This is the modern, modular approach introduced in .NET 8.
public class GlobalExceptionFilter : IExceptionFilter
{
private readonly ILogger<GlobalExceptionFilter> _logger;
public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger)
{
_logger = logger;
}
public void OnException(ExceptionContext context)
{
_logger.LogError(context.Exception, "Unhandled exception");
context.Result = new ObjectResult(new
{
error = "An error occurred",
details = context.Exception.Message
})
{
StatusCode = StatusCodes.Status500InternalServerError
};
context.ExceptionHandled = true;
}
}
Register Filter in Program.cs :
services.AddControllers(options =>
{
options.Filters.Add<GlobalExceptionFilter>();
});
UseDeveloperExceptionPage in development for detailed errors.UseExceptionHandler or custom middleware in production.ILogger.ProblemDetails.ILogger<T> into your handler to log exceptions with context. | ControllerBase vs Controller in ASP.NET Core | REST-vs-GraphQL | |