前言
表现层(Ordering.API)
1.表现层职责
| 职责 | 说明 |
|---|---|
| 1️⃣ 用户交互接口 | 提供给用户或外部系统的访问入口,如 Web API、MVC Controller、Razor 页面、Blazor 页面等。 |
| 2️⃣ 请求验证 | 对输入数据进行基础校验,如必填字段、格式校验,可调用应用层验证器或 FluentValidation。 |
| 3️⃣ 调用应用层服务 | 将用户请求转化为应用层命令(Command)或查询(Query),调用应用层服务完成业务逻辑。 |
| 4️⃣ 数据映射与转换 | 将应用层 DTO 或领域对象映射为前端视图模型(ViewModel)或 API 输出对象(Response DTO)。 |
| 5️⃣ 错误处理与响应统一 | 捕获异常,返回统一格式的错误响应(如 API 状态码、错误消息),保证前端可处理。 |
| 6️⃣ 安全与权限控制 | 校验请求是否有访问权限,可结合认证(Authentication)和授权(Authorization)框架。 |
| 7️⃣ 用户体验处理 | 控制视图渲染、分页、排序、过滤、数据格式化、HTTP 状态码管理等,保证前端用户体验。 |
| 8️⃣ 请求日志与监控 | 收集请求日志、性能指标,支持系统监控和审计(可结合中间件或 AOP 实现)。 |
2.表现层设计原则
| 原则 | 说明 |
|---|---|
| 1️⃣ 职责单一(Single Responsibility) | 表现层只负责展示和请求转发,不包含业务规则。 |
| 2️⃣ 调用应用层而非领域层 | 不直接操作领域对象或执行业务逻辑,通过应用层服务协调业务。 |
| 3️⃣ 输入输出隔离(Input/Output Isolation) | 使用 DTO、ViewModel、Command/Query 对象,避免暴露领域模型。 |
| 4️⃣ 错误统一处理(Centralized Error Handling) | 异常通过统一中间件或过滤器处理,保持 API 或界面响应一致性。 |
| 5️⃣ 安全性优先(Security First) | 表现层是访问入口,应执行认证、授权、CSRF/XSS 等安全检查。 |
| 6️⃣ 可测试性(Testable) | 控制器/页面应易于单元测试和集成测试,避免紧耦合到外部依赖。 |
| 7️⃣ 可观察性(Observability) | 日志、监控、指标收集必须集中,支持审计和故障排查。 |
3.表现层组成架构
| 组成 | 作用 | 示例 |
|---|---|---|
| 1️⃣ 控制器(Controller) | 处理 HTTP 请求,调用应用层服务,返回响应(API 或视图)。 | OrdersApi |
| 2️⃣ 页面 / 视图(View) | 渲染数据给前端用户界面,负责展示层逻辑。 | Razor Pages、Blazor 页面、MVC View |
| 3️⃣ 视图模型(ViewModel) | 用于在表现层和前端之间传输数据,映射应用层 DTO 或领域对象。 | OrderSummary |
| 4️⃣ API DTO / 请求对象 | 封装前端请求数据(Command/Query),与应用层交互。 | CreateOrderRequest |
| 5️⃣ 中间件(Middleware) | 处理请求管道中的公共逻辑,如异常捕获、日志、授权、CORS 等。 | LoggingBehavior |
| 6️⃣ 过滤器(Filter) | 针对 MVC 或 Web API 的特定请求执行操作,如授权、验证或缓存。 | [Authorize]、RequireAuthorization() |
| 7️⃣ 前端交互组件(可选) | SPA 或 Blazor 组件负责 UI 交互逻辑。 | OrderListComponent、UserProfileComponent |
4.Minimal API
MapOrdersApiV1为订单服务定义了一组基于 Minimal API 的 v1 版本 REST 接口路由,包括取消、发货、查询、创建订单等操作,并统一挂载在 /api/orders 路径下。
Ordering.API是AspNetCore WebAPI项目,只提供接口,没有页面

5.视图模型(ViewModel)
视图模型(ViewModel / DTO)是表现层与外部世界(HTTP 客户端、UI、API 客户端)之间的契约,职责是序列化/反序列化、输入校验、权限隔离与减少耦合——绝不包含业务规则。
以根据订单Id查询订单(GetOrderAsync)为例,查询完成后返回Order对象

但这个Order不是聚合根实体,而是单独封装返回前端的视图模型(ViewModel)

6.API DTO / 请求对象
API DTO(请求对象)是一种轻量、结构化的模型,用来定义前端与后端交换数据的契约——负责序列化/反序列化、基本校验与敏感数据脱敏,将外部输入映射成应用层命令或查询,但不包含业务规则。
这里以创建订单(CreateOrderAsync)为例,前端传入了CreateOrderRequest参数,然后转换成CreateOrderCommand,再Mediator.Send发送后通过验证管道(ValidatorBehavior)验证参数

