中间件必须注册在userouting()之后、useendpoints()或useauthorization()之前;否则因请求管道顺序执行特性而完全不生效。

中间件必须注册在 UseRouting() 之后、UseEndpoints() 或 UseAuthorization() 之前,否则它根本不会执行——这不是 bug,是设计使然。
为什么中间件注册顺序错就等于没写
ASP.NET Core 的请求管道是严格按 app.UseXXX() 调用顺序串起来的链表。中间件只会在“它被注册的位置”参与处理:注册在 UseRouting() 前,context.Request.RouteValues 还为空;注册在 UseEndpoints() 后,请求早已被路由匹配并交给了控制器,你的中间件压根收不到调用。
-
UseRouting()解析出RouteValues和终结点(Endpoint),这是后续授权、模型绑定等操作的前提 -
UseAuthentication()和UseAuthorization()依赖RouteValues判断是否需要检查权限,所以必须在UseRouting()之后 - 自定义中间件若需读取路由信息、访问
HttpContext.User或调用IHttpContextAccessor,也必须放在这两者之间 - 常见错误现象:
Console.WriteLine不输出、断点不命中、context.Request.Path是根路径但实际请求是/api/values
如何安全读取并复用 Request.Body
直接调用 ReadAsStringAsync() 或 CopyToAsync() 会耗尽流,导致后续中间件(如 MVC 模型绑定)报 System.InvalidOperationException: Form value count limit 1024 exceeded 或 Request.Form 为空。
- 必须提前启用缓冲:
app.Use((ctx, next) => { ctx.Request.EnableBuffering(); return next(); });—— 这行要放在所有依赖 Body 的中间件之前 - 读完后务必重置位置:
context.Request.Body.Position = 0;,否则后续中间件仍读不到原始内容 - 生产环境避免全文本读取大 Body;对 JSON 请求,优先用
JsonSerializer.DeserializeAsync<t>(context.Request.Body)</t> - 不要在中间件里调用
context.Request.ReadFormAsync(),它内部已做缓冲,重复启用会出错
InvokeAsync 里漏掉 await next(context) 的后果
这句不是可选的“礼貌调用”,而是决定整个管道是否继续的关键开关。漏掉它,等于把后续所有中间件(包括 UseRouting、UseEndpoints)全部跳过,请求卡死或返回空响应。
-
await next(context)是请求阶段和响应阶段的分水岭:它之前是“上游逻辑”,之后是“下游逻辑”(响应处理) - 不能写
return、不能throw、不能return Task.CompletedTask替代——必须显式 await 下一个委托 - 如果中间件要短路(比如未登录直接返回 401),必须自己写
context.Response.StatusCode = 401并return,且不调用next - 异步方法签名必须是
public async Task InvokeAsync(HttpContext context),写成Invoke或返回void会导致编译失败或运行时被跳过
什么时候该用 MapWhen 而不是 if 判断路径
在中间件里硬写 if (context.Request.Path.StartsWithSegments("/api")) 看似简单,实则破坏管道清晰性,还容易因条件判断遗漏导致逻辑错乱。
-
app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/api"), branch => branch.UseMyApiMiddleware())显式创建分支管道,主链路不受干扰 -
MapWhen内部自动隔离作用域,分支中注册的中间件不会影响其他路径 - 适合 API 版本隔离(
/v1/vs/v2/)、灰度流量分流、静态资源专用处理等场景 - 分支内仍需遵守顺序规则:分支里的
UseRouting()和UseEndpoints()也要配对出现
最容易被忽略的是:中间件中的 HttpContext 生命周期仅限于 InvokeAsync 执行期间,跨 await 后不能再缓存或传递整个 HttpContext 实例;要用 context.Items 存临时数据,且注意键名冲突。










