在 ASP.NET Core 中创建中间件的五种方式
在 ASP.NET Core 中,创建中间件(Middleware)主要有 5 种方式,从最常用到最底层依次排列。下面按实战频率给你讲清楚每种的写法、适用场景和坑点。
一、app.Use() 内联 Lambda(最常用、最快速)
适合:简单逻辑、临时调试、少量代码
app.Use(async (context, next) => { // 请求进入时 Console.WriteLine($"Request: {context.Request.Path}"); await next(); // 调用下一个中间件 // 响应返回时 Console.WriteLine($"Response: {context.Response.StatusCode}"); });
|
优点 |
缺点 |
|---|---|
|
零定义、零注册,直接写 |
不能复用、不能注入复杂依赖 |
|
适合快速验证 |
逻辑多了 Program.cs 爆炸 |
⚠️ 注意:
app.Use()注册顺序 = 执行顺序(管道顺序)
二、约定类(Convention-based Middleware)—— 最推荐
适合:正式项目、需要注入服务、需要复用
约定规则:
- 构造函数接收
RequestDelegate next - 必须有
public async Task InvokeAsync(HttpContext context, [可选注入])
public class RequestLoggingMiddleware { private readonly RequestDelegate _next; private readonly ILogger<RequestLoggingMiddleware> _logger; public RequestLoggingMiddleware( RequestDelegate next, ILogger<RequestLoggingMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { _logger.LogInformation("Request: {Path}", context.Request.Path); await _next(context); // 继续管道 _logger.LogInformation("Response: {StatusCode}", context.Response.StatusCode); } }
// Program.cs
app.UseMiddleware<RequestLoggingMiddleware>();
// 或
app.UseRequestLoggingMiddleware(); // 如果有扩展方法
依赖注入:InvokeAsync 里可以注入 Scoped 服务(如 DbContext、IUserService),框架自动解析。
|
优点 |
缺点 |
|---|---|
|
支持构造函数 + Invoke 双重注入 |
不能按请求条件跳过自身 |
|
生命周期由 DI 管理 |
每次请求都 new(可通过 IMiddleware 接口优化) |
|
最常用、文档主推 |
— |
三、IMiddleware 接口(显式接口方式)
适合:需要精确控制中间件生命周期(单例)、或需要 Dispose
// Middleware/RequestLoggingMiddleware.cs public class RequestLoggingMiddleware : IMiddleware { private readonly ILogger<RequestLoggingMiddleware> _logger; public RequestLoggingMiddleware(ILogger<RequestLoggingMiddleware> logger) { _logger = logger; } public async Task InvokeAsync(HttpContext context, RequestDelegate next) { _logger.LogInformation("Request: {Path}", context.Request.Path); await next(); _logger.LogInformation("Response: {StatusCode}", context.Response.StatusCode); } }
注册(必须注册为 Scoped 或 Transient):
builder.Services.AddScoped<RequestLoggingMiddleware>();
app.UseMiddleware<RequestLoggingMiddleware>();
|
对比约定类 |
IMiddleware |
|---|---|
|
每次请求 new 实例 |
由 DI 控制生命周期 |
|
构造函数只能注入 Singleton |
可以注入 Scoped |
|
更轻量 |
更灵活、支持 Dispose |
⚠️ 坑:
IMiddleware方式不能在InvokeAsync里额外注入参数(只能HttpContext+RequestDelegate)
四、app.Map() / app.MapWhen() 分支管道
适合:特定路径/条件的独立处理逻辑(不走后续管道)
app.Map() —— 路径前缀匹配
app.Map("/health", handleHealth); static void handleHealth(IApplicationBuilder app) { app.Run(async context => { context.Response.ContentType = "text/plain"; await context.Response.WriteAsync("OK"); }); }
app.MapWhen() —— 任意条件分支
app.MapWhen(context => context.Request.Query.ContainsKey("debug"), handleDebug); static void handleDebug(IApplicationBuilder app) { app.Run(async context => { context.Response.ContentType = "text/html; charset=utf-8"; await context.Response.WriteAsync("<h1>Debug Mode</h1>"); }); }
app.UseWhen() —— 条件满足时加入中间件,但不短路
app.UseWhen( context => context.Request.Headers.ContainsKey("X-Custom-Header"), builder => { builder.Use(async (context, next) => { context.Response.Headers["X-Injected"] = "true"; await next(); }); });
|
方法 |
是否短路 |
用途 |
|---|---|---|
|
|
✅ 是 |
路径独立处理 |
|
|
✅ 是 |
条件独立处理 |
|
|
❌ 否 |
条件附加逻辑,继续管道 |
五、IStartupFilter(启动时注入管道头)
适合:类库/组件作者,想在管道最前面插入中间件
注册:
builder.Services.AddTransient<IStartupFilter, AutoRequestLogStartupFilter>();
|
优点 |
缺点 |
|---|---|
|
类库可以自动注册中间件 |
无法控制位置(在最前面) |
|
不用改 Program.cs |
调试困难、不直观 |
六、终端中间件(app.Run())
适合:终点处理(404、健康检查、兜底)
app.Run(async context => { context.Response.StatusCode = 404; await context.Response.WriteAsync("Not Found"); });
Run= 不调用next(),管道到此结束。
七、对比总结表
|
方式 |
生命周期 |
注入支持 |
短路 |
适用场景 |
|---|---|---|---|---|
|
|
每次请求 |
有限 |
可选 |
快速逻辑、调试 |
|
约定类 |
每次 new |
构造 + Invoke |
可选 |
正式项目首选 |
|
|
DI 控制 |
构造 |
可选 |
需要精确生命周期 |
|
|
分支独立 |
同 Use |
✅ 是 |
路径/条件独立处理 |
|
|
条件附加 |
同 Use |
❌ 否 |
条件附加逻辑 |
|
|
启动注入 |
构造 |
可选 |
类库/组件 |
|
|
终端 |
有限 |
✅ 是 |
兜底/终点 |

浙公网安备 33010602011771号