ASP.NET Core Web API如何统一处理异常替代控制器重复try-catch
最优实现方案:全局异常处理
ASP.NET Core 原生请求管道支持全局异常拦截,完全不需要在每个控制器重复编写try-catch代码,一次配置即可覆盖所有接口、服务层、中间件抛出的未处理异常,冗余度为0。
方案1:自定义异常中间件(兼容所有.NET Core/.NET 5+版本,通用性最强)
核心逻辑是把异常捕获逻辑放到请求管道的最外层,所有后续请求执行过程中抛出的异常都会被统一捕获处理。
- 编写全局异常中间件:
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 context) { try { await _next(context); } catch (UnauthorizedAccessException ex) { _logger.LogWarning(ex, "未授权访问"); await WriteResponseAsync(context, StatusCodes.Status401Unauthorized, "访问未授权"); } catch (BadRequestException ex) { _logger.LogWarning(ex, "请求参数错误"); await WriteResponseAsync(context, StatusCodes.Status400BadRequest, ex.Message); } catch (HttpRequestException ex) { _logger.LogError(ex, "下游HTTP请求失败"); await WriteResponseAsync(context, StatusCodes.Status503ServiceUnavailable, "外部服务调用失败"); } catch (TimeoutRejectedException ex) { _logger.LogError(ex, "请求处理超时"); await WriteResponseAsync(context, StatusCodes.Status408RequestTimeout, "请求处理超时,请稍后重试"); } catch (FileNotFoundException ex) { _logger.LogError(ex, "资源文件不存在"); await WriteResponseAsync(context, StatusCodes.Status404NotFound, "请求的资源不存在"); } catch (SqlException ex) { _logger.LogCritical(ex, "数据库操作异常"); await WriteResponseAsync(context, StatusCodes.Status500InternalServerError, "服务内部数据处理错误"); } catch (ValidationException ex) { _logger.LogWarning(ex, "参数校验失败"); await WriteResponseAsync(context, StatusCodes.Status400BadRequest, ex.Message); } catch (Exception ex) { _logger.LogCritical(ex, "未处理的全局异常"); await WriteResponseAsync(context, StatusCodes.Status500InternalServerError, "服务内部错误,请联系管理员"); } } private async Task WriteResponseAsync(HttpContext context, int statusCode, string message) { context.Response.ContentType = "application/json"; context.Response.StatusCode = statusCode; // 可替换为项目自定义的统一响应结构 var result = JsonSerializer.Serialize(new { code = statusCode, msg = message, success = false }); await context.Response.WriteAsync(result); } }
- 注册中间件
在Program.cs中,将异常中间件注册到所有需要捕获异常的中间件最前面,保证异常能被拦截:
var app = builder.Build(); // 全局异常中间件必须放在最前面 app.UseMiddleware<GlobalExceptionMiddleware>(); // 后续再注册其他中间件,例如HTTPS重定向、路由、授权、控制器映射等 app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();
方案2:.NET 7+ 原生IExceptionHandler(轻量方案,官方推荐)
.NET 7及以上版本提供了原生异常处理器抽象,不需要手动编写完整中间件,实现接口即可完成全局异常处理:
- 编写自定义异常处理器
public class CustomExceptionHandler : IExceptionHandler { private readonly ILogger<CustomExceptionHandler> _logger; public CustomExceptionHandler(ILogger<CustomExceptionHandler> logger) { _logger = logger; } public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken) { var (statusCode, msg) = exception switch { UnauthorizedAccessException => (StatusCodes.Status401Unauthorized, "访问未授权"), BadRequestException ex => (StatusCodes.Status400BadRequest, ex.Message), HttpRequestException => (StatusCodes.Status503ServiceUnavailable, "外部服务调用失败"), TimeoutRejectedException => (StatusCodes.Status408RequestTimeout, "请求处理超时,请稍后重试"), FileNotFoundException => (StatusCodes.Status404NotFound, "请求的资源不存在"), SqlException => (StatusCodes.Status500InternalServerError, "服务内部数据处理错误"), ValidationException ex => (StatusCodes.Status400BadRequest, ex.Message), _ => (StatusCodes.Status500InternalServerError, "服务内部错误,请联系管理员") }; _logger.LogError(exception, "全局异常捕获:{Message}", msg); httpContext.Response.StatusCode = statusCode; httpContext.Response.ContentType = "application/json"; await httpContext.Response.WriteAsJsonAsync(new { code = statusCode, msg = msg, success = false }, cancellationToken); return true; } }
- 注册服务和中间件
// Program.cs中注册服务 builder.Services.AddExceptionHandler<CustomExceptionHandler>(); var app = builder.Build(); // 注册全局异常中间件 app.UseExceptionHandler(_ => { }); // 其他中间件注册...
注意事项
- 配置完成后可以直接删除所有控制器中冗余的try-catch代码,全局处理器会自动捕获所有未处理异常,覆盖范围包含控制器、服务层、其他自定义中间件,比控制器内单独捕获的覆盖范围更广
- 异常中间件必须放在请求管道的最前端,否则后续中间件抛出的异常无法被捕获
- 特殊接口需要自定义异常返回逻辑时,可以在接口内部单独编写try-catch,局部捕获的异常不会进入全局处理逻辑,兼容特殊场景需求
- 不推荐使用早期MVC版本的ExceptionFilter方案,该方案只能捕获控制器层的异常,无法拦截中间件、路由层等其他位置抛出的异常,覆盖范围有限
内容的提问来源于stack exchange,提问作者Stephen
相关产品推荐
相关产品推荐