7.中间件(Middleware)
概念
中间件是 ASP.NET Core 请求处理管道(pipeline) 中的一段可插拔组件,接收 HttpContext、执行任意逻辑、并决定是否把请求交给管道中的下一个中间件。管道呈“洋葱模型”(请求顺序进入,响应倒序返回)。
调用签名核心是:RequestDelegate next -> Task Invoke(HttpContext context)。
中间件就是请求管道里的“拦路插件”,每个请求经过它,它可以拦截、修改、放行或终止请求。
7.1执行流程
-
注册顺序决定请求进入的顺序。
-
每个中间件要决定是否
await next(context)(放行)或不调用next(短路/终止)。 -
请求走入时是顺序;响应返回时是逆序(栈式回退)。
-
每个中间件既可以在请求前处理逻辑,也可以在请求后处理逻辑。
请求 -> Middleware1 -> Middleware2 -> Middleware3 -> Endpoint
响应 <- Middleware3 <- Middleware2 <- Middleware1

7.2实现方式
RequestDelegate 是中间件的核心,它接收一个 HttpContext,返回一个 Task。
中间件管道本质上是 RequestDelegate 链。
每个中间件通过调用 await next(context) 来传递请求。
public delegate Task RequestDelegate(HttpContext context);
7.2.1内联委托方式(Lambda/RequestDelegate)
直接在 Configure 中写 Lambda 表达式,RequestDelegate 链的最轻量写法。
特点:
- 快速、轻量
- 不可复用
- 适合简单逻辑、调试或快速原型
app.Use(async (context, next) =>
{Console.WriteLine("请求前逻辑");await next(); // 调用下一个中间件Console.WriteLine("响应后逻辑");
});
示例
在Program中通过委托建立中间件

请求http://localhost:5004/WeatherForecast接口,可以看到中间件执行逻辑
请求前逻辑
执行请求中...
响应后逻辑

