How to disable a global filter in ASP.Net MVC selectively
Asked Answered
G

7

81

I have set up a global filter for all my controller actions in which I open and close NHibernate sessions. 95% of these action need some database access, but 5% don't. Is there any easy way to disable this global filter for those 5%. I could go the other way round and decorate only the actions that need the database, but that would be far more work.

Gunstock answered 31/3, 2012 at 6:53 Comment(2)
What about creating another action and decorate the 5% with this. Something like NHibernateNotRequiredAttribute()?Curl
weblogs.asp.net/imranbaloch/…Creationism
N
162

You could write a marker attribute:

public class SkipMyGlobalActionFilterAttribute : Attribute
{
}

and then in your global action filter test for the presence of this marker on the action:

public class MyGlobalActionFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.ActionDescriptor.GetCustomAttributes(typeof(SkipMyGlobalActionFilterAttribute), false).Any())
        {
            return;
        }

        // here do whatever you were intending to do
    }
}

and then if you want to exclude some action from the global filter simply decorate it with the marker attribute:

[SkipMyGlobalActionFilter]
public ActionResult Index()
{
    return View();
}
Natant answered 1/4, 2012 at 8:50 Comment(9)
Really great solution. I wonder how I didn't think of this by myself. Thanks.Gunstock
This was helpful thanks. Just to help future people, you can also put the same thing on a Controller, and use filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes to apply to all actions.Ogg
anyone after api solution the syntax is slightly different: actionContext.ActionDescriptor.GetCustomAttributes<SkipMyGlobalActionFilter>().Any()Loire
And if you also want to skip specific controller attribute then you should add || in the condition filterContext.ControllerContext.ControllerDescriptor.GetCustomAttributes<SkipMyGlobalActionFilter>().Any()Loire
like Leniel wrote, in ASP.NET Core, filterContext.ActionDescriptor doens't have the GetCustomAttributes method. How to do it in ASP.NET core?Incisive
You do it this way. context.ActionDescriptor.GetType().GetCustomAttributes (typeof (SkipMyGlobalActionFilterAttribute)Blossom
I couldn't use .Any().Instead, I replaced it with .Length > 0Hindemith
this is how I did it in .net core: https://mcmap.net/q/260359/-get-custom-attributes-via-actionexecutingcontext-from-controller-net-coreLaager
You can use this for .net core. if (context.Filters.Any(x => x.GetType() == typeof(SkipMyGlobalActionFilterAttribute))) { base.OnActionExecuted(context); return; } Before you must change with SkipMyGlobalActionFilterAttribute :ActionFilterAttributeInstitutor
H
18

Though, the accepted answer by Darin Dimitrov is fine and working well but, for me, the simplest and most efficient answer found here.

You just need to add a boolean property to your attribute and check against it, just before your logic begins:

public class DataAccessAttribute: ActionFilterAttribute
{
    public bool Disable { get; set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (Disable) return;

        // Your original logic for your 95% actions goes here.
    }
}

Then at your 5% actions just use it like this:

[DataAccessAttribute(Disable=true)]
public ActionResult Index()
{            
    return View();
}
Hindemith answered 16/5, 2018 at 9:10 Comment(1)
One little "gotcha" I've noticed here - if you specify an Order in the global declaration of the filter, you must also specify the same order on on the attribute. Eg [DataAccessAttribute(Disable=true,Order=2)] and filters.Add(new DataAccessAttribute(), 2);Barchan
D
9

In AspNetCore, the accepted answer by @darin-dimitrov can be adapted to work as follows:

First, implement IFilterMetadata on the marker attribute:

public class SkipMyGlobalActionFilterAttribute : Attribute, IFilterMetadata
{
}

Then search the Filters property for this attribute on the ActionExecutingContext:

public class MyGlobalActionFilter : IActionFilter
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (context.Filters.OfType<SkipMyGlobalActionFilterAttribute>().Any())
        {
            return;
        }

        // etc
    }
}
Distend answered 22/11, 2018 at 13:30 Comment(1)
So much cleaner. Works for attributes set on action as well as controller.Hemidemisemiquaver
G
4

