前言
软件设计中,我们常常会提开放封闭原则,也就是说软件实体的类、模块、函数等应该对扩展开放,对修改关闭。当需要增加新功能时,可以通过新增代码来实现,而不是修改已有代码,一旦软件实体开发完成并通过测试,就不应再修改其源代码。
本文讨论的Filter(过滤器),微软官方文档里译作筛选器。正是应用了AOP的这种编程范式,实现开闭原则。将横切关注点抽离出来。当需要添加横切功能时,无需修改原有业务代码。保证原有逻辑的同时,更加便捷的扩展和管理横切功能。
一、Filter(过滤器)是什么
在ASP.NET Core中,我们通过使用过滤器,在请求处理管道中在操作Action的时候运行特定代码。通过这种方式将独立的横切功能应用到Action内部逻辑里,达到不修改原逻辑代码的情况下,扩展新功能,这样方便我们去管理各种横切功能。
有点类似
请求管道
ASP.NET Core的请求管道可以看作一系列中间件的组成,这是处理HTTP请求的核心流程。ASP.NET Core中请求管道是在应用启动阶段创建的,在Program.cs文件里通过代码构建配置。
来自客户端的请求按顺序经过管道中的中间件,每个中间件处理请求后传递给下一个中间件,当请求到达终端中间件后,生成响应后,按相反顺序再次经过之前的中间件,最终返回给客户端。管道的中间件支持短路,特定情况下不执行后续流程。
过滤器在请求管道中的位置,在Action的内部调用里

二、Filters管道与Filter类型
ASP.NET Core中提供了各类筛选器,帮助我们在各式场景里对Action进行精细化处理。特定情况下执行特定的功能。
类似上文提到的请求管道,Filter也有一个由各类Filter组成的Filter管道,并且Filter支持短路机制,可以跳过后续的Filter执行。这个Filter管道里,按照如下顺序执行:
- 授权过滤器:检查权限,未授权则短路(返回 401);
- 资源过滤器:前置逻辑(如缓存命中则短路,返回缓存结果);
- 模型绑定(将请求数据转为操作参数);
- 操作过滤器:前置逻辑(如验证模型状态)→ 执行操作方法 → 操作过滤器后置逻辑(如修改返回结果);
- 异常过滤器:补充Action运行发生的异常信息;
- 结果过滤器:前置逻辑(如加响应头)→ 执行操作结果(如渲染视图、序列化 JSON)→ 结果过滤器后置逻辑;
- 资源过滤器:后置逻辑(如缓存未命中时存入缓存);
模型绑定不属于过滤器管道,但是和过滤器管道关系密切。位于资源过滤器和操作过滤器之间。它用于将请求里的表单、URL 参数、查询字符串、JSON/XML 请求体等等转换为.NET对象。比方说一个Action里通过方法参数获取请求参数,不需要通过HttpContext,其实模型绑定也就是帮我们将查询参数和对象进行绑定,并且支持模型验证。
换句话说,在模型绑定之前的过滤器执行时修改HttpContext会影响绑定后的模型,但由于模型绑定是一次性的,后面修改的HttpContext不会影响模型绑定。

