全局异常拦截需分层处理:useexceptionhandler捕获整个中间件管道未处理异常,须置于userouting后、useendpoints前;iexceptionfilter仅处理controller action内抛出的异常,需设context.exceptionhandled=true;catch when是c#语言特性,与框架无关。

全局异常拦截不是“加个过滤器就完事”,得先分清异常发生在哪一层:是 HTTP 请求管道里未处理的崩溃,还是 MVC 控制器 Action 内部抛出的业务异常。两者机制不同、作用域不同、注册方式也不同,混用或错配会导致大量异常根本收不到。
UseExceptionHandler 是兜底中间件,必须放对位置
它捕获的是整个中间件管道中未被任何环节处理的异常,包括模型绑定失败、授权失败、控制器抛出后没人 catch 的异常,甚至 app.UseStaticFiles() 里出的问题——但前提是它注册在正确顺序上。
-
UseExceptionHandler必须在UseRouting()之后、UseEndpoints()(或UseAuthorization())之前调用,否则 404 或路由匹配失败时的异常会直接绕过它 - 传入路径如
"/error",对应 Controller 中一个不抛异常的 Action;该 Action 返回ObjectResult(如new JsonResult(...)),别返回ViewResult,否则 API 客户端拿不到 JSON - 它拿不到
ActionContext,没法按 Controller 名称或 Route 做差异化响应;适合统一错误页、标准ProblemDetails输出 - 后台任务(
IHostedService、Timer、Task.Run)里的异常完全不经过它,必须各自try/catch
IExceptionFilter 只管 Controller Action 内部
它只在 MVC 层生效:从 Action 方法开始执行,到返回 IActionResult 之前这段逻辑里抛出的异常才归它管。模型验证失败(ModelState.IsValid == false)、[ApiController] 自动触发的 400 响应,压根不会走到 OnException。
- 注册方式是
services.AddControllers().AddExceptionFilter<myfilter>()</myfilter>,别用AddMvc(),纯 API 项目引入视图引擎纯属冗余 -
OnException里必须设context.ExceptionHandled = true,否则即使你赋了context.Result,异常仍会继续上抛,最终被UseExceptionHandler拦住,导致响应重复或失效 - 可以访问
context.ActionDescriptor和context.HttpContext,适合按 Action 特征做日志增强、补充请求 ID、局部覆盖响应结构 - 别在
OnException里throw新异常,这会触发二次崩溃,且可能跳过你刚设的Result
catch (Exception ex) when (...) 是语言级语法,跟框架无关
这是 C# 6.0 起的原生特性,写在任意 try/catch 块里都生效,不依赖 ASP.NET Core,也不走任何过滤器注册流程。CLR 在匹配阶段就求值 when 表达式,为 false 就直接跳过整个 catch 块,不展开栈、不执行任何语句。
-
when后只能是纯表达式:支持属性访问(ex.StatusCode)、方法调用(ex.Message.Contains("timeout"))、配置读取(Configuration["Retry:Enabled"]),但禁止foreach、using、或可能抛异常的调用(如JsonSerializer.Deserialize) - 它比
catch + if + throw更安全:不破坏原始堆栈,性能更高,且避免了throw后丢失上下文的问题 - 和
IExceptionFilter完全不是一回事——你在 Service 层手动写的try/catch用when,和你在 Startup 里注册的全局过滤器,是两条独立路径
最容易被忽略的一点:异常是否进入过滤器,取决于它“在哪一层抛出”以及“有没有被更早的环节吞掉”。比如 [ApiController] 对模型验证失败的自动处理,会让 IExceptionFilter 彻底失能;而 UseExceptionHandler 对非 HTTP 上下文的异常完全无感。别指望一个机制覆盖所有场景。











