在 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 服务(如 DbContextIUserService),框架自动解析。

优点

缺点

支持构造函数 + 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();
        });
    });

方法

是否短路

用途

Map

✅ 是

路径独立处理

MapWhen

✅ 是

条件独立处理

UseWhen

❌ 否

条件附加逻辑,继续管道


五、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(),管道到此结束。


七、对比总结表

 

方式

生命周期

注入支持

短路

适用场景

app.Use(lambda)

每次请求

有限

可选

快速逻辑、调试

约定类 InvokeAsync

每次 new

构造 + Invoke

可选

正式项目首选

IMiddleware

DI 控制

构造

可选

需要精确生命周期

Map / MapWhen

分支独立

同 Use

✅ 是

路径/条件独立处理

UseWhen

条件附加

同 Use

❌ 否

条件附加逻辑

IStartupFilter

启动注入

构造

可选

类库/组件

app.Run

终端

有限

✅ 是

兜底/终点

 

 
posted @ 2026-09-09 16:04  Albert_MIN  阅读(8)  评论(0)    收藏  举报