ASP.NET Core 中间件和过滤器的区别
核心概念先明确
在看流程前,需先区分两者的核心差异,避免混淆:
维度 | 中间件(Middleware) | 过滤器(Filter) |
---|---|---|
作用层级 | 全局请求管道(HTTP 生命周期) | 控制器/动作方法层级(MVC 框架内部) |
处理时机 | 早于过滤器(请求进入管道即触发) | 晚于中间件(路由匹配后、控制器执行前后触发) |
核心职责 | 处理 HTTP 上下文(如日志、认证、静态文件) | 处理 MVC 上下文(如权限校验、模型验证、异常) |
依赖框架 | 依赖 ASP.NET Core 基础管道,与 MVC 无关 | 依赖 MVC/Razor Pages 框架 |
关键节点拆解(避免踩坑)
-
中间件的“双向性”
中间件按“正向顺序执行(请求阶段)→ 反向顺序执行(响应阶段)”,例如:
日志中间件(正向)→ 认证中间件(正向)→ 路由中间件(正向)→ 控制器执行 → 路由中间件(反向)→ 认证中间件(反向)→ 日志中间件(反向)
若某中间件未调用await _next(context)
(未传递请求给下一个中间件),则管道会“短路”,直接进入反向流程。 -
过滤器的“层级优先级”
过滤器支持 全局、控制器、动作方法 三个层级,执行顺序为:
全局过滤器 → 控制器过滤器 → 动作方法过滤器
(如全局授权过滤器先于控制器授权过滤器执行)。 -
异常处理的“覆盖关系”
- 过滤器异常(如授权失败、模型验证失败):优先由 异常过滤器 处理,处理后直接返回响应,不进入后续中间件的“正向流程”。
- 中间件异常(如认证失败、静态文件不存在):由 异常处理中间件(如
UseExceptionHandler
)处理,早于过滤器触发。
下面给你一个能直接跑起来的 ASP.NET Core 6 最小 API 案例,把“中间件”和“过滤器”两种扩展点串在一起。
代码结构 = 两段自定义中间件 + 一段 MVC Endpoint + 四类常用过滤器。
运行时观察控制台,就能直观看到请求在中间件与过滤器之间的完整穿梭顺序。
────────────────────
1️⃣ 新建空白项目(.NET 6)
dotnet new web -n MiddlewareFilterDemo
cd MiddlewareFilterDemo
────────────────────
2️⃣ 把 Program.cs 整个替换为下面内容即可运行
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
var builder = WebApplication.CreateBuilder(args);
// 为了演示过滤器,我们显式启用 MVC
builder.Services.AddControllers();
var app = builder.Build();
// ┌────────────── 自定义中间件 1 ──────────────┐
app.Use(async (ctx, next) =>
{
Console.WriteLine("[Middleware-1] 进入 —— 在 Routing 之前");
await next();
Console.WriteLine("[Middleware-1] 离开 —— 响应已发出");
});
// ┌────────────── 自定义中间件 2 ──────────────┐
app.Use(async (ctx, next) =>
{
Console.WriteLine("[Middleware-2] 进入 —— 在 UseRouting 之后");
await next();
Console.WriteLine("[Middleware-2] 离开 —— 响应即将回到 Middleware-1");
});
// 标准中间件
app.UseRouting();
app.UseAuthorization(); // 为了演示授权过滤器
app.MapControllers(); // 把请求交给 MVC
app.Run();
────────────────────
3️⃣ 新建 Controllers/DemoController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace MiddlewareFilterDemo.Controllers;
[ApiController]
[Route("api/[controller]")]
public class DemoController : ControllerBase
{
[HttpGet]
[CustomResource]
[CustomAction]
[CustomResult]
[CustomAuth]
public string Get()
{
Console.WriteLine(" → 正在执行 Controller Action");
return "Hello ASP.NET Core!";
}
}
// 以下 4 个过滤器仅打日志,方便观察顺序
public class CustomAuthAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context) =>
Console.WriteLine(" [Filter] Authorization —— 授权检查");
}
public class CustomResourceAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext _) =>
Console.WriteLine(" [Filter] Resource —— 执行前");
public void OnResourceExecuted(ResourceExecutedContext _) =>
Console.WriteLine(" [Filter] Resource —— 执行后");
}
public class CustomActionAttribute : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext _) =>
Console.WriteLine(" [Filter] Action —— 执行前");
public void OnActionExecuted(ActionExecutedContext _) =>
Console.WriteLine(" [Filter] Action —— 执行后");
}
public class CustomResultAttribute : Attribute, IResultFilter
{
public void OnResultExecuting(ResultExecutingContext _) =>
Console.WriteLine(" [Filter] Result —— 执行前");
public void OnResultExecuted(ResultExecutedContext _) =>
Console.WriteLine(" [Filter] Result —— 执行后");
}
────────────────────
4️⃣ 运行 & 调用
dotnet run
# 浏览器或 Postman 访问 https://localhost:5001/api/demo
────────────────────
5️⃣ 控制台输出(顺序即管线)
[Middleware-1] 进入 —— 在 Routing 之前
[Middleware-2] 进入 —— 在 UseRouting 之后
[Filter] Authorization —— 授权检查
[Filter] Resource —— 执行前
[Filter] Action —— 执行前
→ 正在执行 Controller Action
[Filter] Action —— 执行后
[Filter] Result —— 执行前
[Filter] Result —— 执行后
[Filter] Resource —— 执行后
[Middleware-2] 离开 —— 响应即将回到 Middleware-1
[Middleware-1] 离开 —— 响应已发出
────────────────────
6️⃣ 总结(流程图文字版)
Client
│
▼
[Middleware-1] ——> [Middleware-2] ——> Routing/Endpoint Selection
│
▼
┌──────────────────────────┐
│ MVC Filter Pipeline │
│ Authorization │
│ Resource (before) │
│ Action (before) │
│ Controller.Action() │
│ Action (after) │
│ Result (before) │
│ Result (after) │
│ Resource (after) │
└──────────────────────────┘
│
▼
[Middleware-2] ——> [Middleware-1] ——> Client
通过这份“最小但完整”的代码,你可以:
- 把中间件想象成“洋葱”外层,所有请求/响应都要经过。
- 把过滤器想象成 MVC 内部的“切面”,只对命中 Endpoint 的请求生效。
改两行日志、下个断点,就能亲手验证官方文档里说的顺序。