.NET 5全局异常处理选型:ExceptionFilter与Middleware该如何选择?ASP.NET Core应用最佳实践解析
Hey there! Great question—this is a super common point of confusion when setting up robust error handling in ASP.NET Core (including .NET 5). Let’s break down the two options, their strengths, and which one is the best fit for most apps.
First, Let’s Understand the Differences
Middleware: The Broad, Pipeline-Wide Solution
Middleware sits directly in ASP.NET Core’s request pipeline, which means it can catch every unhandled exception that flows through the entire pipeline. That includes exceptions from:
- API controllers and MVC actions
- Razor Pages
- Static file requests
- Custom middleware components
- Any other endpoint or request handler in your app
It’s the most comprehensive option because it’s not tied to the MVC framework—its scope covers the entire request lifecycle.
Here’s a quick example of a custom exception-handling middleware (though I’d recommend using the built-in UseExceptionHandler instead, which we’ll cover later):
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 httpContext) { try { // Pass the request to the next middleware in the pipeline await _next(httpContext); } catch (Exception ex) { _logger.LogError(ex, "Unhandled exception occurred"); await HandleExceptionResponse(httpContext, ex); } } private static async Task HandleExceptionResponse(HttpContext context, Exception ex) { context.Response.ContentType = "application/json"; context.Response.StatusCode = StatusCodes.Status500InternalServerError; var errorResponse = new { StatusCode = context.Response.StatusCode, Message = "An unexpected error occurred while processing your request", Details = ex.Message }; await context.Response.WriteAsJsonAsync(errorResponse); } } // Extension method to register the middleware public static class MiddlewareExtensions { public static IApplicationBuilder UseGlobalExceptionHandling(this IApplicationBuilder app) { return app.UseMiddleware<GlobalExceptionMiddleware>(); } } // Register in Startup.cs app.UseGlobalExceptionHandling();
ExceptionFilter: MVC/Razor Pages-Specific Handling
ExceptionFilters are part of the MVC/Razor Pages framework, so they only catch exceptions thrown during the execution of MVC actions or Razor Page handlers. They can’t touch exceptions from static files, custom middleware, or non-MVC endpoints.
The upside? They give you full access to MVC-specific context: action routes, model state, route data, and more. This makes them useful if you need to tailor error responses specifically for MVC requests.
Here’s an example of an async exception filter:
public class GlobalMvcExceptionFilter : IAsyncExceptionFilter { private readonly ILogger<GlobalMvcExceptionFilter> _logger; public GlobalMvcExceptionFilter(ILogger<GlobalMvcExceptionFilter> logger) { _logger = logger; } public async Task OnExceptionAsync(ExceptionContext context) { _logger.LogError(context.Exception, "Exception occurred in MVC action/handler"); // Customize the response using MVC context var errorResult = new ObjectResult(new { StatusCode = StatusCodes.Status500InternalServerError, Message = "Error processing your request", ActionName = context.ActionDescriptor.DisplayName, Details = context.Exception.Message }) { StatusCode = StatusCodes.Status500InternalServerError }; context.Result = errorResult; context.ExceptionHandled = true; // Mark the exception as handled } } // Register in Startup.cs services.AddControllersWithViews(options => { options.Filters.Add<GlobalMvcExceptionFilter>(); });
Which is the Best Practice for Most Apps?
For the vast majority of ASP.NET Core applications (APIs, web apps, hybrid apps), Middleware (specifically the built-in UseExceptionHandler middleware) is the best choice. Here’s why:
- Full coverage: It catches every unhandled exception across your entire app, ensuring consistent error responses no matter what request a user makes.
- Simplicity: The built-in
UseExceptionHandlerhandles a lot of boilerplate for you, like accessing exception details viaIExceptionHandlerPathFeature. - Flexibility: You can configure it to return JSON responses for APIs or redirect to error pages for web apps.
Here’s how to use the official UseExceptionHandler middleware in .NET 5:
app.UseExceptionHandler(errorApp => { errorApp.Run(async context => { context.Response.StatusCode = StatusCodes.Status500InternalServerError; context.Response.ContentType = "application/json"; var exceptionFeature = context.Features.Get<IExceptionHandlerPathFeature>(); if (exceptionFeature != null) { var errorResponse = new { StatusCode = context.Response.StatusCode, Message = "Oops! Something went wrong.", Path = exceptionFeature.Path, Details = exceptionFeature.Error.Message }; await context.Response.WriteAsJsonAsync(errorResponse); } }); });
When Should You Use ExceptionFilter?
ExceptionFilter isn’t a replacement for middleware, but it’s great as a supplementary tool when you need:
- MVC-specific context to customize error responses (e.g., returning different errors based on the action route)
- To handle exceptions only within MVC/Razor Pages workflows without affecting other parts of your app
Final Takeaway
Stick with Middleware (preferably the built-in UseExceptionHandler) as your primary global exception handling mechanism. Use ExceptionFilter only when you need MVC/Razor Pages-specific logic that middleware can’t easily handle.
内容的提问来源于stack exchange,提问作者Tolga Cakir