At least nowadays, this is quite easy: to exclude all action filters from an action, just add the OverrideActionFiltersAttribute.

There are similar attributes for other filters: OverrideAuthenticationAttribute, OverrideAuthorizationAttribute and OverrideExceptionAttribute.

See also https://www.strathweb.com/2013/06/overriding-filters-in-asp-net-web-api-vnext/

Gunzburg answered 20/2, 2018 at 13:1 Comment(1)
Nowadays being 2013+? Not sure why this isn't the accepted answer as it seems canonical.Rauscher
M
2

Create a custom Filter Provider. Write a class which will implement IFilterProvider. This IFilterProvider interface has a method GetFilters which returns Filters which needs to be executed.

public class MyFilterProvider : IFilterProvider
{
        private readonly List<Func<ControllerContext, object>> filterconditions = new List<Func<ControllerContext, object>>();
        public void Add(Func<ControllerContext, object> mycondition)
        {
            filterconditions.Add(mycondition);
        }

        public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
        {
            return from filtercondition in filterconditions
                   select filtercondition(controllerContext) into ctrlContext
                   where ctrlContext!= null
                   select new Filter(ctrlContext, FilterScope.Global);
        }
}

=============================================================================
In Global.asax.cs

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            MyFilterProvider provider = new MyFilterProvider();
            provider.Add(d => d.RouteData.Values["action"].ToString() != "SkipFilterAction1 " ? new NHibernateActionFilter() : null);
            FilterProviders.Providers.Add(provider);
        }


protected void Application_Start()
{
    RegisterGlobalFilters(GlobalFilters.Filters);
}
Maihem answered 7/6, 2013 at 15:15 Comment(0)
I
2

Well, I think I got it working for ASP.NET Core.
Here's the code:

public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // Prepare the audit
        _parameters = context.ActionArguments;

        await next();

        if (IsExcluded(context))
        {
            return;
        }

        var routeData = context.RouteData;

        var controllerName = (string)routeData.Values["controller"];
        var actionName = (string)routeData.Values["action"];

        // Log action data
        var auditEntry = new AuditEntry
        {
            ActionName = actionName,
            EntityType = controllerName,
            EntityID = GetEntityId(),
            PerformedAt = DateTime.Now,
            PersonID = context.HttpContext.Session.GetCurrentUser()?.PersonId.ToString()
        };

        _auditHandler.DbContext.Audits.Add(auditEntry);
        await _auditHandler.DbContext.SaveChangesAsync();
    }

    private bool IsExcluded(ActionContext context)
    {
        var controllerActionDescriptor = (Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)context.ActionDescriptor;

        return controllerActionDescriptor.ControllerTypeInfo.IsDefined(typeof(ExcludeFromAuditing), false) ||
               controllerActionDescriptor.MethodInfo.IsDefined(typeof(ExcludeFromAuditing), false);
    }

The relevant code is in the 'IsExcluded' method.

Incisive answered 12/9, 2017 at 11:18 Comment(0)
C
1

You can change your filter code like this:

 public class NHibernateActionFilter : ActionFilterAttribute
    {
        public IEnumerable<string> ActionsToSkip { get; set; }

        public NHibernateActionFilter(params string[] actionsToSkip)
        {
            ActionsToSkip = actionsToSkip;
        }

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (null != ActionsToSkip && ActionsToSkip.Any(a => 
String.Compare(a,  filterContext.ActionDescriptor.ActionName, true) == 0))
                {
                    return;
                }
           //here you code
        }
    }

And use it:

[NHibernateActionFilter(new[] { "SkipFilterAction1 ", "Action2"})]
Cyn answered 1/4, 2012 at 15:11 Comment(2)
That's one way to do it, but a little difficult to maintain over time.Gunstock
If you change your action name and forget to change it in the attribute usage, the compiler won't warn you. This is very likely to cause maintenance pains. I prefer Darin's answer since you don't have to specify the actions manually.Thralldom

© 2022 - 2024 — McMap. All rights reserved.