ASP.NET Core 7.0之后,引入了EndpointFilters(终结点筛选器)它的执行位置处于路由匹配之后、控制器Action(或其他终结点处理逻辑)执行前后,早于ActionFilter。
主要用于验证已发送到终结点的请求参数和正文。记录有关请求和响应的信息。验证请求是否面向受支持的 API 版本。
2.1 授权过滤器(Authorization Filter)
授权筛选器是筛选器管道中运行的第一个筛选器,用于控制对操作方法的访问,它只有前置的执行的方法。
一般情况下我们不需要编写自定义授权筛选器,使用内置授权筛选器调用授权系统就够用了。
比方说引入JWT授权鉴权。我们都是在Program.cs里通过AddAuthentication注册认证服务的基础配置,再AddJwtBearer添加JWT Bearer认证方案的具体配置。最后在通过app.UseAuthentication()和app.UseAuthorization()启用认证中间件和授权中间件。
这样我们就能在控制器或者动作上添加Authorize特性,开启授权筛选器。
官方提供了几个封装好的特性,方便我们调用各种过滤器。
[Route("[controller]/[action]")]
[Authorize]
[ApiController]
public class MeterStationController : ControllerBase
2.2 资源过滤器(Resource Filter)
资源过滤器主要用于在模型绑定之前和结果执行之后介入请求处理流程,专注于对 “资源”(的访问进行处理。一般用来做缓存相关的。
资源过滤器是通过IResourceFilter或异步版本的IAsyncResourceFilter接口。推荐使用异步版本。继承自IAsyncResourceFilter接口的方法需要实现OnResourceExecutionAsync这个异步方法
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
}
该方法包含两个参数和一个执行后逻辑生成的ResourceExecutedContext 对象
- ResourceExecutingContext context :是在资源过滤器执行前的上下文信息,包含四大属性
- context.HttpContext :获取和设置请求的 HTTP上下文
- context.Result :设置响应结果,直接短路,不再执行后续逻辑
- context.ActionDescriptor :获取当前终结点的控制器、动作方法等的元数据
- context.ValueProviderFactories :控制模型绑定的数据来源
- ResourceExecutionDelegate next:是Task< ResourceExecutedContext>的委托, 表示代表过滤器管道的后续执行逻辑
- 调用await next()用于划分执行前后与否。也就是说await next()前的方法是执行前逻辑,await next()是执行后逻辑。await next()会调用后返回 ResourceExecutedContext 对象,包含后续管道执行后的结果
- ResourceExecutedContext :Action方法返回的结果
- resourceExecutedContext.HttpContext/Controller :与 ActionExecutingContext 一致,可继续操作上下文。
- resourceExecutedContext.Result: 获取或者设置Action方法执行后结果,修改此结果会改变最终响应
- resourceExecutedContext.Exception: 设置响应结果,动作执行过程中抛出的异常,无异常是null
- resourceExecutedContext.ExceptionHandled :设置为true表示异常已处理,框架不再向上抛出
资源过滤器的工作机制使其天然适合处理自定义缓存相关的功能。在方法到达指定的Action时候还没有执行具体方法,判断当前客户端发起的请求是否有缓存,存在则短路请求,返回缓存,无需执行后续逻辑。不存在执行原先的逻辑,并且在后续逻辑里保存缓存。
案例——应用资源过滤器实现简单的缓存
前面我们知道过滤器管道里,资源过滤器之后才执行模型绑定和操作过滤。也就是说在此之前Action的逻辑并未执行。依据这个特性,我们可以在具体动作执行的时候判断是否有缓存,实现性能的优化。
- 先通过依赖注入获取分布式缓存服务
- await next()前编写判断是否有缓存,存在缓存则短路过滤器管道
- await next()后编写更新缓存的逻辑,设置缓存的过期时间,因为执行到这里的代码是不存在缓存的情况。
值得注意的是应用资源过滤器后才是模型绑定。我们得通HttpContext才能获取到请求参数。
实际运行中还得在分布式缓存服务里添加防止缓存穿透,给不存在的数据生成缓存。另外就是添加分布式锁解决热点key失效,大量并发请求导致的缓存击穿情况。这写内容会在专门的分布式缓存章节里讨论。
完整代码
public class CacheFilter : IAsyncResourceFilter
{
private readonly IDistributedCache _cache;
private readonly int _cacheDurationSeconds;
// 通过依赖注入获取分布式缓存服务
public CacheFilter(IDistributedCache cache, int cacheDurationSeconds)
{
_cache = cache;
_cacheDurationSeconds = cacheDurationSeconds;
}
// 异步执行方法(同时处理"执行前"和"执行后"逻辑)
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
// 1.执行前逻辑(模型绑定前)
var keyBuilder = new StringBuilder();
keyBuilder.Append($"resource_{context.HttpContext.Request.Method}_{context.HttpContext.Request.Path}");
// 根据查询字符串生成key
foreach (var query in context.HttpContext.Request.Query.OrderBy(kv => kv.Key))
{
keyBuilder.Append($"_{query.Key}={query.Value}");
}
var cacheKey = keyBuilder.ToString();
// 从分布式缓存异步读取
var cachedData = await _cache.GetStringAsync(cacheKey);
if (!string.IsNullOrEmpty(cachedData))
{
// 短路请求:直接返回缓存结果(无需执行后续逻辑)
var result = JsonSerializer.Deserialize<object>(cachedData);
context.Result = new OkObjectResult(result);
return;
}
// 2.执行后续管道(控制器动作、其他筛选器等)
var executedContext = await next();
// 3.执行后逻辑(结果执行后)
if (executedContext.Result is OkObjectResult okResult)
{
// 将结果异步写入分布式缓存
var serializedData = JsonSerializer.Serialize(okResult.Value);
await _cache.SetStringAsync(
cacheKey,
serializedData,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(_cacheDurationSeconds)
});
}
}
}
2.3 操作过滤器(Action Filter)
操作过滤器在过滤器中是扮演一个处理与动作执行相关的功能角色,比如说业务功能上的验证参数,记录Action的执行日志完整情况,又或者是在执行结果后给一些修改数据加上处理状态标签的印记。主要用于在Action方法执行前和执行后插入自定义逻辑。
操作过滤器的实现同样也有IAsyncActionFilter和IActionFilter两个版本,推荐使用异步版本。
public class CommActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
}
}
该方法包含两个参数,和一个执行后逻辑生成的ActionExecutedContext对象
-
ActionExecutingContextcontext :在动作执行前的上下文,用于在过滤器中读取请求数据、修改参数、提前终止请求。
- context.HttpContext: 获取和设置请求的 HTTP上下文
- context.Result :设置响应结果,直接短路,不再执行后续逻辑
- context.ActionDescriptor: 获取当前终结点的控制器、动作方法等的元数据
- context.ActionArguments :字典类型,存储动作方法的输入参数(键为参数名,值为参数值)。可直接修改。【因为已经执行了模型绑定,可以直接获取】
- context.ModelState :获取模型验证状态(ModelState.IsValid),用于参数合法性校验。
-
ResourceExecutionDelegate next :是Task< ActionExecutedContext>的委托, 表示代表过滤器管道的后续执行逻辑
- 调用await next()用于划分执行前后与否。也就是说await next()前的方法是执行前逻辑,await next()是执行后逻辑。await next()会调用后返回 ActionExecutedContext对象,包含Action方法执行后的信息
-
ActionExecutedContext :Action方法返回的结果
- executedContext.HttpContext/Controller :与 ActionExecutingContext 一致,可继续操作上下文。
- executedContext.Result :获取或者设置Action方法执行后结果,修改此结果会改变最终响应
- executedContext.Exception: 设置响应结果,动作执行过程中抛出的异常,无异常是null
- executedContext.ExceptionHandled :设置为true表示异常已处理,框架不再向上抛出
案例——应用操作过滤器实现简单的参数拦截和响应结果的修改
模型绑定实在Action Filter之前。所以我们能在ActionExecutingContext上下文里获取ModelState,此时已经完成了模型绑定
- 动作执行前,验证模型状态
- 动作执行后,处理返回结果给,返回结果添加方法执行时间信息
完整代码
public class CommActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// 动作执行前:验证模型状态
if (!context.ModelState.IsValid)
{
// 模型验证失败,直接返回错误,不执行后续动作
context.Result = new BadRequestObjectResult(context.ModelState);
return;
}
// 执行后续管道(包括动作方法)
var resultContext = await next();
// 动作执行后:可处理返回结果(如修改响应)
if (resultContext.Result is ObjectResult objectResult)
{
// 示例:给返回结果添加额外信息
objectResult.Value = new
{
data = objectResult.Value,
timestamp = DateTime.Now
};
}
}
}
2.4 异常过滤器(Exception Filter)
异常过滤器是专门用于捕获和处理控制器动作执行过程中抛出的异常,包括动作方法、其他过滤器或模型绑定中产生的异常。用于统一处理异常,避免将原始错误信息暴露给客户端,不用频繁的编写try-catch语句。
异常过滤器的实现同样也有IAsyncExceptionFilter和IExceptionFilter两个版本,推荐使用异步版本。
public class CommExceptionFilter : IAsyncExceptionFilter
{
public Task OnExceptionAsync(ExceptionContext context)
{
}
}
该方法包含一个参数
- ExceptionContext:异常处理上下文,承载异常相关的所有信息,并提供处理异常的能力
- context.HttpContext: 获取和设置请求的 HTTP上下文
- context.Result :设置异常处理后的响应结果,替代原始异常的默认响应。【单独设置Result 不会直接短路,必须把ExceptionHandled 也设置成True才短路】
- context.ActionDescriptor: 获取当前终结点的控制器、动作方法等的元数据
- context.Exception:获取当前抛出的异常对象,包含错误消息、堆栈跟踪、内部异常等细节
- context.ExceptionHandled:标记异常是否已被处理。设置为 true 后,框架会停止将异常传递给后续过滤器或中间件,也不会触发全局异常中间件。
案例——应用异常过滤器实现简单的异常捕获,同时支持自定义异常抛出
通过异常过滤器实现简单的异常捕获,并且支持我们手动添加异常参数信息。
- 首先自定义异常类BusinessException,运行我们自定义异常信息,状态码。自定义异常类多种构造函数,满足多种实例化。
- 补充异常,优先补充自定义异常。context.Result设置好结果后,context.ExceptionHandled也要同步设置成true。
自定义异常类
public class BusinessException : System.Exception
{
//自定义错误码
public int ErrorCode { get; }
//HTTP状态码(默认400 Bad Request)
public int StatusCode { get; } = 400;
public BusinessException(string message) : base(message) { }
public BusinessException(int errorCode, string message) : base(message)
{
ErrorCode = errorCode;
}
public BusinessException(int errorCode, string message, int statusCode) : base(message)
{
ErrorCode = errorCode;
StatusCode = statusCode;
}
}
异常过滤器
public class CommExceptionFilter : IAsyncExceptionFilter
{
private readonly IWebHostEnvironment _webHostEnvironment;
public CommExceptionFilter(IWebHostEnvironment webHostEnvironment)
{
_webHostEnvironment = webHostEnvironment;
}
public Task OnExceptionAsync(ExceptionContext context)
{
var exception = context.Exception;
var requestPath = context.HttpContext.Request.Path;
ProblemDetails problemDetails;
int statusCode;
if (exception is BusinessException businessException)
{
//提取自定义信息
statusCode = businessException.StatusCode;
problemDetails = new ProblemDetails
{
Status = statusCode,
Title = "业务错误",
Detail = businessException.Message, // 使用自定义消息
Instance = requestPath
};
}
else
{
problemDetails = new ProblemDetails
{
Status = 500,
Title = "服务器内部错误",
Detail = _webHostEnvironment.IsDevelopment() ? exception.Message : "请联系管理员处理", // 开发环境显示详细信息
Instance = requestPath
};
}
context.Result = new ObjectResult(problemDetails)
{
StatusCode = 500
};
context.ExceptionHandled = true;
return Task.CompletedTask;
}
}
2.5 结果过滤器(Result Filter)
结果过滤器是在Action方法执行完成之后,且在准备返回执行结果ActionResult的过程中介入,在ActionResult执行前和执行后插入自定义逻辑,关注的是Action执行返回的结果,这样的特质使其成为一个天然对对结果进行二次加工的理想选择。
换句话说,
结果过滤器的实现同样也有IAsyncResultFilter和IResultFilter两个版本,推荐使用异步版本。
public class CommResultFilter : IAsyncResultFilter
{
public Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
throw new NotImplementedException();
}
}
该方法包含两个参数和一个结果执行后对象
- ResultExecutingContext:结果执行上下文,包含结果执行前的关键信息,可用于修改结果或短路结果执行
- context.HttpContext: 获取和设置请求的 HTTP上下文
- context.Result :设置当前要执行的IActionResult对象
- context.ActionDescriptor: 获取当前终结点的控制器、动作方法等的元数据
- context.Cancel:设置为true可取消结果执行(短路),不再调用next()
- context.Controller:当前控制器实例。
- ResourceExecutionDelegate next :是Task< ResultExecutedContext>的委托, 表示代表过滤器管道的后续执行逻辑
- 调用await next()用于划分执行前后与否。也就是说await next()前的方法是执行前逻辑,await next()是执行后逻辑。await next()会调用后返回 ResultExecutedContext对象,包含结果执行后的信息
- ResultExecutedContext:执行await next()后返回的对象,包含结果执行后的信息,可用于处理执行结果或捕获异常
- context.HttpContext: 获取和设置请求的 HTTP上下文
- context.Result :获取已执行的IActionResult对象,修改无效
- context.Exception: 结果执行过程中抛出的异常(若为null表示无异常)
- context.ExceptionHandled:设置为true可取消结果执行(短路),不再调用next()
案例——应用结果过滤器格式化数据
通过结果过滤器统一响应结果。
完整代码
public class CommResultFilter : IAsyncResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
//统一数据的响应
if (context.Result is ObjectResult objectResult && objectResult.StatusCode == 200)
{
var wrappedResult = new
{
code = 200,
message = "success",
data = objectResult.Value
};
//替换原始结果
context.Result = new ObjectResult(wrappedResult)
{
StatusCode = 200
};
}
//触发后续过滤器和结果本身的执行
var executedContext = await next();
// 执行后的逻辑,添加自定义响应头
if (executedContext.Exception == null)
{
//添加自定义响应头
executedContext.HttpContext.Response.Headers.TryAdd("custom-tag", "test");
}
}
}
三、Filter的应用
在控制器和动作中应用Filter一般有以下几种方式:
3.1 全局注册
Program.cs文件里全局注册,这样控制器和动作不需要显示,就能默认应用。覆盖范围最广,要谨慎使用。
builder.Services.Configure<MvcOptions>(options =>
{
options.Filters.Add<AnotherActionFilter>();
options.Filters.Add<CommExceptionFilter>();
options.Filters.Add<LogExceptionFilter>();
});
3.2 自定义过滤器特性
让过滤器同时继承Attribute和过滤器接口后,就能在Controller或者Action上面应用过滤器,当然也可也指定Attribute的层级。
但是这种方式不支持依赖注入,无法从DI容器里获取实例。
public class AnotherActionFilter : Attribute, IAsyncActionFilter
{
}
[HttpGet]
[AnotherActionFilter]
public ActionResult test()
{
return Ok("123");
}
3.3 ServiceFilterAttribute
在Program.cs中注册服务筛选器实现类型。这样ServiceFilterAttribute就可从DI中检索筛选器实例。这样我们就能在特定地方调用需要依赖注入的过滤器。
Program.cs
builder.Services.AddScoped<CommExceptionFilter>();
过滤器
public class CommExceptionFilter : IAsyncExceptionFilter
{
private readonly IWebHostEnvironment _webHostEnvironment;
public CommExceptionFilter(IWebHostEnvironment webHostEnvironment)
{
_webHostEnvironment = webHostEnvironment;
}
}
Action
[HttpGet]
[ServiceFilter(typeof(CommExceptionFilter))]
public ActionResult test()
{
return Ok("123");
}
3.4 TypeFilterAttribute
TypeFilterAttribute不需要在Program.cs里注册控制器,具备依赖注入的环境,生命周期由框架临时管理,每次使用时都会创建新实例。支持在特定的地方特定地方调用控过滤器
过滤器
public class CacheFilter : IAsyncResultFilter
{
private readonly int _durationSeconds;
public CacheFilter(int durationSeconds)
{
_durationSeconds = durationSeconds;
}
public Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
Console.WriteLine(_durationSeconds);
return Task.CompletedTask;
}
}
[HttpGet]
[TypeFilter(typeof(CacheFilter), Arguments = new object[] { 10 })]
public ActionResult test()
{
return Ok("123");
}
3.5 同类型过滤器的执行优先度
当多个同类型过滤器用于同一请求时,可通过给过滤器添加IOrderedFilter接口。这个接口实现,并定义Order属性。Order越小,同类型过滤器里执行的优先级就越高。
过滤器
public class CommExceptionFilter : IAsyncExceptionFilter,IOrderedFilter
{
private readonly IWebHostEnvironment _webHostEnvironment;
public int Order { get; set; } = 1;
总结
ASP.NET Core 过滤器是基于 AOP 思想,通过授权、资源、操作、异常、结果等多种类型过滤器按特定顺序介入请求处理流程,可通过全局注册、特性、ServiceFilter/TypeFilter 等方式应用,实现横切关注点(如权限、缓存、日志、异常处理)的解耦与复用。
3662

被折叠的 条评论
为什么被折叠?



