useexceptionhandler必须在userouting之后、useendpoints之前注册,否则无法捕获404、路由失败等异常;它仅处理http请求上下文中的未处理异常,后台任务异常需手动try-catch并记录日志。

UseExceptionHandler中间件必须放在UseRouting之后、UseEndpoints之前
顺序错,UseExceptionHandler就形同虚设。它只捕获 HTTP 请求管道中后续中间件抛出的未处理异常,如果注册在UseStaticFiles或UseRouting之前,404、静态资源路径错误、路由解析失败等异常根本不会流经它。
常见错误写法:app.UseExceptionHandler("/error"); app.UseRouting(); app.UseEndpoints(...);——此时所有路由相关异常(包括 404)都已被前置中间件吞掉或直接响应,UseExceptionHandler收不到。
- 正确顺序:先
UseRouting,再UseExceptionHandler,最后UseEndpoints - 若用路径形式(如
"/error"),必须确保对应控制器 Action 存在且返回ObjectResult(不是ViewResult),否则 API 客户端收到 HTML 就会解析失败 - 开发阶段可配合
UseDeveloperExceptionPage(),但仅限IsDevelopment()环境启用,生产环境必须禁用
自定义中间件里别直接throw new Exception()或return new JsonResult()
手写中间件时,很多人在HandleExceptionAsync里直接throw新异常,或者用JsonResult构造响应——这会绕过框架对ProblemDetails的标准化处理,导致状态码错乱、字段泄露、Content-Type不匹配等问题。
比如return new JsonResult(new { message = ex.Message }),既没设Status,又可能把数据库连接字符串、文件路径等敏感信息打出去。
- 优先复用
context.Response.WriteAsJsonAsync(new ProblemDetails { ... }) - 手动构造
ProblemDetails时,务必显式设置Status和Type,清空Detail(尤其生产环境)和Instance - 不要在异常处理逻辑里再抛异常,否则可能触发循环调用或响应已提交异常(
System.InvalidOperationException: Headers are read-only)
后台任务、Timer、IHostedService里的异常完全不会进UseExceptionHandler
UseExceptionHandler只管 HTTP 请求上下文中的异常。一旦离开这个上下文——比如Task.Run、Timer回调、IHostedService.StartAsync里抛出的异常,中间件压根看不到。
更危险的是async void方法(如 WinForms 事件或 Timer 回调写成async void OnTimerElapsed),这种异常连TaskScheduler.UnobservedTaskException都捕获不到,线程直接终止,进程可能静默退出。
- 所有后台任务必须显式
try/catch,并记录日志 - 把
async void全部改为async Task,并在调用处await -
TaskScheduler.UnobservedTaskException可作为兜底,但仅适用于未await的Task,不能替代主动防护
ModelState验证失败、400类错误默认不触发UseExceptionHandler
很多人以为UseExceptionHandler能捕获所有“错误”,结果发现参数绑定失败、ModelState.IsValid == false、[Required]校验不通过时,接口返回 400 却没进中间件——因为这些是框架提前拦截并响应的,根本没走到你的控制器逻辑里。
想统一处理这类问题,得靠InvalidModelStateResponseFactory配置,而不是指望UseExceptionHandler。
- 400 类错误走模型验证管道,500 才进
UseExceptionHandler - 若需统一封装 400 响应,改
services.Configure<apibehavioroptions>(...)</apibehavioroptions>里的InvalidModelStateResponseFactory - 自定义
ExceptionFilter可捕获控制器内throw new ArgumentException()等,但它和中间件是两套机制,不能互相替代










