ASP.NET Core Middleware
ASP.NET Core Middleware(中间件)本质上是基于“责任链模式”构建的 HTTP 请求处理管道。每个中间件可以:
- 在请求进入时执行逻辑;
- 决定是否调用下一个中间件;
- 在下游处理结束后执行响应逻辑;
- 直接返回响应,短路整个管道。
1. 洋葱模型
请求
↓
Middleware A:进入
↓
Middleware B:进入
↓
Endpoint / Controller
↑
Middleware B:退出
↑
Middleware A:退出
↑
响应
示例:
app.Use(async (context, next) =>
{
Console.WriteLine("A Before");
await next(context);
Console.WriteLine("A After");
});
app.Use(async (context, next) =>
{
Console.WriteLine("B Before");
await next(context);
Console.WriteLine("B After");
});
app.Run(async context =>
{
await context.Response.WriteAsync("Hello");
});
输出顺序:
A Before
B Before
B After
A After
这就是中间件最核心的“洋葱模型”。
2. Use、Run 和 Map
Use:继续调用后续中间件
app.Use(async (context, next) =>
{
// 请求前逻辑
await next(context);
// 响应后逻辑
});
如 果不调用 next,管道就会被短路:
app.Use(async (context, next) =>
{
if (!context.Request.Headers.ContainsKey("X-Api-Key"))
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
await context.Response.WriteAsync("Missing API key");
return;
}
await next(context);
});
Run:终结中间件
Run 不包含 next,通常放在管道末尾:
app.Run(async context =>
{
await context.Response.WriteAsync("Fallback response");
});
执行到这里后,不会再继续处理。
Map:按路径创建分支
app.Map("/admin", adminApp =>
{
adminApp.Use(async (context, next) =>
{
Console.WriteLine("Admin middleware");
await next(context);
});
adminApp.Run(async context =>
{
await context.Response.WriteAsync("Admin area");
});
});
只有 /admin 路径下的请求才会进入这个分支。
也可以按任意条件分支:
app.MapWhen(
context => context.Request.Query.ContainsKey("debug"),
debugApp =>
{
debugApp.Run(async context =>
{
await context.Response.WriteAsync("Debug mode");
});
});
3. 自定义 Middleware 类模式
推荐将复杂逻辑封装成独立类:
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: {Method} {Path}",
context.Request.Method,
context.Request.Path);
await _next(context);
_logger.LogInformation(
"Response: {StatusCode}",
context.Response.StatusCode);
}
}
注册扩展方法:
public static class RequestLoggingMiddlewareExtensions
{
public static IApplicationBuilder UseRequestLogging(
this IApplicationBuilder app)
{
return app.UseMiddleware<RequestLoggingMiddleware>();
}
}
使用:
app.UseRequestLogging();
传统 Middleware 通常在应用启动时实例化一次,因此不要通过构造函数直接注入 Scoped 服务。需要 Scoped 服务时,可以注入到 InvokeAsync:
public async Task InvokeAsync(
HttpContext context,
MyScopedService service)
{
await service.ProcessAsync();
await _next(context);
}
4. IMiddleware 模式
如果中间件本身需要按请求创建,或者希望直接通过依赖注入管理生命周期,可以实现 IMiddleware:
public class AuditMiddleware : IMiddleware
{
private readonly MyScopedService _service;
public AuditMiddleware(MyScopedService service)
{
_service = service;
}
public async Task InvokeAsync(
HttpContext context,
RequestDelegate next)
{
await _service.RecordAsync(context);
await next(context);
}
}
注册:
builder.Services.AddScoped<AuditMiddleware>();
使用:
app.UseMiddleware<AuditMiddleware>();
两种模式的区别:
| 模式 | next 的位置 | 生命周期 |
|---|---|---|
| 传统 Middleware | 构造函数 | 通常应用级实例 |
IMiddleware | InvokeAsync 参数 | 由 DI 控制,可使用 Scoped |
5. 中间件顺序非常重要
典型配置:
var app = builder.Build();
app.UseExceptionHandler("/error");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
关键规则:
- 异常处理中间件应靠前,才能捕获后续组件抛出的异常。
- 静态文件通常放在认证授权之前,提高性能。
UseAuthentication()必须在UseAuthorization()前。- CORS 通常位于路由之后、认证授权之前。
- Endpoint 映射放在相关认证、授权配置之后。
- 顺序写反时,代码可能正常编译,但运行行为会错误。
6. 常见应用场景
Middleware 适合处理所有请求共有的横切逻辑,例如:
- 全局异常处理;
- 请求日志和性能统计;
- 身份认证和授权;
- CORS;
- 静态文件;
- 请求追踪 ID;
- 限流;
- 多租户识别;
- 请求头检查;
- 响应头设置;
- 请求或响应内容修改。
如果逻辑只属于某个 Controller 或 Action,通常使用 MVC Filter 更合适;如果逻辑需要覆盖整个 HTTP 管道,包括静态文件和非 MVC Endpoint,则使用 Middleware。
一句话概括:
Middleware 是一个有顺序、可嵌套、可短路、可分支的异步 HTTP 责任链。