惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog RSS Feed
量子位
Y
Y Combinator Blog
大猫的无限游戏
大猫的无限游戏
B
Blog
U
Unit 42
C
Check Point Blog
I
InfoQ
aimingoo的专栏
aimingoo的专栏
雷峰网
雷峰网
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 【当耐特】
人人都是产品经理
人人都是产品经理
The Cloudflare Blog
H
Help Net Security
MongoDB | Blog
MongoDB | Blog
博客园 - Franky
H
Hackread – Cybersecurity News, Data Breaches, AI and More
J
Java Code Geeks
Microsoft Azure Blog
Microsoft Azure Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
云风的 BLOG
云风的 BLOG
宝玉的分享
宝玉的分享
爱范儿
爱范儿

博客园 - Albert_MIN

在 ASP.NET Core MVC 中,接收数据的几种方式 Content-Type 对应不同的前端数据结构与构造方式 Revit二次开发 钢筋生成API(二) Revit二次开发 钢筋生成API(一) revit二次开发 钢筋布置方式 revit二次开发之 钢筋功能详细分析 revit 二次开发之收集器、过滤器和选择器 Revit Server的注意要配置说明 WCF文件配置服务 WCF服务的各种绑定 Revit二次开发之 对象的隐藏与显示 Revit二次开发之 GeometryObject分析 Revit二次开发之 Material 分析 Revit二次开发之 PolymeshTopology Revit 二次开发之 图纸的导出 Revit开发之 IExportContext接口详细 JavaScript 困惑之 ArrayBuffer JS 困惑之this的指向 Revit二次开发之 族的创建 Revit二次开发 钢筋生成 Revit二次开发之 尺寸标线(二) Revit二次开发之 尺寸标线
在 ASP.NET Core 中创建中间件的五种方式
Albert_MIN · 2026-09-09 · via 博客园 - Albert_MIN

在 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

终端

有限

✅ 是

兜底/终点