ASP.NET Core自定义中间件需严格遵循InvokeAsync签名、正确await_next(context)、精准注册顺序;否则请求卡死或静默失效。

ASP.NET Core 自定义中间件不是“写个类就能用”,关键在 InvokeAsync 是否被正确识别、await _next(context) 是否漏掉、以及注册位置是否破坏管道顺序——任一出错,请求就卡死、状态码错乱或静默失效。
InvokeAsync 方法签名必须严格匹配
框架只认 public Task InvokeAsync(HttpContext context) 这一种签名。常见错误包括:
- 写成
Invoke(同步方法)→ 运行时报InvalidOperationException: No public Invoke or InvokeAsync method found - 返回
void或Task<T>→ 编译不报错但实际被跳过 - 参数名改了(比如叫
ctx)或加了额外参数 →UseMiddleware<T>()找不到入口,直接抛异常 - 没加
async修饰符 → 即使内部没 await,也必须声明为 async Task,否则 DI 解析失败
UseMiddleware() 和 Use() 的注册方式不能混用
两者底层行为完全不同,选错会导致依赖注入失效或上下文提前释放:
-
app.UseMiddleware<MyMiddleware>():走 DI 容器,支持构造函数注入单例服务(如ILogger<MyMiddleware>),但作用域服务(如IDbContextFactory<AppDbContext>)**必须放在InvokeAsync参数里**,否则跨请求复用引发并发异常 -
app.Use((context, next) => { ... }):纯委托,无 DI 注入能力;若需日志,得手动从app.Services拿,且next必须写成await next(context),写成next()或next(context)会跳过后续管道 - 别在
Use里 new 中间件实例——它不参与生命周期管理,Dispose不会被调用
await _next(context) 漏掉就等于删掉整个后续管道
这是最隐蔽也最致命的坑。中间件不自动调用下一个,漏掉这句,UseRouting、UseEndpoints 全部失效,客户端可能收不到响应或卡住:
- 错误写法:
next(context);(没 await)→ 编译通过,但等同于短路 - 调试技巧:在
InvokeAsync开头和结尾加Console.WriteLine,只看到 “enter” 没 “exit”,基本就是这里断了 - 读取
context.Response.StatusCode必须在await _next(context)之后;修改它必须在之前,否则抛InvalidOperationException: Headers are read-only - 短路逻辑(如鉴权失败)要显式
return或throw,避免误执行await _next(context)
中间件顺序错一位,StatusCode 或 Body 就全错
管道是纯顺序执行,框架不做合理性校验。顺序错了,不是报错,而是逻辑静默错乱:
-
app.UseAuthentication()必须在app.UseAuthorization()之前,否则User.Identity.IsAuthenticated始终为false - 想记录最终响应状态码的日志中间件,必须注册在
app.UseAuthorization()之后、app.UseEndpoints()之前;放太前会打印默认200,放太后果然没机会执行 -
app.UseStaticFiles()放在 MVC 中间件后面,静态资源请求会被路由匹配走,压根不会进静态文件中间件 -
app.UseExceptionHandler()要放在所有可能抛异常的中间件之前(比如 JWT 验证、自定义参数绑定),否则捕获不到
真正难的不是写中间件,而是理解它在管道里的“时间点”——它既在请求进入时执行一次,又在响应发出前再执行一次,而 await _next(context) 就是那个分水岭。跨过它之前改响应头,跨过它之后读响应体,漏掉它,就什么都没了。


















