ASP.NET Core请求处理管道由Middleware按注册顺序串联,通过next.Invoke()传递请求;UseExceptionHandler必须在UseRouting之后才能捕获路由等后续异常;HttpContext仅在InvokeAsync内有效,跨异步阶段需用Items传递数据。

ASP.NET Core请求处理管道是怎么串起来的
请求进来后不是直接进控制器,而是先流经一连串 Middleware。每个中间件决定是否把请求“往下传”,靠的是调用 next.Invoke() —— 不调它,后续中间件和终点(如 Controller)就完全收不到这个请求。
注册顺序就是执行顺序:在 Program.cs 中 app.UseXXX() 的先后,决定了它们在管道中的位置。比如 app.UseAuthentication() 必须在 app.UseAuthorization() 之前,否则 HttpContext.User 还没设好,授权逻辑就拿不到身份信息。
常见误区是把 app.MapControllers() 放太前面。它本质是“最后兜底”的路由分发中间件,一旦放在某个 UseXXX 之前,那些本该拦截或修饰请求的中间件(如日志、CORS、异常处理)就对 API 请求失效了。
并发模型依赖于 .NET 的异步 I/O 和线程池协作
ASP.NET Core 默认不为每个请求独占一个线程。它用 ThreadPool 处理 CPU-bound 工作,但绝大多数请求生命周期(如等待数据库响应、HTTP 调用、文件读写)走的是操作系统级异步 I/O(IOCP),不消耗线程。
这意味着你能轻松支撑数千并发连接,但前提是代码真正异步:所有 async 方法都必须到底层驱动(如 HttpClient.GetAsync()、EFCore.SaveChangesAsync()),而不是用 .Result 或 .Wait() 阻塞线程。
-
await后续代码可能在不同线程上恢复(取决于 SynchronizationContext,但在 ASP.NET Core 中默认无捕获) - 不要在
async void方法里处理请求逻辑 —— 异常会直接崩掉整个应用域 - 共享状态(如静态变量、
HttpContext.Items以外的全局缓存)必须考虑并发读写,ConcurrentDictionary比Dictionary安全,但也要注意迭代时的快照语义
为什么 UseExceptionHandler 一定要放在 UseRouting 之后
UseExceptionHandler 是个“异常捕获中间件”,但它只对它之后注册的中间件抛出的异常生效。如果它放在 UseRouting 前面,连路由匹配失败(如 404)都捕获不到,因为 UseRouting 自己抛的异常根本不会流经它。
典型错误配置:
app.UseExceptionHandler(); // ❌ 太早,捕获不到路由、认证等环节异常 app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(...);
正确顺序:
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseExceptionHandler("/error"); // ✅ 在路由之后、终结点之前
app.UseEndpoints(...);另外注意:/error 路由对应的 endpoint 必须是同步的(或明确标记为 [HttpGet]),否则异常处理自身再抛异常会导致循环崩溃。
中间件里访问 HttpContext 的边界在哪
HttpContext 只在当前请求的中间件执行上下文中有效。一旦离开 Invoke 或 InvokeAsync 方法体,或者把它存到后台任务、静态字段、线程池回调里,大概率会遇到 ObjectDisposedException 或空引用 —— 因为请求生命周期已结束,上下文被释放了。
安全做法:
- 只在
InvokeAsync参数中接收并使用HttpContext - 需要跨异步阶段传递数据,用
HttpContext.Items(请求级字典,随请求销毁) - 要记录日志或埋点,提取关键字段(如
Request.Path、Response.StatusCode)立即拷贝,别存整个HttpContext - 绝对不要在
Task.Run(() => { /* 访问 HttpContext */ })里操作它
最隐蔽的坑是 Entity Framework Core 的 DbContext 实例 —— 它通常注册为 Scoped,生命周期绑定到 HttpContext。一旦你在中间件外启动新线程或 Task,再试图用这个 DbContext,就会触发 InvalidOperationException: Cannot resolve scoped service...。