7.2.2类中间件方式(RequestDelegate+Invoke/InvokeAsync)
自定义中间件类,通过构造函数注入 RequestDelegate next,实现 InvokeAsync(HttpContext) 方法。
特点:
- 灵活、可复用
- 支持依赖注入(构造函数注入服务)
- 最常用的自定义中间件实现方式
public class CustomMiddleware
{private readonly RequestDelegate _next;public CustomMiddleware(RequestDelegate next){_next = next;}public async Task InvokeAsync(HttpContext context){Console.WriteLine("CustomMiddleware请求前逻辑");await _next(context); // 调用下一个中间件Console.WriteLine("CustomMiddleware响应后逻辑");}
}
调用方式:app.UseMiddleware<MyMiddleware>();
示例
自定义中间件CustomMiddleware

请求接口,可以看到CustomMiddleware请求逻辑
委托中间件请求前逻辑
CustomMiddleware请求前逻辑
执行请求中...
CustomMiddleware响应后逻辑
委托中间件响应后逻辑

7.2.3接口(IMiddleware)
IMiddleware 是 ASP.NET Core 自带的接口化中间件实现方式,通过 DI 容器管理生命周期和依赖,简化传统中间件类的实现。
特点:
- 生命周期由 DI 管理(Transient/Scoped/Singleton)
- 不需要手动保存
_next - 支持复杂依赖注入,适合大型中间件
public interface IMiddleware
{Task InvokeAsync(HttpContext context, RequestDelegate next);
}public class MyMiddlewareWithDI : IMiddleware
{private readonly ILogger<MyMiddlewareWithDI> _logger;public MyMiddlewareWithDI(ILogger<MyMiddlewareWithDI> logger){_logger = logger;}public async Task InvokeAsync(HttpContext context, RequestDelegate next){_logger.LogInformation("请求前逻辑");await next(context); // 调用下一个中间件_logger.LogInformation("响应后逻辑");}
}
调用方式
// 先注入DI
services.AddTransient<MyMiddlewareWithDI>();
// 再注册中间件
app.UseMiddleware<MyMiddlewareWithDI>();
示例
新增CustomDIMiddleware实现IMiddleware

然后先通过AddTransient<CustomDIMiddleware>()注册到DI,后面再使用UseMiddleware注册中间件CustomDIMiddleware
委托中间件请求前逻辑
CustomMiddleware请求前逻辑
CustomDIMiddleware请求前逻辑
执行请求中...
CustomDIMiddleware响应后逻辑
CustomMiddleware响应后逻辑
委托中间件响应后逻辑

7.2.4RequestDelegate 工厂方式(高级方式)
将中间件逻辑封装成返回 RequestDelegate 的方法。动态生成中间件、框架内部使用。
特点:
- 高度灵活、可复用
- 可动态生成中间件
- 底层原理仍然是 RequestDelegate 链
public static class CustomMiddlewareFactory
{public static RequestDelegate CreateMiddleware(RequestDelegate next){return async context =>{Console.WriteLine("CustomMiddlewareFactory请求前逻辑");await next(context);Console.WriteLine("CustomMiddlewareFactory响应后逻辑");};}
}
调用方式
app.Use(CustomMiddlewareFactory.CreateMiddleware);
示例
新建自定义中间件工厂(CustomMiddlewareFactory),然后提供一个创建RequestDelegate的方法

然后使用Use方法调用,Use方法接收一个参数是RequestDelegate并且返回RequestDelegate的委托,其实和委托方式调用蕾西,这里只是封装起来了
委托中间件请求前逻辑
CustomMiddleware请求前逻辑
CustomDIMiddleware请求前逻辑
CustomMiddlewareFactory请求前逻辑
执行请求中...
CustomMiddlewareFactory响应后逻辑
CustomDIMiddleware响应后逻辑
CustomMiddleware响应后逻辑
委托中间件响应后逻辑

7.2.5内置扩展方法中间件(Use系列)
框架自带中间件封装,例如:
// 路由
app.UseRouting();
// 认证
app.UseAuthentication();
// 授权
app.UseAuthorization();
// 执行路由匹配后的请求处理
app.UseEndpoints(endpoints => { ... });
特点:
- 封装 RequestDelegate 或类中间件
- 使用方便,集成 MVC / Minimal API
- 灵活性略低
7.2.5总结
关于中间件的执行流程,这里调试一遍就很明显了:
中间件按注册顺序依次执行“请求前逻辑”,到达终点后再按相反顺序依次执行“响应后逻辑”,形成典型的洋葱模型(先进后出、后进先出)。
委托中间件请求前逻辑
CustomMiddleware请求前逻辑
CustomDIMiddleware请求前逻辑
CustomMiddlewareFactory请求前逻辑
执行请求中...
CustomMiddlewareFactory响应后逻辑
CustomDIMiddleware响应后逻辑
CustomMiddleware响应后逻辑
委托中间件响应后逻辑
总结
-
ASP.NET Core 中间件都是 RequestDelegate 链
-
_next指向下一个中间件,形成管道链 -
每个中间件可以在请求前/响应后处理逻辑
-
不同实现方式只是 封装形式、复用性、DI 生命周期管理 不同
| 实现方式 | 核心机制 | 调用方式 | 特点 |
|---|---|---|---|
| 内联委托 / Lambda | RequestDelegate | app.Use(...) |
快速、轻量,不可复用 |
| 类中间件 + Invoke | 类 + RequestDelegate | app.UseMiddleware<T>() |
灵活、可复用,支持依赖注入 |
| 实现 IMiddleware 接口 | IMiddleware.InvokeAsync | app.UseMiddleware<T>() + DI |
生命周期可控,支持复杂依赖 |
| RequestDelegate 工厂 | 返回 RequestDelegate | app.Use(...) |
高度灵活,可动态生成 |
| 内置扩展方法 | 框架封装 RequestDelegate | app.UseXxx() |
使用方便,集成 MVC / Minimal API |
7.3Use、Run、Map、UseWhen
-
Use:标准,多数中间件用await next()放行。 -
Run:终端中间件,不接next,写完后不会再调用后续中间件(短路)。 -
Map/MapWhen:基于路径或条件分支路由,创建子管道(可用于多态处理)。 -
UseWhen:条件中间件,满足条件时在子管道内执行next,不满足则略过。
| 方法 | 是否可放行 | 作用范围 | 匹配条件 | 常见用途 |
|---|---|---|---|---|
| Use | 可放行/短路 | 全局管道 | 无 | 日志、认证、异常处理、请求处理 |
| Run | 不放行 | 全局管道/终端 | 无 | 健康检查、默认响应、短路请求 |
| Map | 可放行/短路 | 子管道 | 基于路径 | 分模块处理不同 URL 分支 |
| UseWhen | 可放行/短路 | 子管道 | 任意条件 | 条件化中间件,如仅对 API 或特定 header 生效 |
7.4应用场景
-
全局异常捕获(
UseExceptionHandler或DeveloperExceptionPage) -
日志/请求追踪/CorrelationId(Inject TraceId)
-
身份认证与授权(JWT、Cookies)
-
CORS、RateLimiting、RequestSizeLimit
-
Static Files、Response Compression、Response Caching
-
HTTPS 强制、HSTS、Content-Security-Policy header 插入
-
Body 读取、请求重写(Rewrite)、路径重写(UsePathBase、RewriteOptions)
-
流量劫持(Proxy headers)、IP 限制
7.5总结
中间件是 ASP.NET Core 请求管道的插件
顺序和调用 next() 决定请求响应流程
Use 可放行,Run 终止,Map/UseWhen 创建子管道
常用于日志、异常、认证、CORS、限流、压缩等横切关注点
7.6eshop的中间件
eshop中没有单独封装中间件,而是使用了MediatR命令管道统一处理业务命令的日志、参数验证、管道。
中间件是AspNetCore中很重要的组成,eshop中没有封装使用,但我还是再次学习了一遍

8.过滤器(Filter)
8.1概念
过滤器(Filter)是 ASP.NET Core MVC 的一种 拦截器机制,可以在 Controller 或 Action 执行前后插入逻辑。
它类似中间件,但只作用于 MVC/Web API 请求(控制器和操作方法),不能拦截静态文件或 Minimal API。
通俗理解:中间件作用于整个请求管道,过滤器作用于“控制器动作”,是请求的局部“钩子”。
特点:
- 只作用于 MVC / API Controller(或者 Razor Pages)
- 可以处理横切关注点:认证、授权、日志、异常处理、缓存等
- 与中间件类似,但作用范围更细(仅限 MVC 请求)
8.2权限过滤器(Authorization Filter)
8.2.1概念
职责 / 时机
- 最先运行(在认证之后),用于授权判断(是否有权限访问)。
- 可短路:如果未通过授权,可直接设置
context.Result返回 401/403,后续 Filter/Action 不会执行。
接口
- 同步:
IAuthorizationFilter(OnAuthorization) - 异步:
IAsyncAuthorizationFilter(OnAuthorizationAsync) —— 推荐使用异步实现。
典型场景
- 检查用户是否认证/是否有角色/是否有某个 Claim。
- 实现自定义
[RequireRoles]、API Key 校验(需先认证)等。
8.2.2示例
8.2.2.1配置认证授权
注册认证服务,并指定默认认证方案为 "Bearer"。AddJwtBearer里的配置我们之前在注册认证(AuthenticationExtensions)的时候也了解过:发行者(Issuer)、接受者(Audience),剩下的就是token相关配置:过期、验证签名、签名密钥。那个是微服务有认证中心,这个是单体服务。
注册授权服务(配合 [Authorize]、角色验证等使用)

启用认证/授权中间件

8.2.2.2配置Scalar支持BearerAuth认证
自定义BearerSecuritySchemeTransformer添加BearerAuth认证方式

注册OpenApi,让后在OpenApi中注入Bearer认证方案

在开发环境中启用 Scalar UI(位于 /scalar/v1),禁用外部字体并把根路径 / 自动重定向到 Scalar 文档页面。

在launchSettings.json中配置 "launchBrowser": true,启动项目自动打开浏览器
启动项目,Scalar中Authentication就有了BearerAuth认证方案了

8.2.2.3生成Token
从配置读取密钥、发行者(Issuer)和受众(Audience),用用户ID与角色生成相应的声明,再使用HMAC-SHA256 对称加密算法签名,最终创建并返回一个有效期 1 小时的 JWT。

创建2个api:
Login:模拟登录获取tokenAdmin:模拟只有Admin用户才能访问

使用Scalar获取token

8.2.2.4自定义权限过滤器(RequireRolesAttribute)
RequireRolesAttribute 是一个可加在控制器或方法上的自定义授权特性,它通过实现 IAuthorizationFilter 在执行动作前验证用户是否已认证并包含指定角色,若无权限则返回 401 或 403 直接终止请求。

使用的时候在方法上加上特性(RequireRoles)和角色(Admin)

8.2.2.5测试
/api/User/login接口设置参数userId和role(区分大小写),发送请求获取token

在首页设置token,这样每次请求都会带上

请求api/User/admin授权通过即可

创建一个超级管理员的接口[RequireRoles("SuperAdmin")]

启动项目,不设置token,接口返回401,未认证
{"type": "https://tools.ietf.org/html/rfc9110#section-15.5.2","title": "Unauthorized","status": 401,"traceId": "00-96c74738e8cb1d4cbbd081d106e92fdc-beaeda2ae4e6b0d9-00"
}

如果还是角色还是admin,然后获取token后请求,那返回403,未授权
{"type": "https://httpstatuses.io/403","title": "Forbidden","status": 403,"traceId": "00-bcfb934c9ae3779654656e9f5ed64b62-1cb30ea7ea0ec0ea-00"
}

重新获取SuperAdmin的token

再次请求才是200,授权通过
{"message": "SuperAdmin 授权通过","userId": "1","role": "SuperAdmin"
}

8.2.3异步接口(IAsyncAuthorizationFilter)
同步接口 (IAuthorizationFilter)
- 执行时阻塞线程,直到方法执行完成
- 如果方法内有耗时 I/O(如数据库查询),线程会被占用
- 框架会自动把同步过滤器也包装到异步请求管道中,但本身还是阻塞
异步接口 (IAsyncAuthorizationFilter)
- 支持异步 I/O,执行过程中可以释放线程等待结果
- 对高并发、耗时操作非常友好
- 框架直接等待
Task完成,非阻塞执行
| 接口 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
IAuthorizationFilter |
简单、轻量、CPU-bound 快 | 阻塞线程,不适合异步 I/O | 只检查 Claims、角色或内存数据 |
IAsyncAuthorizationFilter |
可异步、释放线程、适合高并发 I/O | 代码稍复杂,需要 async/await |
数据库/缓存/远程服务授权验证 |
因为我们的例子没有数据库、缓存等异步操作,所以我们的例子使用的是同步;如果存在数据库、Redis可以使用异步接口(IAsyncAuthorizationFilter)进行优化
IAsyncAuthorizationFilter示例
比如说我们用户的角色需要去数据库查询,这时候就可以使用异步接口(IAsyncAuthorizationFilter)优化

8.2资源过滤器(Resource Filter)
8.2.1概念
Resource Filter 是 ASP.NET Core 五种过滤器之一,位于 Action 执行前后,可以处理请求和响应,是资源级别的过滤器。
接口
- OnResourceExecuting:Action 执行前,可短路请求(比如缓存命中、权限不足)
- OnResourceExecuted:Action 执行后,可处理结果(比如写缓存、记录日志)
特点
- 最早执行的过滤器之一,最晚离开
- 可以决定是否执行 Action
- 可以在执行前获取请求上下文,执行后处理结果
- 支持同步(
IResourceFilter)和异步(IAsyncResourceFilter)实现
典型应用场景
- 缓存:GET 请求结果缓存,减少重复执行 Action
- 日志:记录请求和响应信息、耗时统计
- 权限控制:基于用户角色或资源权限判断访问
- 资源初始化/释放:数据库连接、外部 API 初始化等
资源过滤器是 Action 执行前后操作的入口,可以短路请求或处理结果,是缓存、权限和日志等资源级操作的理想位置。
8.2.2示例
8.2.2.1自定义资源过滤器(ResponseCacheFilter)
创建ResponseCacheFilter,继承并实现IResourceFilter
依赖注入内存缓存(IMemoryCache)和自定义缓存选项

CacheOptions缓存配置项:
DurationSeconds:绝对过期时间SlidingExpirationSeconds:滑动过期时间

OnResourceExecuting在进入Action之前执行:
- 如果不是
Get请求,直接返回,只缓存查询接口的结果 - 生成缓存
key(URL + 参数 + SHA256) - 根据
key查询缓存,如果缓存命中,直接设置context.Result
在 ASP.NET Core 的资源过滤器中,只要在 OnResourceExecuting 设置 context.Result,框架就会短路执行管道,直接返回结果,不执行 Action。

OnResourceExecuted 在 Action 执行后触发:
- 只缓存
GET请求 - 只支持接口返回类型为
ContentResult、JsonResult、ObjectResult - 生成缓存
Key(URL + 参数 + SHA256) - 将接口的返回结果写入缓存,缓存策略:绝对过期 + 滑动过期
为什么只支持接口返回类型为
ContentResult、JsonResult、ObjectResult?
缓存需要明确知道如何获取响应内容:
类型 缓存内容获取方式 说明 ContentResultcontentResult.Content已经是字符串,可以直接缓存 JsonResultJsonSerializer.Serialize(jsonResult.Value)对象需要序列化成 JSON 字符串才能缓存 ObjectResultJsonSerializer.Serialize(objectResult.Value)通用对象返回,也需要序列化成字符串 如果是其它返回类型(比如
FileResult、RedirectResult、StatusCodeResult等),它们的内容不是简单字符串或对象,缓存起来比较复杂。所以示例中只处理了最常用的三种类型,确保缓存逻辑简单可靠。缓存策略:绝对过期 + 滑动过期?
类型 含义 示例 绝对过期(Absolute Expiration) 缓存项在固定时间后一定过期,不管是否访问 设置 60 秒,缓存 60 秒后自动失效 滑动过期(Sliding Expiration) 缓存项如果在指定时间内被访问,会延长过期时间 设置 30 秒,如果每 10 秒访问一次,缓存会延长 30 秒到下一次访问 两者可以组合使用:
- 绝对过期 防止缓存无限存在
- 滑动过期 保证热点数据被频繁访问时不会过早失效
总结
- 滑动过期延长的时间不会超过绝对过期时间
- 缓存必须在滑动过期时间内访问才能延长,否则就会因为滑动过期而过期
- 到达绝对过期时,缓存无条件失效
在绝对过期时间内,每次访问会延长缓存到“滑动过期时间”,但不会超过绝对过期;一旦到达绝对过期,缓存无条件失效。

缓存key组成:
URL- 参数
sha256算法
/Product/list|keyword=9|page=2|pageSize=5

分页参数必须通过 URL 查询字符串传入,默认值好像没有生效,request.Query获取不到page、pageSize,暂时也没有影响,但是留意下

8.2.2.2注册资源过滤器、缓存、配置
自定义过滤器 ResponseCacheFilter 依赖本地缓存 IMemoryCache 和配置选项 CacheOptions,三者均需通过依赖注入注册到容器中。

8.2.2.3创建测试接口
ProductController:
-
在
ProductController添加[ServiceFilter(typeof(ResponseCacheFilter))] -
模拟商品数据
Products -
GetListAsync:获取商品列表

8.2.2.4测试
启动项目,打开Scalar,第一次请求/Product/list,/Product/list会被写入缓存

第二次请求/Product/list,会直接读取缓存

过一分钟请求/Product/list,缓存过期,会重新写入缓存

如果请求参数不一致,也会重新写入缓存

8.2.3异步接口(IAsyncResourceFilter)
Resource Filter也有异步接口,如果需要数据库/缓存/远程服务授权验证,也可以使用异步接口
我这里也实现了一个异步接口的示例,支持分布式缓存,其他基本都一致

使用前注册,然后配置在Controller上,我这里已经测试过,就不详细看了
俩个接口基本的都差不多,同步简单、快速,异步支持高并发,正常开发环境可能异步多一些,但用法都差不多

8.3动作过滤器(Action Filter)
8.3.1概念
动作过滤器(Action Filter)*是 ASP.NET Core MVC 的一种*管道机制,用于在 Controller 的 Action 方法执行前或执行后插入自定义逻辑。
- 执行顺序:Authorization → Resource → Action → Exception → Result
- 动作过滤器位于 Action 阶段,专门拦截 Action 方法的执行。
- 可以操作:
- 请求参数(ActionExecutingContext)
- Action 执行结果(ActionExecutedContext)
作用:
- 日志记录(请求参数、执行耗时、响应结果)
- 权限检查(虽然也可以用 Authorization Filter,但部分自定义逻辑在 Action Filter 也可实现)
- 数据验证或修改
- 性能监控
- 缓存(比如缓存 Action 执行结果)
同步接口
OnActionExecuting:
- 在 Action 方法调用前触发
- 可以修改参数、取消执行(通过
context.Result = ...)
OnActionExecuted:
- Action 方法执行完成后触发
- 可以访问返回结果(Result)并修改或处理
public interface IActionFilter
{void OnActionExecuting(ActionExecutingContext context); // Action 执行前void OnActionExecuted(ActionExecutedContext context); // Action 执行后
}
异步接口
参数说明:
ActionExecutingContext context:Action 执行前的上下文ActionExecutionDelegate next:执行 Action 的委托,返回ActionExecutedContext
优点:
- 支持
await异步操作 - 避免阻塞线程,性能更好
public interface IAsyncActionFilter
{Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next);
}
生命周期
ControllerActionInvoker
│
├─ AuthorizationFilter
├─ ResourceFilter
├─ ActionFilter
│ ├─ OnActionExecuting
│ └─ Action方法执行
│ └─ OnActionExecuted
├─ ExceptionFilter
├─ ResultFilter
└─ 返回客户端
8.3.2示例
8.3.2.1自定义日志过滤器(ActionLoggingAsyncFilter)
这次我们使用异步接口IAsyncActionFilter:
- 继承
IAsyncActionFilter,实现OnActionExecutionAsync - 在
await next()前记录日志(请求参数)、开启计时,await next()就是执行Action

在await next()之后记录日志(响应结果)、结束计时,如果是异常则记录异常
同时是支持ObjectResult、JsonResult、ContentResult三种返回类型

这次使用全局注册,不用每个Controller单独使用特性,所有Controller都生效

8.3.2.2测试
请求/Product/list:
- 第一次请求会记录请求参数和耗时、响应结果
- 然后写入缓存
- 第二次请求会直接读取缓存(缓存未过期)
因为资源过滤器(缓存)在动作过滤器之前执行,如果有缓存会直接返回,不会往下执行

8.3.3同步接口(ActionLoggingFilter)
业务逻辑都一样,OnActionExecuting用于记录请求参数和开始计时
然后使用了context.HttpContext.Items存了Action开始执行时间
HttpContext.Items
HttpContext.Items是一个IDictionary<object, object>类型的集合,用于在同一次 HTTP 请求的生命周期内存储临时数据。特点:
- 1.生命周期短:只在当前请求期间有效,请求结束后自动释放。
- 2.类型不限:Key 可以是任意对象(常用 string 或 Type),Value 可以是任意对象。
- 3.线程安全:单个请求通常是一个线程处理,所以不需要额外同步。
作用:实现请求级别的数据传递,避免使用全局静态变量或依赖注入传递数据。

OnActionExecuted用于记录耗时、响应结果

然后全局注册,剩下的就自己测试了

8.4异常过滤器(Exception Filter)
8.4.1概念
异常过滤器(Exception Filter) 是 ASP.NET Core MVC 中用于 捕获 Action 执行期间未处理异常 的组件,属于 过滤器管道的最后一层保护机制。
它的目标是让系统在出现异常时:
- 不崩溃
- 不把内部错误曝露给客户端
- 统一返回格式一致的错误响应
- 记录日志或上报监控平台
异常过滤器只在 Action 执行阶段 捕获异常,不处理 Model 绑定阶段的异常(接口参数绑定的异常:FromBody、FromQuery、FromHeader...)。异常过滤器 = Action 执行中所有未捕获异常的兜底处理器。
执行顺序如下: Action 执行 → 如果抛异常 → 异常过滤器捕获 → 返回统一 JSON → 停止异常继续冒泡
同步接口(IExceptionFilter)
OnException
- 在 Action 执行或过滤器执行过程中 发生异常时触发
- 可以记录日志
- 可以统一包装异常为 JSON
- 可以通过
context.Result = ...返回自定义响应 - 设置
context.ExceptionHandled = true阻止异常继续抛出
public interface IExceptionFilter
{void OnException(ExceptionContext context); // 有异常时触发(同步)
}
异步接口(IAsyncExceptionFilter)
参数说明
ExceptionContext context:包含异常、HttpContext、Action 信息Task:异步执行,可await调用日志服务、数据库、远程接口等
优点
- 支持异步日志处理
- 不阻塞线程
- 性能更优,推荐现代项目使用
public interface IAsyncExceptionFilter
{Task OnExceptionAsync(ExceptionContext context); // 有异常时触发(异步)
}
异常过滤器是 ASP.NET Core MVC 中用于捕获 Action 执行期间异常的机制,提供统一错误响应、区分业务异常与系统异常、记录日志并防止异常向外冒泡,是 MVC 控制器层的专属“异常统一入口”。
8.4.2示例
8.4.2.1自定义全局异常过滤器(GlobalExceptionFilter)
自定义业务异常(BusinessException),用于区分系统异常和业务异常

GlobalExceptionFilter继承IExceptionFilter实现OnException(ExceptionContext context)方法

OnException:
-
1.获取请求路径和方法
-
2.如果是业务异常:记录状态码和错误消息
-
3.如果是系统异常:记录状态码和错误消息之后如果是生产环境也显示堆栈消息
为什么区分业务异常和系统异常?
区分业务异常和系统异常的主要原因是为了处理策略不同:
- 业务异常(BusinessException)
- 代表可预期的、与业务逻辑相关的错误,比如用户输入错误、库存不足、余额不足等。
- 处理方式:向前端返回明确的错误信息(友好提示),通常不需要记录堆栈,也不触发告警。
- 系统异常(SystemException / 未捕获异常)
- 代表不可预期的、底层系统或框架错误,如数据库连接失败、空引用、文件读取失败等。
- 处理方式:记录详细日志、堆栈信息,可能需要告警、排查问题,前端返回通用错误信息,避免泄露内部实现。
一句话总结:区分业务异常和系统异常,可以实现对用户友好提示、降低系统风险,并便于开发排查问题。

- 4.最后统一返回
JsonResult

8.4.2.2全局注册GlobalExceptionFilter
所有Controller都生效

注:我们之前使用ProblemDetails中间件,会统一返回异常格式,如果我们捕获到异常,然后自定义格式,这个就不生效了

8.4.2.3构建异常测试接口
创建2个接口:
GetError:返回系统异常GetBusinessError:返回业务异常

8.4.2.4测试
请求/Product/error,返回500,代表服务器错误

请求/Product/businesserror,返回400,代表业务错误

8.4.3异步接口(IAsyncExceptionFilter)
GlobalExceptionAsyncFilter和同步实现上没有什么区别,也是在OnExceptionAsync中实现,区分业务异常和系统异常

判断完业务异常和系统异常之后,构建统一的JsonResult
因为是异步方法,最后需要返回Task.CompletedTask

注册到容器

8.5结果过滤器(Result Filter)
8.5.1概念
结果过滤器用于在 Action 方法执行完成并准备产生响应(IActionResult) 前后运行逻辑。它能:
- 在结果执行前(
OnResultExecuting)拦截并修改/替换IActionResult(可短路,直接返回自定义结果)。 - 在结果执行后(
OnResultExecuted)读取执行结果、记录日志或修改响应流(若要修改响应内容通常需替换/捕获HttpContext.Response.Body)。
它适合做:响应缓存、统一包装(如统一 JSON 结构)、记录响应体、设置响应头、压缩、内容审计等。
ASP.NET Core 的过滤器管线顺序大致为(从前到后):
- 1.Authorization filters
- 2.Resource filters
- 3.Action filters
- 4.Exception filters
- 5.Result filters ← 这里
- 6.中间件环绕整个请求(Use...)
Result Filter 的两个时点:
- 执行前:
OnResultExecuting(ResultExecutingContext)/ 在异步中是ResultExecutingContext在调用await next()之前。 是 在 action 已产生IActionResult之后、写出之前 被调用的 —— 所以可以合法地读取并替换context.Result,这就是 Result Filter 用来包装/短路/修改响应的正确位置。 - 执行后:
OnResultExecuted(ResultExecutedContext)/ 在异步中ResultExecutedContext在await next()之后由返回值提供。是 写出之后的通知,只能读或记录,不能再改变context.Result(只读属性)。
常用接口:
IResultFilter:同步,方法OnResultExecuting(ResultExecutingContext)、OnResultExecuted(ResultExecutedContext)IAsyncResultFilter:异步,方法Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
8.5.2示例
8.5.2.1自定义结果过滤器(IResultFilter)
GlobalResultFilter继承IResultFilter,并实现OnResultExecuting(ResultExecutingContext context)方法
SensitiveFields:存储需要脱敏的字段
JsonOptions:统一Json配置,防止每次创建,提升性能

在HttpContext存储Stopwatch记录接口耗时
只处理ObjectResult、JsonResult、ContentResult三种返回结果,并获取响应数据

如果有返回数据:
-
如果是JSON文本(
JsonResult、ContentResult):json格式:JsonNode.Parse转换为JsonNode- 普通文本:
JsonValue.Create转换为JsonNode
-
如果是对象(
ObjectResult):先序列化为 JSON,再解析为JsonNode -
解析完
JsonNode,调用MaskJsonNode递归脱敏 -
如果异常记录日志和返回
null

统一构建response:
-
success:根据 statusCode 决定 success -
data:脱敏后的数据 -
message:根据 statusCode 决定 message -
traceId:ASP.NET Core 自动为 每个 HTTP 请求分配的全局唯一 ID(GUID 样式的字符串) -
最后统一设置
context.Result

响应结果写出之后记录日志并记录耗时

8.5.2.1构建测试接口
这俩个接口都是模拟用户的数据,存在敏感字段

启动项目,打开Scalar,请求/api/User/list,日志正常记录,响应数据也进行了脱敏

8.5.3异步接口(IAsyncResultFilter)
异步接口(IAsyncResultFilter)只有一个方法OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)

