ASP.NET Core中Action过滤器未覆盖Controller级别过滤器问题咨询
ASP.NET Core 过滤器Action级参数未覆盖控制器级的问题解决
问题描述
我实现了一个继承IAsyncAuthorizationFilter/IActionFilter的过滤器,并封装为TypeFilterAttribute子类MyAuthorizeAttribute。当同时在控制器和Action上添加该特性时,Action级过滤器的参数并未覆盖控制器级的设置:
- 控制器上添加默认的
[MyAuthorize](redirectOnFailure默认值为true) - Action上添加
[MyAuthorize(redirectOnFailure: false)]
但实际运行时,RedirectOnFailure的值仍为true。在ASP.NET MVC中,Action过滤器会自动覆盖控制器级过滤器,可实现“为所有Action设置默认值,特定Action用不同参数覆盖”的效果,想知道ASP.NET Core中如何实现该功能。
代码示例
过滤器定义
public class MyAuthorizeAttribute : TypeFilterAttribute { public MyAuthorizeAttribute (bool redirectOnFailure = true) : base(typeof(MyFilter)) { Arguments = new object[] { redirectOnFailure }; } } public class MyFilter: IAsyncAuthorizationFilter, IActionFilter { public bool RedirectOnFailure { get; set; } public MyFilter(bool redirectOnFailure) { RedirectOnFailure = redirectOnFailure; } public void OnActionExecuting(ActionExecutingContext context) { if (context.Controller is Controller controller) { // Do some work if (true) { if (!RedirectOnFailure) { context.Result = new JsonResult("Your session has expired."); } else { context.Result = new RedirectResult("LoginUrl"); } return; } } } public void OnActionExecuted(ActionExecutedContext context) { // Do nothing } public virtual async Task OnAuthorizationAsync(AuthorizationFilterContext context) { // Do work } }
控制器代码
[MyAuthorize] public class HomeController : Controller { [MyAuthorize(redirectOnFailure: false)] public IActionResult Index() { // Do work } }
原因分析
ASP.NET Core与ASP.NET MVC的过滤器执行逻辑不同:
- ASP.NET MVC中,Action级过滤器会覆盖控制器级的同类型过滤器,仅执行Action级的实例
- ASP.NET Core中,默认会同时执行控制器和Action上的两个过滤器实例,且控制器级过滤器优先执行。由于控制器级过滤器先设置了
Result,Action级过滤器的逻辑根本不会触发,导致看起来参数未覆盖。
解决方案
要实现Action级参数覆盖控制器级的效果,核心是确保仅执行Action级的过滤器实例,以下是两种简单可行的实现方式:
方案一:在过滤器内部添加跳过逻辑
在过滤器的执行方法中,检查当前Action上是否存在同类型的过滤器,如果存在,则跳过控制器级过滤器的执行:
修改后的MyFilter代码:
public class MyFilter: IAsyncAuthorizationFilter, IActionFilter { public bool RedirectOnFailure { get; set; } public MyFilter(bool redirectOnFailure) { RedirectOnFailure = redirectOnFailure; } public void OnActionExecuting(ActionExecutingContext context) { // 检查Action上是否存在MyAuthorizeAttribute var hasActionLevelFilter = context.ActionDescriptor.EndpointMetadata .Any(m => m is MyAuthorizeAttribute); // 判断当前过滤器是否来自控制器级 var isControllerLevelFilter = context.Controller.GetType() .GetCustomAttribute<MyAuthorizeAttribute>() != null; // 如果是控制器级过滤器且Action上有同类型过滤器,直接跳过 if (isControllerLevelFilter && hasActionLevelFilter) { return; } if (context.Controller is Controller controller) { // 保留原业务逻辑 if (true) { if (!RedirectOnFailure) { context.Result = new JsonResult("Your session has expired."); } else { context.Result = new RedirectResult("LoginUrl"); } return; } } } public void OnActionExecuted(ActionExecutedContext context) { // Do nothing } public virtual async Task OnAuthorizationAsync(AuthorizationFilterContext context) { // 同样添加跳过逻辑 var hasActionLevelFilter = context.ActionDescriptor.EndpointMetadata .Any(m => m is MyAuthorizeAttribute); var isControllerLevelFilter = context.Controller.GetType() .GetCustomAttribute<MyAuthorizeAttribute>() != null; if (isControllerLevelFilter && hasActionLevelFilter) { return; } // 保留原授权逻辑 } }
方案二:自定义过滤器提供程序移除重复实例
创建自定义IFilterProvider,在过滤器收集阶段,若发现Action上有MyAuthorizeAttribute,则移除控制器级的对应过滤器:
自定义过滤器提供程序
public class MyAuthorizeFilterProvider : IFilterProvider { // 设置较高优先级,确保在默认提供程序前执行 public int Order => -1000; public void OnProvidersExecuting(FilterProviderContext context) { // 检查Action上是否存在MyAuthorizeAttribute var hasActionFilter = context.ActionDescriptor.EndpointMetadata .OfType<MyAuthorizeAttribute>() .Any(); if (hasActionFilter) { // 移除控制器级的MyFilter实例 context.Results.RemoveAll(result => result.Filter is MyFilter && context.ActionDescriptor.ControllerTypeInfo?.GetCustomAttribute<MyAuthorizeAttribute>() != null); } } public void OnProvidersExecuted(FilterProviderContext context) { // 无需额外处理 } }
注册过滤器提供程序
在Program.cs(或Startup.cs)中注册该提供程序:
builder.Services.AddControllersWithViews(options => { options.Filters.Add<MyAuthorizeFilterProvider>(); });
效果验证
修改后,访问Home/Index时,只会执行Action级的MyFilter实例,RedirectOnFailure的值为false,会返回对应的Json结果,实现了参数覆盖的效果。
内容的提问来源于stack exchange,提问作者Kenshin
相关产品推荐
相关产品推荐

