本系列专栏基于杨中科老师的《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. 中间件的执行模型
每个标准中间件都由三部分构成:
- 前逻辑:请求进入时,最先执行的代码(处理请求、解析参数、校验等);
- next 委托:调用下一个中间件,将请求传递下去;
- 后逻辑 :后续中间件执行完毕后,回溯执行的代码(处理响应、记录日志、修改返回值等)。
整个流程: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# 类,无需继承 / 实现接口,但必须满足:
- 构造方法:必须包含
RequestDelegate next参数(指向下一个中间件); - 执行方法:必须名为
Invoke或InvokeAsync; - 方法参数:必须包含
HttpContext(当前请求上下文); - 返回值:必须是
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 个中间件串联
- MyStaticFilesMiddleware:静态文件中间件 → 处理 wwwroot 静态资源
- MyWebAPIMiddleware:API 处理中间件 → 匹配路由、执行业务逻辑
- NotFoundMiddleware:404 中间件 → 所有请求未处理时返回未找到
核心思路
- 静态文件中间件优先,找到文件则直接返回,终止管道;
- 否则进入 API 中间件,匹配路径则处理请求;
- 都不匹配则进入 404 中间件。
四、实战案例 2:Markdown 转 HTML 中间件
访问 .md 文件,自动在服务端转为 HTML 返回。
功能说明
- 请求
/doc/readme.md; - 中间件读取服务器对应的 md 文件;
- 使用 Markdig 库将 Markdown 转为 HTML;
- 直接返回网页,无需前端渲染。
部分代码
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 / 方法 |
区别总结
- 中间件是全局的,不管是不是 MVC 请求都会经过;
- Filter 是 MVC 专属的,只能拦截控制器动作;
- 中间件运行在更底层,无法使用 MVC 相关对象(如 Controller、ModelState、IActionResult);
- Filter 可以轻松获取Action 参数、返回值、控制器实例,中间件做不到。
选择建议
- 优先使用中间件:通用功能、全局处理、不依赖 MVC;
- 必须用 Filter:需要操作 MVC 相关对象、仅针对 API / 控制器生效。
总结
- 中间件是 ASP.NET Core 请求管道的基本单元 ;执行模型:前逻辑 → next → 后逻辑 ,请求顺序进,响应逆序回;三大方法:
Use串联、Run终止、Map分支。 - 自定义中间件:依赖
RequestDelegate+InvokeAsync。 - Middleware 全局通用,Filter MVC 专用,按需选择。