【ASP.NET CORE】 6. 中间件

本系列专栏基于杨中科老师的《ASP.NET Core技术内幕与项目实战》,本人记录梳理的学习笔记,有部分的增补和省略。更全面系统的讲解,请看杨老师的视频课:【.NET教程,.Net Core视频教程,杨中科主讲】

一、中间件概念

中间件是ASP.NET Core 请求处理 pipeline(请求管道) 的核心基石,是框架的灵魂。ASP.NET Core 中几乎所有功能都是基于中间件实现的:MVC、静态文件、响应缓存、身份认证、CORS、路由、Swagger、异常处理等,全部都是内置中间件。

1. 广义与狭义中间件

  • 广义中间件:独立于业务逻辑、为应用提供通用服务的组件,比如 Tomcat、WebLogic、Redis、IIS、Nginx 等;
  • 狭义中间件 :专指 ASP.NET Core 请求管道中处理 HTTP 请求 / 响应的组件

2. 中间件的执行模型

每个标准中间件都由三部分构成:

  1. 前逻辑:请求进入时,最先执行的代码(处理请求、解析参数、校验等);
  2. next 委托:调用下一个中间件,将请求传递下去;
  3. 后逻辑 :后续中间件执行完毕后,回溯执行的代码(处理响应、记录日志、修改返回值等)。

整个流程:HTTP 请求 → 中间件 1(前逻辑)→ 中间件 2(前逻辑)→ ... → 核心业务逻辑 → ... → 中间件 2(后逻辑)→ 中间件 1(后逻辑)→ HTTP 响应返回客户端。

3. 中间件管道模型

中间件按照注册顺序串联成一个管道 ,请求和响应在管道中顺序进入、逆序返回 。开发人员可以自由增删、排序、自定义中间件,实现高度灵活的请求处理。

4. 三大核心配置方法

ASP.NET Core 提供三个关键方法配置中间件管道:

(1)Run

  • 终止型中间件,没有 next,执行完后直接返回响应;
  • 作为管道的终点,执行最终业务逻辑。

(2)Use

  • 串联型中间件,可以调用 next,将请求传递给下一个中间件;
  • 用于编写自定义处理逻辑。

(3)Map / MapWhen

  • 分支型方法,根据请求路径 / 条件拆分管道
  • 满足条件则进入分支管道,不满足则继续主管道。

注意:Use 串联、Run 终止、Map 分支

二、自定义中间件

中间件代码简单时可以用匿名方法,复杂 / 复用场景必须封装为独立中间件类

1. 中间件类规范

中间件类是普通 C# 类,无需继承 / 实现接口,但必须满足:

  1. 构造方法:必须包含 RequestDelegate next 参数(指向下一个中间件);
  2. 执行方法:必须名为 InvokeInvokeAsync
  3. 方法参数:必须包含 HttpContext(当前请求上下文);
  4. 返回值:必须是 Task(支持异步)。

注意:构造方法和 Invoke 中的其他参数,会自动从依赖注入(DI) 中获取。

2. 示例

校验密码 + 自动解析请求 JSON 并存入上下文,供后续使用。

复制代码
/// <summary>
/// 请求校验与JSON解析中间件
/// </summary>
public class CheckAndParsingMiddleware
{
    private readonly RequestDelegate _next;

    // 构造函数:注入下一个中间件委托
    public CheckAndParsingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    // 核心执行方法(异步)
    public async Task InvokeAsync(HttpContext context)
    {
        // 前逻辑:校验密码
        string pwd = context.Request.Query["password"];
        if (pwd != "123")
        {
            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
            return;
        }

        // 前逻辑:如果是JSON请求,自动解析并存入 Context.Items
        if (context.Request.HasJsonContentType())
        {
            using var reader = new StreamReader(context.Request.Body);
            string jsonBody = await reader.ReadToEndAsync();
            // 这里可替换为 System.Text.Json 解析
            dynamic? jsonObj = System.Text.Json.JsonSerializer.Deserialize<dynamic>(jsonBody);
            context.Items["BodyJson"] = jsonObj;
        }

        // 调用下一个中间件
        await _next(context);

        // 后逻辑:可在这里统一处理响应(本案例无需后处理)
    }
}

3. 注册与使用中间件

复制代码
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// 分支管道:仅 /test 路径生效
app.Map("/test", appBuilder =>
{
    // 注册自定义中间件
    appBuilder.UseMiddleware<CheckAndParsingMiddleware>();
    
    // 最终执行逻辑
    appBuilder.Run(async ctx =>
    {
        ctx.Response.ContentType = "text/plain";
        dynamic? jsonObj = ctx.Items["BodyJson"];
        
        if (jsonObj == null)
        {
            await ctx.Response.WriteAsync("未传递JSON数据");
            return;
        }

        int i = jsonObj.i;
        int j = jsonObj.j;
        await ctx.Response.WriteAsync($"{i} + {j} = {i + j}");
    });
});

