Search code examples
asp.net-core.net-6.0

Action filter does not override controller action?


I have implemented an IAsyncAuthorizationFilter/IActionFilter filter and implemented TypeFilterAttribute for the filter. When I add the attribute to both the controller and action, the action filter does not appear to override the controller level filter.

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
    }
}

The redirectOnFailure will be true for the Index action even though the filter specified false. In ASP.NET MVC, the action filter would override the controller filter. You could have a default for all actions but override specific actions with different properties/parameters. Can you not do this in Core?

[MyAuthorize]
public class HomeController : Controller
{
    [MyAuthorize(redirectOnFailure: false)]
    public IActionResult Index()
    {
        // Do work
    }   
}

Solution

  • As per the Microsoft website, filters do not override each other. They simply run one after the other in the order described in the cited document.

    Just because the same attribute is put in both the controller and the action doesn't mean that ASP.net will say "ah, you probably want to override the class-level attribute". That's just not how it works.

    If you want override logic, you need to write override logic.

    Here's a sample made for .Net 6. The magic is done by the FindEffectivePolicy() method. This sample shows how to compare the current object against the effective one and only run the logic if the comparison matches.

    public class MyFilter : IAsyncAuthorizationFilter
    {
        #region Properties
        public string Name { get; }
        #endregion
    
        #region Constructors
        public MyFilter(string name)
        {
            Name = name;
        }
        #endregion
    
        #region IAsyncAuthorizationFilter
        public Task OnAuthorizationAsync(AuthorizationFilterContext context)
        {
            var effectiveAtt = context.FindEffectivePolicy<MyFilter>();
            System.Diagnostics.Debug.Print($"Effective filter's name: {effectiveAtt?.Name}");
            System.Diagnostics.Debug.Print($"Am I the effective attribute? {this == effectiveAtt}");
            if (this == effectiveAtt)
            {
                // Do stuff since this is the effective attribute (policy).
            }
            else
            {
                // ELSE part probably not needed.  We just want the IF to make sure the code runs only once.
            }
            return Task.CompletedTask;
        }
        #endregion
    }
    

    Update 2024-07-16

    I just noticed there's a "shorthand" function called IsEffectivePolicy (docs here). I would imagine there is very little difference between the two. Just use whatever you feel it is best. This one can be used in the if() directly:

    if (context.IsEffectivePolicy(this))
    // OR
    if (context.IsEffectivePolicy<IMyMetadata>(this))