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

ASP.NET Core 的过滤器不是“写完就能用”的黑盒,它依赖执行顺序、作用域和 DI 生命周期三个关键点;漏掉任一环节,过滤器可能不触发、注入失败或行为错乱。
过滤器接口选哪个:IActionFilter vs ActionFilterAttribute
直接实现 IActionFilter 接口最灵活,但必须手动注册进 DI 容器(services.AddScoped<myfilter>()</myfilter>),否则 OnActionExecuting 根本不会调用。而继承 ActionFilterAttribute 更常用——它自带属性语法(如 [MyFilter]),且默认支持构造函数注入,但要求类必须是 public 且无参数构造函数可用(否则运行时报 InvalidOperationException: No constructor for type xxx)。
- 需要在多个 Action 上复用?优先用
ActionFilterAttribute,加[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]控制使用位置 - 要统一拦截所有请求且不依赖属性标记?走全局注册:
options.Filters.Add<myfilter>()</myfilter>,此时必须实现接口并完成服务注册 - 想在过滤器里用
HttpContext或IConfiguration?构造函数注入可行,但注意:全局注册的过滤器生命周期是 singleton,而HttpContext是 scoped,直接注入会报Cannot resolve scoped service from root provider—— 改用context.HttpContext.RequestServices.GetService<t>()</t>按需解析
Order 值怎么设才真正控制执行顺序
很多人以为 options.Filters.Add<logfilter>()</logfilter> 和 options.Filters.Add<authfilter>()</authfilter> 的调用顺序就是执行顺序,其实不是。默认所有通过 Add<t>()</t> 注册的过滤器 Order 都是 0,而 Order 相同时,后添加的反而先执行。所以如果你希望日志过滤器在外层、认证在内层,就得显式指定:
options.Filters.Add<LogFilter>(10); // 先执行 options.Filters.Add<AuthFilter>(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<appdbcontext></appdbcontext>、ILogger<t></t>)是 scoped。直接构造函数注入会导致启动时报错:Cannot consume scoped service 'xxx' from singleton 'yyy'。
- 解决方案一:把过滤器本身也注册为
Scoped,但注意——全局过滤器设为 scoped 后,每次请求都会新建实例,性能开销略增 - 解决方案二:在方法体内用
context.HttpContext.RequestServices.GetService<imyservice>()</imyservice>拿,这是最稳妥的做法,尤其适合日志、配置、缓存等轻量依赖 - 千万别在
OnActionExecuting里 new 一个 DbContext,它不会自动加入当前请求的变更跟踪,SaveChanges 可能静默失败
最常被忽略的是:过滤器的执行时机与 MVC 管道阶段强绑定,而不是“代码写了就运行”。比如资源过滤器的 OnResourceExecuting 在模型绑定前,这时候 context.ActionArguments 还是空的;而动作过滤器的 OnActionExecuting 里才能看到绑定后的参数。看错阶段,就容易对着空值做逻辑判断。


















