ASP.NET Core过滤器需关注执行顺序、作用域和DI生命周期;接口实现需手动注册,特性继承更常用但需满足构造函数要求;全局注册须注意scoped服务注入问题,Order值须显式设置以控制同类型过滤器执行顺序。

ASP.NET Core的过滤器,很多人觉得它是个黑盒——写上去就能用,但实际远没那么简单。它的执行顺序、作用域,还有依赖注入的生命周期,这三个点但凡有一个没搞明白,过滤器就可能不触发、注入失败,或者行为完全不对。所以,想用好过滤器,先得把这几个关键点理清楚。
过滤器接口选哪个:IActionFilter vs ActionFilterAttribute
直接实现 IActionFilter 接口,灵活性是最高的,但得自己手动把它注册到DI容器里,比如 services.AddScoped。这一步忘了,那 OnActionExecuting 方法压根不会被执行。相比之下,继承 ActionFilterAttribute 是更常见的做法——它自带属性语法,比如 [MyFilter] 就能直接用,而且在构造函数里注入依赖也是默认支持的。但要注意,这个类必须是 public 的,而且得有一个无参数构造函数,否则运行时就会报 InvalidOperationException: No constructor for type xxx 这样的错。
- 如果想在多个 Action 上复用同一个过滤器,优先选
ActionFilterAttribute,再加上[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]来控制它到底能用在方法上还是类上。 - 要是想统一拦截所有请求,不想依赖属性标记,那就走全局注册的路子:
options.Filters.Add。但这时候必须得实现接口,而且得把服务注册好。() - 在过滤器里要用
HttpContext或IConfiguration吗?构造函数注入可以,但有个坑:全局注册的过滤器生命周期是 singleton,而HttpContext是 scoped 的,直接注入会报Cannot resolve scoped service from root provider。正确做法是用context.HttpContext.RequestServices.GetService按需解析。()
Order 值怎么设才真正控制执行顺序
不少人以为 options.Filters.Add 和 options.Filters.Add 的调用顺序就是执行顺序,其实不是这么回事。默认情况下,通过 Add 注册的过滤器,Order 值都是 0——是的,你没看错,全是 0。Order 相同时,后添加的反而先执行。所以,如果你想让日志过滤器在外层、认证在内层,就得显式指定 Order 值:
options.Filters.Add(10); // 先执行 options.Filters.Add (20); // 后执行
需要特别注意的是,这个 Order 值只在同一类过滤器中生效。比如两个 IActionFilter 之间可以比较,但跨类型就不行——IAuthorizationFilter 永远比 IActionFilter 先执行,这是由框架设计决定的。
- 控制器或 Action 上标注的属性过滤器,
Order默认是 -1,比全局注册的同类型过滤器优先级更高。 - 如果混用了全局注册、特性标记、控制器级过滤器,建议全部显式设置
Order,避免靠“试出来”的顺序来维护。 Order = int.MinValue和int.MaxValue可以作为安全边界,但别用0当“默认值”来凑数,那样容易乱。
异常过滤器 IExceptionFilter 的常见失效场景
IExceptionFilter 只捕获操作方法内部抛出的异常。模型绑定失败、中间件异常、授权失败(比如 UnauthorizedResult)这些情况,它都不会触发。典型的失效现象是:你加上了 [CustomExceptionFilter],但参数绑定出错时,比如字符串转 int 失败了,页面还是返回 400,而过滤器的 OnException 方法根本没进去。
- 想统一处理模型绑定错误?得用
ModelStateInvalidFilter,它继承自IActionFilter,在方法里检查context.ModelState.IsValid就行。 - 想捕获授权失败?改用
IAuthorizationFilter,在OnAuthorization里判断context.Result is UnauthorizedResult。 - 想记录所有未处理异常,包括中间件抛出的?应该用
UseExceptionHandler中间件,而不是过滤器。 - 还有一点,
context.ExceptionHandled = true必须显式设置,否则异常会继续向上冒泡,可能被外层中间件再次处理,导致行为异常。
DI 注入时 Scoped 服务拿不到的典型错误
全局注册的过滤器默认是 singleton 生命周期,但很多业务服务,比如 IDbContextFactory、ILogger,都是 scoped 的。直接通过构造函数注入,启动时就会报错:Cannot consume scoped service 'xxx' from singleton 'yyy'。
- 解决方案一:把过滤器本身也注册为
Scoped。但要注意,全局过滤器设为 scoped 后,每次请求都会新建实例,性能开销会稍微增加。 - 解决方案二:在方法体内用
context.HttpContext.RequestServices.GetService来获取服务。这是最稳妥的做法,特别适合日志、配置、缓存这类轻量级依赖。() - 千万别在
OnActionExecuting里 new 一个 DbContext,它不会自动加入当前请求的变更跟踪,Sa veChanges可能会静默失败,查都查不到。
最容易被忽略的一点是:过滤器的执行时机与 MVC 管道阶段是强绑定的,并不是“代码写了就运行”。比如资源过滤器的 OnResourceExecuting 在模型绑定之前执行,这时候 context.ActionArguments 还是空的;而动作过滤器的 OnActionExecuting 执行时,才能看到绑定后的参数。如果看错了阶段,就很容易对着空值做逻辑判断,结果自然不对。