1. 过滤器在C#中的核心价值
在.NET开发中,过滤器(Filter)就像手术室里的无菌操作流程——它能在特定环节自动拦截请求,进行预处理或后处理。我接手过的一个电商项目就曾因为缺少订单接口的权限校验,导致严重的安全漏洞。后来通过实现AuthorizationFilter,只用20行代码就修复了这个隐患。
过滤器本质上是一种面向切面编程(AOP)的实现,它允许我们将横切关注点(如日志、缓存、验证)与业务逻辑解耦。与中间件(Middleware)相比,过滤器更贴近MVC/WebAPI的请求生命周期,能精确控制Action级别的行为。
2. 五大过滤器类型深度解析
2.1 AuthorizationFilter:安全守卫
这是最先执行的过滤器,常用于权限验证。我在金融项目中实现的动态权限系统就继承自AuthorizeAttribute:
public class DynamicAuthorizeAttribute : AuthorizeAttribute { public override void OnAuthorization(AuthorizationContext context) { var user = context.HttpContext.User; var action = context.ActionDescriptor.ActionName; if(!PermissionService.Check(user, action)) { context.Result = new ForbidResult(); } } }重要提示:不要在AuthorizationFilter中进行耗时操作(如数据库查询),这会拖慢整个请求管道。建议使用缓存策略。
2.2 ActionFilter:业务逻辑的瑞士军刀
ActionFilter能在方法执行前后插入逻辑,比如这个API耗时监控的实现:
public class BenchmarkFilter : IActionFilter { private Stopwatch _sw; public void OnActionExecuting(ActionExecutingContext context) { _sw = Stopwatch.StartNew(); } public void OnActionExecuted(ActionExecutedContext context) { _sw.Stop(); Logger.Info($"Action {context.ActionDescriptor.DisplayName} 耗时 {_sw.ElapsedMilliseconds}ms"); } }实际项目中我常用它来处理:
- 自动模型验证(替代Controller中的if(!ModelState.IsValid))
- 请求数据预处理(如Trim字符串参数)
- 响应数据统一包装
2.3 ResultFilter:最后的加工车间
当需要统一修改视图结果时,ResultFilter非常有用。比如这个自动包装API响应的案例:
public class ApiResultFilter : IResultFilter { public void OnResultExecuting(ResultExecutingContext context) { if(context.Result is ObjectResult objResult) { context.Result = new ObjectResult(new { Code = 200, Data = objResult.Value, Timestamp = DateTimeOffset.Now.ToUnixTimeSeconds() }); } } }2.4 ExceptionFilter:系统消防员
全局异常处理是ExceptionFilter的典型场景。这个实现会区分业务异常和系统异常:
public class CustomExceptionFilter : IExceptionFilter { public void OnException(ExceptionContext context) { if(context.Exception is BusinessException bizEx) { context.Result = new JsonResult(new { Code = bizEx.ErrorCode, Message = bizEx.Message }); } else { context.Result = new StatusCodeResult(500); Logger.Error(context.Exception); } context.ExceptionHandled = true; } }经验之谈:ExceptionFilter处理不了的异常(如Filter本身的异常)会进入中间件异常处理,建议两者配合使用。
2.5 ResourceFilter:高性能利器
ResourceFilter可以在模型绑定前短路请求,适合做缓存检查。我在高并发项目中用它实现接口缓存:
public class CacheResourceFilter : IResourceFilter { public void OnResourceExecuting(ResourceExecutingContext context) { var cacheKey = GenerateCacheKey(context.HttpContext.Request); if(MemoryCache.TryGetValue(cacheKey, out var cachedResult)) { context.Result = cachedResult; } } public void OnResourceExecuted(ResourceExecutedContext context) { if(context.Result != null) { MemoryCache.Set(GenerateCacheKey(context.HttpContext.Request), context.Result, TimeSpan.FromMinutes(5)); } } }3. 过滤器实战进阶技巧
3.1 执行顺序控制
过滤器的默认执行顺序是:
- AuthorizationFilter
- ResourceFilter
- ActionFilter.OnActionExecuting
- 控制器方法
- ActionFilter.OnActionExecuted
- ResultFilter
- ExceptionFilter
通过Order属性可以调整顺序:
[MyFilter(Order = 1)] [MyFilter(Order = 2)] public class HomeController : Controller3.2 依赖注入的正确姿势
过滤器支持构造函数依赖注入,但需要改用ServiceFilter或TypeFilter:
[ServiceFilter(typeof(LoggingFilter))] public IActionResult Get() { //... } // 在Startup中注册 services.AddScoped<LoggingFilter>();3.3 动态过滤器方案
对于需要运行时判断的场景,可以实现IFilterFactory:
public class DynamicFilterFactory : IFilterFactory { public bool IsReusable => false; public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) { var context = serviceProvider.GetService<IHttpContextAccessor>(); return context.HttpContext.Request.Path.StartsWith("/api") ? new ApiFilter() : new MvcFilter(); } }4. 性能优化与避坑指南
4.1 常见性能陷阱
同步阻塞:在过滤器中调用异步方法时没有正确await,会导致线程池耗尽
// 错误做法 public void OnActionExecuting(ActionExecutingContext context) { _client.GetAsync("url").Result; // 同步阻塞 } // 正确做法 public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { await _client.GetAsync("url"); await next(); }过度日志:在高频接口的过滤器中记录完整请求体会显著影响性能
4.2 过滤器与中间件的抉择
| 特性 | 过滤器 | 中间件 |
|---|---|---|
| 执行阶段 | MVC管道内(模型绑定后) | 更早的请求管道阶段 |
| 上下文信息 | 丰富的MVC上下文(ActionDescriptor等) | 只有基础的HttpContext |
| 适用范围 | 仅对Controller/Action生效 | 全局所有请求 |
经验法则:
- 需要MVC上下文信息 → 用过滤器
- 需要处理静态文件请求 → 用中间件
- 性能关键路径 → 优先考虑中间件
4.3 单元测试策略
测试过滤器需要模拟执行上下文,推荐使用Moq框架:
[Test] public void AuthFilter_Should_Reject_Anonymous() { var filter = new JwtAuthFilter(); var context = new AuthorizationContext( new ActionContext( new DefaultHttpContext(), new RouteData(), new ActionDescriptor()), new List<IFilterMetadata>()); filter.OnAuthorization(context); Assert.IsInstanceOf<UnauthorizedResult>(context.Result); }5. 企业级应用案例
5.1 审计日志系统
结合ActionFilter和DI实现的审计方案:
public class AuditFilter : IAsyncActionFilter { private readonly AuditDbContext _db; public AuditFilter(AuditDbContext db) => _db = db; public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { var audit = new AuditLog { UserId = context.HttpContext.User.FindFirst("sub")?.Value, Action = context.ActionDescriptor.DisplayName, Parameters = JsonSerializer.Serialize(context.ActionArguments), Timestamp = DateTime.UtcNow }; var resultContext = await next(); audit.ResponseStatus = context.HttpContext.Response.StatusCode; audit.IsSuccess = resultContext.Exception == null; await _db.AuditLogs.AddAsync(audit); await _db.SaveChangesAsync(); } }5.2 多租户数据隔离
通过ResourceFilter实现租户上下文注入:
public class TenantFilter : IAsyncResourceFilter { public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) { var tenantId = context.HttpContext.Request.Headers["X-Tenant-Id"]; var tenant = await TenantStore.GetAsync(tenantId); if(tenant == null) { context.Result = new BadRequestResult(); return; } // 注入租户上下文 context.HttpContext.Items["CurrentTenant"] = tenant; await next(); } }在控制器中通过特性标注使用:
[TypeFilter(typeof(TenantFilter))] public class ProductsController : Controller这些实战经验来自我参与的SaaS平台项目,过滤器方案使租户隔离代码量减少了70%,同时保证了数据安全性。