跳到主要内容

ASP.NET Core Middleware

ASP.NET Core Middleware(中间件)本质上是基于“责任链模式”构建的 HTTP 请求处理管道。每个中间件可以:

  1. 在请求进入时执行逻辑;
  2. 决定是否调用下一个中间件;
  3. 在下游处理结束后执行响应逻辑;
  4. 直接返回响应,短路整个管道。

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构造函数通常应用级实例
IMiddlewareInvokeAsync 参数由 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 责任链。