这部分代码跟同步接口一样:
- 处理
ObjectResult、JsonResult、ContentResult三种结果响应数据 - 记录耗时
- 脱敏
- 构建统一响应数据
- 设置
context.Result后直接返回前端数据了
在调用 await next() 之前:Action 已经执行并产生 IActionResult,但响应尚未写入客户端,此时可以修改或替换 context.Result。
在调用 await next() 之后:响应已经写入客户端,此时只能做日志、清理或监控,无法再修改 context.Result。

响应写入客户端后,日志记录响应数据和耗时

8.6总结
8.6.1五种过滤器
ASP.NET Core 过滤器分为 五类(按执行顺序):
| 类型 | 接口 | 异步接口 | 执行时机 | 作用 / 用途 |
|---|---|---|---|---|
| 权限过滤器 (Authorization Filter) | IAuthorizationFilter |
IAsyncAuthorizationFilter |
Action 执行前 | 校验用户是否有权限访问,例如 [Authorize]。可短路请求(不执行 Action)。 |
| 资源过滤器 (Resource Filter) | IResourceFilter |
IAsyncResourceFilter |
Action 执行前/后 | 可用于缓存、全局请求前处理或短路请求。OnResourceExecuting/Executed 包围 Action + Result 执行。 |
| 动作过滤器 (Action Filter) | IActionFilter |
IAsyncActionFilter |
Action 执行前/后 | 对 Action 方法入参、返回结果进行处理或包装。可短路 Action 执行(OnActionExecuting)。 |
| 异常过滤器 (Exception Filter) | IExceptionFilter |
IAsyncExceptionFilter |
Action 或结果执行异常时 | 捕获异常,可返回自定义结果或日志处理,不适合处理管道中间件异常。 |
| 结果过滤器 (Result Filter) | IResultFilter |
IAsyncResultFilter |
Action 执行完成、结果写出前/后 | 对 Action 返回的结果进行统一包装、脱敏、日志记录、缓存等处理。OnResultExecuting 可修改结果,OnResultExecuted 仅能记录。 |
8.6.2同步/异步接口
权限过滤器
- 同步接口 (
IAuthorizationFilter):void OnAuthorization(AuthorizationFilterContext context):同步执行授权逻辑,可短路请求。
- 异步接口 (
IAsyncAuthorizationFilter):Task OnAuthorizationAsync(AuthorizationFilterContext context):异步执行授权逻辑,可访问数据库/缓存。
资源过滤器
- 同步接口 (
IResourceFilter):void OnResourceExecuting(ResourceExecutingContext context):Action 执行前,可短路请求。void OnResourceExecuted(ResourceExecutedContext context):Action 和 Result 执行后,可做日志或清理。
- 异步接口 (
IAsyncResourceFilter):Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next):可在await next()前后分别执行逻辑,前:Action + Result 未执行,后:已执行完。
动作过滤器
- 同步接口 (
IActionFilter):void OnActionExecuting(ActionExecutingContext context):Action 执行前,可修改参数或短路请求。void OnActionExecuted(ActionExecutedContext context):Action 执行后,可修改返回值或做日志。
- 异步接口 (
IAsyncActionFilter):Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next):可在await next()前后分别处理逻辑。
异常过滤器
- 同步接口 (
IExceptionFilter):void OnException(ExceptionContext context):捕获 Action 或 Result 异常,可返回自定义结果。
- 异步接口 (
IAsyncExceptionFilter):Task OnExceptionAsync(ExceptionContext context):异步捕获异常,可做日志或调用服务。
结果过滤器
- 同步接口 (
IResultFilter):void OnResultExecuting(ResultExecutingContext context):Action 执行后、结果写回前,可修改结果。void OnResultExecuted(ResultExecutedContext context):结果写回后,只能做日志或监控。
- 异步接口 (
IAsyncResultFilter):Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next):可在await next()前修改结果,后记录日志。
8.6.3同步vs异步
同步接口(IAuthorizationFilter、IResourceFilter、IActionFilter、IExceptionFilter、IResultFilter)
使用场景:
- 过滤器内部逻辑非常简单,不需要访问数据库、缓存、远程服务或执行耗时 I/O。
- 执行非常快,可以直接在同步线程完成。
- 适合:参数验证、简单权限判断、日志记录、数据脱敏(在内存操作)。
优点:
- 实现简单,调用链直接,不需要
async/await。 - 对性能要求高、I/O 不密集的情况更高效。
异步接口(IAsyncAuthorizationFilter、IAsyncResourceFilter、IAsyncActionFilter、IAsyncExceptionFilter、IAsyncResultFilter)
使用场景:
- 需要访问数据库、缓存、Web API 或其他异步 I/O 操作。
- 过滤器逻辑可能耗时,需要避免阻塞线程池。
- 适合:异步权限验证(JWT、数据库角色检查)、缓存读取、异步日志写入、异步统计上报。
优点:
- 避免阻塞 ASP.NET Core 线程,提高吞吐量。
- 可以在
await next()前后处理逻辑,实现前后环绕操作。
简单规则
-
不做异步 I/O → 用同步接口
-
需要异步 I/O → 用异步接口
-
异步接口可以兼容同步逻辑(直接返回
Task.CompletedTask)。框架优先调用异步接口,同步接口被忽略。
📌 创作不易,感谢支持!
每一篇内容都凝聚了心血与热情,如果我的内容对您有帮助,欢迎请我喝杯咖啡☕,您的支持是我持续分享的最大动力!
💬 加入交流群(QQ群):576434538
