ASP.NET Core中间件本质是RequestDelegate链上的Func委托,按注册顺序执行,仅处理或转发HttpContext,不参与视图渲染;需正确启用请求体缓冲、路径过滤及依赖注入。

中间件本质就是 RequestDelegate 链上的一个委托
ASP.NET Core 的中间件不是类,也不是接口实现,它就是一个接收 HttpContext、返回 Task 的函数(更准确说是 Func),挂载在请求管道里按顺序执行。你写的“中间件类”,只是微软为组织代码提供的惯用写法,底层仍靠 Use 或 UseMiddleware 注入委托链。
常见错误是把中间件当成 MVC 控制器去写:加属性注入、搞复杂构造逻辑、试图在 Invoke 里 return View() —— 这些都错。中间件只负责处理或转发 HttpContext,不参与视图渲染、模型绑定等上层功能。
-
Invoke方法必须是public async Task,且第一个参数固定为HttpContext;第二个参数如果是RequestDelegate(即 next),说明你要调用下一个中间件,漏掉它就断链 - 不要在中间件里 throw 新异常来“拦截”请求,应该用
context.Response.StatusCode = 401+await context.Response.WriteAsync(...)显式结束响应 - 如果用了依赖注入(比如要记录日志),构造函数参数会被自动解析,但
Invoke方法本身不能有 DI 参数 —— 想用服务得从HttpContext.RequestServices拿,或改用InvokeAsync并把服务作为参数传入(推荐)
两种注册方式:Use 和 UseMiddleware 差在哪
Use 直接传匿名函数或方法组,适合简单逻辑(比如打印时间戳);UseMiddleware 则要求 T 是一个类,且含 Invoke 或 InvokeAsync 方法,支持构造函数注入,更适合复用和测试。
容易踩的坑是混用两者却忽略执行时机:Use 添加的中间件总是“短路型”的(即不调用 next 就结束),而 UseMiddleware 类里的 Invoke 如果忘了 await next(context),后续所有中间件(包括 MVC 路由)都会被跳过。
-
app.Use(async (context, next) => { await next(); });是最简形式,next是下一个RequestDelegate -
app.UseMiddleware要求() AuthMiddleware类构造函数可被 DI 容器解析,且InvokeAsync方法签名必须为public async Task InvokeAsync(HttpContext context, IMyService service)—— 服务参数会自动注入 - 顺序决定一切:
UseRouting必须在UseAuthentication之前,而自定义中间件的位置决定了它能否看到路由信息或用户身份
中间件里读取请求体为什么总为空
因为 HttpRequest.Body 默认是不可重放的 stream,一旦被某个中间件(如 MVC 模型绑定、JSON 输入格式化器)读过,再读就返回空。你在自定义中间件里想记录原始 JSON,就必须提前开启缓冲(buffering)并手动 rewind。
这不是 bug,是设计使然:避免重复读取大文件或流式请求时内存爆炸。但开发调试时很容易卡在这儿,报错可能是 System.InvalidOperationException: Synchronous operations are not permitted 或直接拿到空字符串。
- 必须在
Startup.ConfigureServices中调用services.AddControllers().AddJsonOptions(...)前,先配置services.Configure?不用。正确做法是在中间件内部启用缓冲:(options => { ... }) context.Request.EnableBuffering(); - 启用后记得
context.Request.Body.Position = 0;,否则还是从末尾开始读 - 读完别忘了
context.Request.Body.Position = 0;再交给下游 —— 否则后续中间件(如控制器)会读不到数据 - 缓冲会把整个请求体加载进内存,生产环境慎用大文件上传场景
如何让中间件只对特定路径生效
ASP.NET Core 没有内置“路径过滤中间件”的语法糖,所谓“只对 /api/ 生效”,其实是靠在中间件内部手动判断 context.Request.Path 并决定是否执行逻辑 + 是否调用 next。
有人想用 MapWhen,但它会新建子管道,导致 UseAuthentication 等全局中间件失效,除非你显式再注册一遍 —— 得不偿失。更稳妥的做法是守在主管道里做轻量判断。
- 用
if (context.Request.Path.StartsWithSegments("/api")) { /* 处理 */ } else { await next(); }最直接 - 避免用
string.Contains或正则匹配路径,性能差且易误判(比如/apiv2也被命中) - 如果逻辑复杂,建议封装成扩展方法,比如
context.IsApiRequest(),但别在里面做耗时操作(如 DB 查询) - 注意
Path不含查询字符串,要过滤带参数的 URL 得看context.Request.QueryString
真正难的不是写中间件,而是想清楚它该在管道哪个位置、是否需要短路、是否影响后续中间件对 HttpContext 的修改。这些细节不画 pipeline 图,光看代码很难理清。











