asp.net core自定义中间件关键在invokeasync调用时机、_next是否正确await及注册位置,缺一将导致请求卡死或状态码错乱;必须声明public invokeasync(httpcontext),参数严格匹配,构造函数接收requestdelegate next,注册时优先usemiddleware以支持di注入。

直接说结论:ASP.NET Core 自定义中间件不是“写个类就行”,关键在 InvokeAsync 的调用时机、_next 是否被正确 await,以及注册位置是否破坏了管道逻辑。漏掉任一环,请求就卡死或状态码错乱。
中间件类必须实现 InvokeAsync 且接受 HttpContext
不能叫 Invoke(同步方法),也不能省略 async 修饰符——即使内部没 await,也要声明为 Task 返回值。否则 UseMiddleware<t>()</t> 会找不到匹配方法而抛出 InvalidOperationException: No public Invoke or InvokeAsync method found。
-
InvokeAsync必须是public,且参数严格为HttpContext context - 构造函数里必须接收并保存
RequestDelegate next,这是指向下一个中间件的委托 - 不调用
await _next(context)就等于“短路”,后续中间件(包括终点app.Run)全失效
app.UseMiddleware<t>()</t> 和 app.Use(...) 的注册差异
前者走 DI 容器解析,支持构造函数注入服务(如 ILogger<t></t>、IOptions<t></t>);后者是纯委托,所有依赖得手动 new 或从 app.Services 拿,容易绕过生命周期管理。
- 用
UseMiddleware时,中间件类构造函数参数会自动从 DI 解析,比如:public RequestLoggingMiddleware(RequestDelegate next, ILogger<requestloggingmiddleware> logger)</requestloggingmiddleware> - 用
Use匿名委托时,若要记录日志,得写成:app.Use((ctx, next) => { var logger = app.Services.GetRequiredService<ilogger>>(); ... })</ilogger> -
Use委托里不能直接 await 异步操作后还调用next(),必须用await next(context),否则上下文可能被释放
中间件顺序错了,context.Response.StatusCode 就是错的
比如你写了日志中间件想打印响应状态码,但把它注册在 app.UseAuthentication() 之后、app.UseAuthorization() 之前,那未授权请求会在 Authorization 中间件里被拦截并设为 401,而你的日志却在 401 之前执行,打印出的是初始的 200。
- 读取
Response.StatusCode必须在await _next(context)之后,否则它还是默认值 - 修改
Response.StatusCode必须在await _next(context)之前,否则响应头已发送,再改无效(会抛InvalidOperationException: Headers are read-only) - 调试时可加
Console.WriteLine($"Before next: {context.Response.StatusCode}")和Console.WriteLine($"After next: {context.Response.StatusCode}")对比验证
最容易被忽略的点:中间件类如果带泛型参数、静态字段或缓存了 HttpContext 实例,会在并发请求下引发状态污染——HttpContext 是 per-request 的,绝不能跨请求复用或长期持有。