app.Run();

测试方式 :POST 请求 http://localhost:5000/test?password=123 Body 传递 {"i":10,"j":20}返回:10 + 20 = 30

三、实战案例 1:自定义迷你 Web API 中间件

模仿ASP.NET Core MVC,手写极简版 Web API 管道,理解框架底层原理。(代码见杨老师github)

管道设计

3 个中间件串联

  1. MyStaticFilesMiddleware:静态文件中间件 → 处理 wwwroot 静态资源
  2. MyWebAPIMiddleware:API 处理中间件 → 匹配路由、执行业务逻辑
  3. NotFoundMiddleware:404 中间件 → 所有请求未处理时返回未找到

核心思路

  • 静态文件中间件优先,找到文件则直接返回,终止管道;
  • 否则进入 API 中间件,匹配路径则处理请求;
  • 都不匹配则进入 404 中间件。

四、实战案例 2:Markdown 转 HTML 中间件

访问 .md 文件,自动在服务端转为 HTML 返回。

功能说明

  1. 请求 /doc/readme.md
  2. 中间件读取服务器对应的 md 文件;
  3. 使用 Markdig 库将 Markdown 转为 HTML;
  4. 直接返回网页,无需前端渲染。

部分代码

复制代码
public async Task InvokeAsync(HttpContext context)
{
    if (context.Request.Path.Value.EndsWith(".md"))
    {
        string mdPath = Path.Combine(webRootPath, context.Request.Path.Value.TrimStart('/'));
        if (File.Exists(mdPath))
        {
            string mdContent = await File.ReadAllTextAsync(mdPath);
            string html = Markdig.Markdown.ToHtml(mdContent);
            
            context.Response.ContentType = "text/html;charset=utf-8";
            await context.Response.WriteAsync(html);
            return;
        }
    }

    await _next(context);
}

五、中间件 Middleware vs 过滤器 Filter

对比项 中间件 Middleware 过滤器 Filter
作用范围 全局,处理所有请求 仅处理控制器 / Action 请求
执行阶段 最外层管道,早于 MVC 依赖 MVC 管道,属于 MVC 生命周期
依赖环境 无依赖,最底层 强依赖 MVC、Controller、Action 等对象
使用场景 日志、认证、跨域、缓存、静态文件 权限校验、参数验证、Action 拦截
控制粒度 粗粒度,全局通用 细粒度,可精确到 Action / 方法

区别总结

  1. 中间件是全局的,不管是不是 MVC 请求都会经过;
  2. Filter 是 MVC 专属的,只能拦截控制器动作;
  3. 中间件运行在更底层,无法使用 MVC 相关对象(如 Controller、ModelState、IActionResult);
  4. Filter 可以轻松获取Action 参数、返回值、控制器实例,中间件做不到。

选择建议

  • 优先使用中间件:通用功能、全局处理、不依赖 MVC;
  • 必须用 Filter:需要操作 MVC 相关对象、仅针对 API / 控制器生效。

总结

  1. 中间件是 ASP.NET Core 请求管道的基本单元 ;执行模型:前逻辑 → next → 后逻辑 ,请求顺序进,响应逆序回;三大方法:Use 串联、Run 终止、Map 分支。
  2. 自定义中间件:依赖 RequestDelegate + InvokeAsync
  3. Middleware 全局通用,Filter MVC 专用,按需选择。
相关推荐
高铭杰2 小时前
Postgresql源码(152)Transaction Redo (RM_XACT_ID = 1)
数据库·postgresql·xact
Zzzzmo_2 小时前
【MySQL】视图
数据库·mysql
随机昵称_1234562 小时前
springboot导出带水印文字的xlsx
java·spring boot·后端
小马爱打代码2 小时前
SpringBoot + JVM 内存泄漏监控 + Heap Dump 自动采集:OOM 前自动预警并留存现场
jvm·spring boot·后端
就不掉头发2 小时前
Linux与数据库
linux·运维·数据库
Engineer邓祥浩2 小时前
JVM学习笔记(1) 总述
jvm·笔记·学习
Soofjan2 小时前
Go Map SwissTable Iter 迭代流程(源码笔记 7)
后端
Predestination王瀞潞2 小时前
基于 `SqlSession` 的事务手动管理机制
数据库·oracle·java-ee
李慕婉学姐2 小时前
Springboot传统文化服饰交流平台k79z52ic(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端