中间件管道由app.use()等按代码顺序拼成委托链,顺序错误或漏await next()会导致请求静默卡住;注册顺序直接决定逻辑生效与否,如useauthentication必须在useauthorization之前。

中间件管道不是“配置出来”的,而是靠 app.Use()、app.UseMiddleware<t>()</t> 和 app.Map() 按代码顺序拼出来的委托链;漏 await next(context)、顺序错、或在 Map 分支里误用 next,请求就静默卡住,不报错也不响应。
中间件注册顺序为什么直接决定逻辑是否生效
ASP.NET Core 不校验合理性,只机械执行代码中 app.UseXXX() 的排列。顺序一错,逻辑就失效,而且往往没有异常提示。
-
app.UseAuthentication()必须在app.UseAuthorization()之前,否则context.User.Identity.IsAuthenticated始终为false -
app.UseExceptionHandler()要放在所有可能抛异常的中间件(如 JWT 验证、自定义日志)之前,否则捕获不到 -
app.UseStaticFiles()若放在app.UseRouting()之后但没加app.UseEndpoints(),静态文件请求会被路由中间件拦截并返回 404 - 调试时可在每个中间件开头加
Console.WriteLine($"[{nameof(MyMiddleware)}] enter"),只看到enter没exit,基本就是顺序或await next()问题
InvokeAsync 方法签名必须严格匹配
框架靠反射找入口方法,任何偏差都会抛 InvalidOperationException: No public Invoke or InvokeAsync method found。
- 方法名必须是
InvokeAsync(不能是Invoke或Handle) - 返回类型必须是
public async Task(即使内部没await,也不能用void或同步Task) - 第一个参数必须是
HttpContext context,不能缺、不能换名、不能多参 - 后续参数(如
ILogger<mymiddleware></mymiddleware>)由 DI 自动注入,但只能出现在InvokeAsync参数列表里,不能塞进构造函数以外的其他位置
Map 和 UseWhen 的分支行为差异很大
它们都改写委托链,但作用域和执行流隔离程度完全不同,混用会导致预期外的短路或跳过。
-
Map("/api", ...):路径前缀匹配后进入子管道,**主管道剩余中间件不再执行**;子管道内的next指向该子管道末端,不是主管道 -
MapWhen(ctx => ctx.Request.Headers.ContainsKey("X-Debug"), ...):任意条件 + 子管道,同样隔离执行流,适合按 Header、Query 或策略分流 -
UseWhen(ctx => ctx.Request.Path.StartsWithSegments("/health"), ...):条件为 true 时才进入该中间件分支,但**不阻断主链**——后续中间件仍会执行 - 性能注意:
MapWhen的 predicate 每次请求都执行,避免在里面做 I/O 或复杂计算;UseWhen更适合轻量守门(如开关日志)
await next(context) 漏掉就等于删掉整个后续管道
中间件默认不自动调用下一个。漏 await、写成 next();、或提前 return,后续所有中间件(包括终结点)都不会执行——HTTP 响应可能已部分写出,客户端卡住或收到不完整响应。
- 常见误写:
next();→ 编译通过,但next是Task,不await就跳过,等同于短路 - 在
Map分支里调用主管道的next是无效的,因为该next已被重绑定到子管道末端 - 想显式结束响应(如鉴权失败),应设
context.Response.StatusCode = 401+await context.Response.WriteAsync(...),而不是 throw 新异常 - 读取请求体前必须先调用
context.Request.EnableBuffering()并重置context.Request.Body.Position = 0,否则后续中间件读不到
最易被忽略的其实是 Map 分支内 next 的作用域边界——它和主管道完全无关,但多数人凭直觉以为能“继续往下走”。这种认知偏差导致大量线上请求静默失败,且日志里找不到痕迹。










