ASP.NET Core中间件执行顺序决定请求拦截效果:须在UseRouting()后、UseEndpoints()前注册,否则不执行;读取Request.Body需EnableBuffering并重置Position;修改Response须在HasStarted为false时操作;服务注入应在InvokeAsync中通过RequestServices获取。
中间件执行顺序错乱导致请求没被拦截
asp.net core 的中间件是按注册顺序串行执行的,usemiddleware<t>() 或 app.use(...) 的调用位置直接决定它是否能拿到请求、是否能影响后续流程。比如把日志中间件放在 app.userouting() 之后、app.useendpoints() 之前,才能看到路由解析后的 httpcontext.request.path;但如果放反了——比如写在 app.useendpoints() 后面,那它根本不会被执行。
常见错误现象:Console.WriteLine 没输出、断点不命中、HttpContext.Response 已完成却还想写入内容,报错 System.InvalidOperationException: Headers are read-only, response has already started。
- 检查
Program.cs(.NET 6+)或Startup.Configure()中中间件注册顺序,确保自定义中间件在UseRouting()之后、UseEndpoints()或UseAuthorization()之前(除非你明确要绕过授权) - 不要在中间件里直接 return;要用
await next();显式调用下一个中间件,否则链路中断 - 如果只想处理特定路径,别在中间件里硬写
if (context.Request.Path.StartsWithSegments("/api"))—— 改用MapWhen()更清晰,也避免干扰主链路
如何让中间件访问 HttpContext.Request.Body 并不破坏后续读取
HttpContext.Request.Body 是个只读流,且默认只能读一次。中间件里调用 ReadAsStringAsync() 或 CopyToAsync() 后,后续中间件(比如 MVC 的模型绑定)会发现 Body 已耗尽,导致 ModelBinding 失败、Request.Form 为空、Request.ReadFormAsync() 报错 System.InvalidOperationException: Form value count limit 1024 exceeded(其实是流已关闭)。
- 必须开启缓冲:在
Program.cs中配置WebApplicationOptions或调用app.Use((ctx, next) => { ctx.Request.EnableBuffering(); return next(); });(注意这行必须放在所有依赖 Body 的中间件之前) - 读完后重置位置:
context.Request.Body.Position = 0;,否则后续中间件仍读不到 - 生产环境慎用大 Body 缓冲,内存和性能代价明显;对 JSON 请求建议用
JsonSerializer.DeserializeAsync<T>(context.Request.Body)替代全文本读取
自定义中间件里怎么安全获取和修改 Response
中间件可以随时写 Response.StatusCode、Response.Headers,但一旦 Response.Body 开始写入(比如调用 WriteAsync()),就无法再改状态码或头信息。典型错误是:先写了一段 HTML,再想设 StatusCode = 401,结果抛出 System.InvalidOperationException: StatusCode cannot be set because the response has already started。
- 修改响应前,先检查
context.Response.HasStarted;为真则说明已发 headers 或 body,不能再动 StatusCode/Headers - 想统一加 CORS 或自定义 Header,优先用
app.UseCors()或app.Use((ctx, next) => { ctx.Response.Headers.Append("X-Trace-ID", Guid.NewGuid().ToString()); return next(); }),这类操作必须在next()前做 - 需要重写整个响应(如全局错误页),应在
try/catch中捕获异常,清空已启动的响应:context.Response.Clear(); context.Response.StatusCode = 500;,再写新内容
中间件 vs 过滤器(Filter)该选哪个
不是所有“拦截”需求都该用中间件。中间件运行在 HTTP 管道最底层,不感知 MVC、Controller、Action;过滤器则在 MVC 层,能访问 ActionDescriptor、ModelState、ControllerContext,还能注入服务、参与模型绑定。
- 需要处理跨域、压缩、日志、认证(如 JWT Bearer 验证)、请求体预处理 → 用中间件
- 需要校验 Action 参数、修改 ViewResult、捕获 Action 异常、动态跳转到其他 Action → 用
IActionFilter或IExceptionFilter - 中间件不能直接
return new JsonResult(...);过滤器可以调用context.Result = new JsonResult(...)终止后续执行 - 性能上,中间件更轻量;但若逻辑只针对 API Controller,用过滤器更精准,避免对静态文件、Swagger 等无意义路径执行
真正容易被忽略的是:中间件里拿不到 HttpContext.RequestServices.GetService<IMyService>() 返回 null——因为 RequestServices 在 UseRouting() 后才初始化。所以别在中间件构造函数里依赖注入服务,而要在 InvokeAsync 方法里通过 context.RequestServices 获取。










