中间件是RequestDelegate链上的函数节点,执行顺序由UseXXX()注册顺序决定,需手动控制短路逻辑、正确await异步调用、在UseExceptionHandler前注册以捕获异常,并通过HttpContext.RequestServices获取scoped服务。

中间件不是“配置完就能用”的黑盒,它本质是 RequestDelegate 链上的函数节点,顺序错、短路没处理好、异步没 await,立刻 500 或请求卡死。
中间件的执行顺序和短路逻辑必须手动控制
ASP.NET Core 的中间件按 UseXXX() 注册顺序依次执行,但每个中间件都可能终止后续调用(即“短路”)。比如 UseAuthentication() 不会自动跳过未认证请求,它只是设置 HttpContext.User;真正拦截靠的是后续的 UseAuthorization() 或控制器上的 [Authorize]。
- 写自定义中间件时,务必在适当位置调用
next(HttpContext)—— 不调就短路,调太早可能绕过安全检查 - 异步中间件必须
await next(context),否则同步执行会阻塞 pipeline,尤其在高并发下容易堆积请求 -
Use()和UseMiddleware行为一致,但后者支持构造函数注入,() Use()写法更轻量,适合简单逻辑(如日志打点)
如何正确捕获并处理中间件中的异常
全局异常过滤器(app.UseExceptionHandler())只捕获未被中间件吞掉的异常。如果在中间件里 try-catch 了但没 re-throw,错误就静默丢失;如果 throw 了但没配 UseExceptionHandler,直接返回 500 并暴露堆栈(开发环境默认开启,生产环境不会)。
- 不要在中间件中用
try { ... } catch { response.StatusCode = 500; }粗暴处理——这会跳过UseExceptionHandler,丢失统一日志和响应格式 - 想记录异常又不中断流程?用
app.Use(async (ctx, next) => { try { await next(ctx); } catch (Exception ex) { _logger.LogError(ex, "Pipeline error"); throw; } }); -
UseExceptionHandler必须放在所有可能抛异常的中间件之前(通常紧挨着UseRouting()后),否则它根本收不到错误
中间件里访问 scoped 服务的正确姿势
中间件实例是 singleton 生命周期,不能直接在构造函数里注入 DbContext 或 IHttpClientFactory 这类 scoped 服务,否则会引发 InvalidOperationException: Cannot resolve scoped service...。
- 必须通过
HttpContext.RequestServices.GetService在每次请求中获取——这是唯一安全方式() - 别缓存
IServiceProvider或服务实例到字段里,scoped 服务生命周期只限当前请求 - 如果中间件逻辑复杂、依赖多,建议封装成独立服务,再通过
context.RequestServices.GetRequiredService调用,避免中间件类膨胀()
最常被忽略的一点:中间件的注册位置决定了它是否能看见路由信息或终结点元数据。比如想根据 EndpointMetadata 做权限预检,就必须放在 UseRouting() 之后、UseEndpoints() 之前;放反了,context.GetEndpoint() 返回 null。










