You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 03:30:54