UseExceptionHandler必须在UseRouting之后、UseEndpoints之前注册,否则404、静态资源异常等无法捕获;开发环境需禁用UseDeveloperExceptionPage避免拦截;仅处理HTTP请求生命周期异常,不覆盖后台任务。

UseExceptionHandler 必须放在 UseRouting 之后、UseEndpoints 之前,否则 404、静态资源异常、路由解析失败等根本进不来——这不是“没生效”,是压根没机会触发。
UseExceptionHandler 的注册顺序为什么关键
它不是“加了就兜住所有 HTTP 异常”的万能开关,而是严格依赖中间件管道位置的拦截器。一旦放错,大量常见错误(比如 /favicon.ico 404、UseStaticFiles 找不到文件、控制器路由不存在)都会绕过它,直接返回原始错误响应或空白页。
- 正确顺序:
UseRouting→UseAuthentication(如有)→UseAuthorization(如有)→UseExceptionHandler→UseEndpoints - 常见错误:把它写在
UseStaticFiles后面但仍在UseRouting前,结果连路由都没解析,异常就抛给了 Kestrel 默认处理器 - 开发环境要禁用
UseDeveloperExceptionPage,否则它会抢在UseExceptionHandler前渲染 HTML,导致自定义逻辑完全不执行
为什么 /error 路由返回空白或 404
传入 app.UseExceptionHandler("/error") 不是“配置路径”,而是发起一次内部重定向请求——这个路径必须对应一个真实、可访问、且不抛异常的 Action,否则就会循环失败或 404。
- 该 Action 必须返回
ObjectResult(如Ok(new ProblemDetails { ... })),不能返回ViewResult(HTML 页面),否则 API 客户端收不到 JSON - Action 内部不能再 throw 新异常,否则会再次触发
UseExceptionHandler,形成死循环 - 确保控制器路由已注册,比如用
MapControllerRoute配了"error"模板,或控制器加了[Route("error")]
后台任务和 async void 的异常完全不归它管
UseExceptionHandler 只捕获 HTTP 请求生命周期内的异常。Task.Run、Timer 回调、IHostedService 启动/执行、甚至 async void 方法里的异常,都不会经过它——CLR 直接终止线程,进程可能继续跑,但错误静默丢失。
-
async void是黑洞:WinForms 事件、Timer.Elapsed += async void handler → 异常无法被任何全局机制捕获,必须改写为async Task并在调用处await - 后台任务必须显式 try/catch:在
StartAsync、ExecuteAsync或 Timer 回调体内包一层try { ... } catch (Exception ex) { _logger.LogError(ex, "xxx"); } - 未 await 的 Task 异常走
TaskScheduler.UnobservedTaskException,需单独订阅并记录,但它也无法阻止进程退出
ProblemDetails 构造时最容易暴露敏感信息
直接把 ex.ToString() 或 ex.StackTrace 塞进 Detail 字段,上线后等于把数据库连接字符串、物理路径、内部类名全打给前端。
- 生产环境务必清空
Detail和Instance字段,或仅保留脱敏后的摘要(如"订单创建失败,请稍后重试") - 用
context.Response.WriteAsJsonAsync(new ProblemDetails { Status = 500, Title = "服务异常" }),别手写JsonResult(new { message = ex.Message })—— 后者绕过框架内容协商,还可能漏设状态码 - 若需差异化响应(如
NotFoundException返回 404),在异常处理逻辑里用if (ex is NotFoundException)判断,再手动设context.Response.StatusCode
最常被忽略的一点:异常处理器自身不能抛异常。哪怕只是日志组件不可用、序列化失败、或临时磁盘满,只要它自己崩了,整个错误处理链就断了——最终用户看到的会是空响应或 500 原始页面。务必在 HandleExceptionAsync 内部做最小化、最稳的操作。


















