在ASP.NET Core的异常处理中,UseExceptionHandler是关键组件,但很多人配置后却发现它并不如预期那样工作。这背后有几个关键点需要注意。

ASP.NET Core 里 UseExceptionHandler 怎么配才真正生效
它不是“加了就自动兜住所有异常”的万能药,必须放在中间件管道的正确位置,而且不能和UseDeveloperExceptionPage冲突。
UseExceptionHandler必须在UseRouting之后、UseEndpoints之前注册,否则404或路由前抛的异常会直接漏掉。- 开发环境如果同时启用了
UseDeveloperExceptionPage,它会抢在UseExceptionHandler前拦截异常并直接返回HTML页面——上线前务必确认已移除或条件启用。 - 它只捕获HTTP请求生命周期内的异常(比如Controller执行中、Model Binding失败),不处理后台任务、定时器、中间件初始化等场景抛出的异常。
为什么 try/catch 在 Action 里写一堆不如用 ProblemDetails 返回标准错误
手写 try/catch 容易重复、状态码错乱、丢失堆栈上下文。而ASP.NET Core原生支持RFC 7807标准的ProblemDetails,结构清晰还自带Content-Type和Status Code。
- 直接return
StatusCode(500, new ProblemDetails { Title = "服务异常", Detail = ex.Message }),比手动构造JSON更可靠。 - 配合
ApiController特性时,BadRequest(ModelState)会自动转成ProblemDetails,字段验证失败也能统一格式。 - 注意:不要在
ProblemDetails里暴露ex.StackTrace到生产环境,敏感信息要过滤。
自定义异常类型怎么被 ExceptionHandler 捕获并差异化响应
默认的异常处理器对所有Exception一视同仁,但业务常需要区分ValidationException、NotFoundException等,返回不同状态码和消息。
- 写一个继承
Exception的类,比如BusinessRuleException,并在构造函数里设好StatusCode属性。 - 在
UseExceptionHandler配置里,用app.UseExceptionHandler("/error/business")分流,再配单独的MapControllerRoute处理该路径。 - 更轻量的做法:在全局异常处理中间件里用
if (ex is NotFoundException)类型判断,手动设置context.Response.StatusCode = 404。
中间件里异步操作抛异常,UseExceptionHandler 为什么抓不到
典型场景是HttpContext.Request.Body流读取、HttpClient调用、EF Core Sa veChangesAsync这些await操作,如果没被await或没包进try/catch,异常就会逃逸出请求上下文。
- 确保所有
async方法都显式await,尤其别在lambda里漏掉(比如app.Use(async (ctx, next) => { await next(); ... }))。 UseExceptionHandler只捕获同步异常和被await捕获的异步异常。未await的Task抛异常会变成未观察到的异常,触发进程级事件,不会走HTTP异常流。- EF Core 6+开始,
Sa veChangesAsync抛出的DbUpdateException是同步包装过的,能被捕获;但低版本若用Task.Run包一层,就可能绕过。
最常被忽略的是:异常处理器本身不能抛异常。一旦ExceptionHandler内部出错(比如日志组件崩了、JSON序列化失败),整个请求就彻底挂掉,连500都回不出去。建议在异常处理逻辑里加最简兜底,比如只写日志 + 返回空ProblemDetails。