中间件管道是按注册顺序拼接的链式委托流,顺序错误、漏await next()或不当短路会导致静默失效;函数式适合简单无依赖场景,类式支持依赖注入;Map创建隔离分支,next不回主流程。

中间件管道不是“创建出来”的,而是靠 app.Use()、app.UseMiddleware() 和 app.Map() 这些注册动作**按顺序拼接出来的链式委托流**。你写错顺序、漏掉 await next()、或在不该短路的地方短路,管道就断了——不是报错,而是静默失效。
中间件注册顺序决定执行逻辑,不是可选项
ASP.NET Core 不会校验你注册的中间件是否合理,它只机械地按代码顺序执行。比如:
-
app.UseAuthentication()必须在app.UseAuthorization()之前,否则User.Identity.IsAuthenticated还是false,授权直接失败 -
app.UseExceptionHandler()要放在所有可能抛异常的中间件之前(比如自定义日志、JWT 验证),否则捕获不到 - 静态文件中间件
app.UseStaticFiles()放太后面,会导致请求先被 MVC 或 API 中间件拦截,压根没机会找文件
函数式中间件 vs 类式中间件:什么时候该用哪个
函数式写法适合一次性、无依赖、逻辑简单的场景;类式写法才能享受构造注入和生命周期管理。别为了“看起来高级”硬套类式。
- 函数式:
app.Use(async (context, next) => { await context.Response.WriteAsync("hi"); await next(); });—— 没状态、不依赖服务、调试时一眼看到全部逻辑 - 类式:必须带
RequestDelegate构造参数,且InvokeAsync方法签名不能改,否则UseMiddleware<t>()</t>会找不到入口 - 类式中间件要注册为服务(如
services.AddTransient<custommiddleware>()</custommiddleware>),否则UseMiddleware会因解析失败而抛InvalidOperationException
为什么 await next() 漏了就“没反应”
中间件默认不自动调用下一个。漏掉 await next() 就等于主动短路,后续所有中间件(包括终结点)都不会执行,但 HTTP 响应可能已部分写出,导致客户端卡住或收到不完整响应。
- 常见误写:
next();(忘了await)→ 编译通过,但next是Task,不 await 就跳过,等同于短路 - 调试技巧:在中间件开头加
Console.WriteLine($"[{nameof(CustomMiddleware)}] enter"),结尾加Console.WriteLine($"[{nameof(CustomMiddleware)}] exit"),看是否成对出现 - 如果只看到 “enter” 没看到 “exit”,基本就是
await next()被跳过、异常未捕获、或提前return了
Map 分支管道里,next 的含义变了
app.Map("/api", branch => { ... }) 内部的 branch 是一个独立的 IApplicationBuilder,里面的 Use 注册的是子管道。这里 next 指向的是该分支内的下一个中间件,不是主管道的后续。
- 分支内调用
next()不会回到主管道,主管道在进入Map后就暂停了 - 分支管道末尾没
Run()或没匹配到终结点,请求会“掉出”分支,但不会自动回主管道继续——它就结束了,返回 404 - 想让分支处理完再走主流程?不行。
Map是隔离的,需要复用逻辑请抽成服务,别指望管道穿透
真正难的不是写中间件,是判断「这个逻辑该不该放中间件里」以及「它该插在哪儿」。很多看似“通用”的需求(比如统一响应包装),其实更适合放在控制器基类或 IApiResponseFormatter 这类更贴近业务层的扩展点上——中间件太底层,一旦写错,影响的是所有请求,且很难 debug。


















