C#怎么使用中间件Middleware_C#如何拦截并处理Http请求【详解】

3次阅读

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.StatusCodeResponse.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 层,能访问 ActionDescriptorModelStateControllerContext,还能注入服务、参与模型绑定。

  • 需要处理跨域、压缩、日志、认证(如 JWT Bearer 验证)、请求体预处理 → 用中间件
  • 需要校验 Action 参数、修改 ViewResult、捕获 Action 异常、动态跳转到其他 Action → 用 IActionFilterIExceptionFilter
  • 中间件不能直接 return new JsonResult(……);过滤器可以调用 context.Result = new JsonResult(……) 终止后续执行
  • 性能上,中间件更轻量;但若逻辑只针对 API Controller,用过滤器更精准,避免对静态文件、Swagger 等无意义路径执行

真正容易被忽略的是:中间件里拿不到 HttpContext.RequestServices.GetService<IMyService>() 返回 null——因为 RequestServicesUseRouting() 后才初始化。所以别在中间件构造函数里依赖注入服务,而要在 InvokeAsync 方法里通过 context.RequestServices 获取。

星耀云
版权声明:本站原创文章,由 星耀云 2026-03-17发表,共计2564字。
转载说明:转载本网站任何内容,请按照转载方式正确书写本站原文地址。本站提供的一切软件、教程和内容信息仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。
text=ZqhQzanResources