如何理解 ASP.NET Core 请求生命周期 📅 2026/7/24 21:25:55 ASP.NET Core 的很多知识点——DI、Middleware、Filter、Routing、Controller、DbContext——单独看都不难。真正容易混的是它们到底是在请求的哪个阶段发生的如果把一次 HTTP 请求完整走一遍这些知识点就会自然串起来。GET /api/orders/1 Authorization: Bearer xxx记住这条主线Kestrel → HttpContext → Request Scope → Middleware → Routing → Endpoint → MVCController / Binding / Filter / Action→ Result → Response → Dispose Scope建议先阅读以下内容理解起来更加通顺如何理解 C#/.NET 的依赖注入与生命周期如何理解 ASP.NET Core 中间件如何理解 ASP.NET Core Filter请求进来Kestrel 和 HttpContextKestrel 监听端口解析 HTTP然后为这一次请求准备好HttpContext。它不是 Controller 创建的后面 Middleware、MVC、Filter 都围着它转。Request A → HttpContext A Request B → HttpContext BUser、Items、Request、Response都属于当前这次请求。请求结束以后不要再拿HttpContext做后台逻辑需要的话在请求里把数据拷出来。框架最终调用的入口可以抽象成publicdelegateTaskRequestDelegate(HttpContextcontext);应用启动时Middleware 被Build成一个大的RequestDelegateKestrel 每收到一个请求大致就是await application(httpContext)。后面说的 Middleware、Endpoint本质上都是在包装或调用RequestDelegate。进入管道Middleware 在干什么HttpContext就绪后请求进入 Middleware 管道。常见顺序可以理解为ExceptionHandler → StaticFiles → Routing → Authentication → Authorization → EndpointMiddlewareMiddleware 在 HTTP 层所有进应用的请求都会经过除非前面就短路返回。静态文件可能在这里直接返回全局异常、HTTPS、CORS 也适合放这一层。Middleware 和 MVC 里的 Filter 容易混Middleware 在管道前端管的是 HTTP 层所有请求都会经过Filter 在 MVC 内部只有进了 Controller 才会跑而且那时 Action 参数已经绑好了。日志、异常、CORS 放 Middleware校验某个 Action 的入参、改返回值放 Filter。Routing只选人不执行请求GET /api/orders/1走到UseRouting()时框架在启动时已登记好的 Endpoint 表里做匹配例如GET /api/orders/{id} → OrdersController.Get(int id)UseRouting不是开始执行 Controller——很多人把UseRouting和MapControllers当成一回事其实前者是每个请求在选目标后者是启动时把 Action 登记进 Endpoint 表。Routing 这一步只做一件事把匹配到的Endpoint挂到HttpContext上。Endpoint 不是 Controller 类本身而是路由、策略、执行方式打包在一起的一份描述RoutePattern — 怎么匹配 URL Metadata — Authorize、Cors 等策略 RequestDelegate — 匹配成功后由谁、怎么执行Routing 真正匹配的是 Endpoint而不是 Controller。自 Endpoint Routing 起框架的模型就是这样很多人仍习惯说“路由找到了 Controller”其实中间还隔着 Endpoint 这一层。[Authorize]会变成 Metadata。所以UseAuthentication写在 Routing 后面先知道访问的是哪个 Endpoint再认用户、再按 Endpoint 上的策略做授权读HttpContext.GetEndpoint()?.Metadata。执行 Endpoint进入 MVCRouting 之后EndpointMiddleware调用该 Endpoint 的RequestDelegate。对 Controller 来说这个委托会跳进 MVC由ControllerActionInvoker接手。UseRouting选中目标 EndpointMiddleware执行目标MapControllers()是在启动时把 Action 注册成 Endpoint不是每个请求再扫描一遍 Controller。MVC 里创建对象、绑参数、Filter、Action进入 MVC 后顺序大致是创建 ControllerDI 从当前 Request Scope 解析依赖 → Model Bindingroute/query/body → Action 参数、ModelState → Filter Pipeline → 执行 Action → 得到 IActionResultMVC 会通过当前请求的HttpContext.RequestServices创建 Controller并解析构造函数依赖。如果依赖的是 Scoped 服务例如DbContext整个请求期间共享同一实例请求结束随 Scope 统一释放。对本例Get(int id)绑定后id 1。此时 Action Filter 才能拿到ActionArguments和ModelState更早的 Middleware 还没有这些信息。Action 里若await _dbContext.Orders.FindAsync(id)I/O 未完成时线程可以去干别的但请求归属不变——HttpContext和 Request Scope 仍属于这次请求AsyncLocal等机制。await之后可能换了一条线程继续Scoped 也不会串到别的请求请求边界跟的是HttpContext不是某一条线程。return Ok(order) 之后还没写完响应returnOk(order);只是得到一个OkObjectResult状态码 要输出的对象。真正写入Response.Body在 Result 执行阶段ObjectResultExecutor → 内容协商 → OutputFormatter常见是 System.Text.Json→ Response.Body所以 Action 只负责决定返回什么真正把 JSON 写进Response.Body是后面的 Formatter 干的。很多人以为return Ok(order)那一刻响应已经发出去了其实还没有。IActionResult设计成接口Action 返回的是“结果对象”真正执行结果的是不同的ResultExecutor——因此 MVC 可以同一套流程支持 JSON、View、File、Redirect 等不同响应形式。Result Filter 包在这一段前后。请求结束管道返回与释放Result 写完后控制流沿 Middleware 的await next()往回走例如记耗时的中间件在next之后才打日志。响应发出后Request Scope 释放Scoped 服务 DisposeHttpContext不再可用线程回线程池。请求边界 HttpContext Request Scope 不是某一条线程把整个顺序再过一遍GET /api/orders/1 ↓ Kestrel创建 HttpContext准备 Request ScopeRequestServices ↓ Middleware异常、静态文件、… ↓ UseRouting → 匹配 EndpointPattern Metadata RequestDelegate ↓ Authentication → User 写入 HttpContext ↓ Authorization → 看 Endpoint Metadata User ↓ EndpointMiddleware → RequestDelegate → MVC ↓ 创建 Controller解析 Scoped 依赖如 DbContext ↓ Model Bindingid1 ↓ Filter → Action可 await 查库 ↓ return Ok(order) → ResultExecutor / Formatter → JSON ↓ Middleware 往回发 Response ↓ Dispose Scope一张表记住整个生命周期概念出现在哪一步要点RequestDelegate贯穿全程Middleware 和 Endpoint 最终都是委托调用HttpContext请求一进来的载体不属于线程属于这次请求RequestServices / Scoped管道运行期间DbContext 随请求创建和释放MiddlewareHttpContext 之后、MVC 之前管 HTTP不管 Action 参数EndpointRouting 匹配结果不是 Controller是 Pattern Metadata 委托FilterMVC 内部、Action 前后能拿绑定后的参数IActionResult / FormatterAction 返回之后Ok()不等于已写 JSONasync/awaitAction 或 Service 内不绑线程绑请求上下文生命周期里最容易搞混的几个问题问题一句话回答HttpContext 谁创建Kestrel 接收请求后由框架创建。Controller 谁创建MVC 通过 DI 创建。DbContext 什么时候创建第一次解析 Scoped 服务时。Routing 干什么匹配 Endpoint不执行 Action。EndpointMiddleware 干什么执行 Endpoint。Filter 为什么能拿到 ActionArgumentsModel Binding 已完成。return Ok()是否已经写响应没有只返回 IActionResult。Scoped 为什么不会串Scope 属于请求不属于线程。HttpContext 为什么 await 后还能拿到AsyncLocal 保存当前请求上下文。官方文档ASP.NET Core Fundamentals