9

Is there a way to add an Attribute on the Controller level but not on a specific action. For example say if i had 10 Actions in my Controller and just 1 of those Actions does not require a specific attribute I created.

[MyAttribute]
public class MyController : Controller
{
    public ActionResult Action1() {}
    public ActionResult Action2() {}

    [Remove_MyAttribute]
    public ActionResult Action3() {}
}

I could potentially move this Action into another controller (but dont like that) or I could apply the MyAttribute to all actions except from Action3 but just thought if there is an easier way?

5 Answers 5

9

I know my answer is a little late (almost four years) to the game, but I came across this question and wanted to share a solution I devised that allows me to do pretty much what the original question wanted to do, in case it helps anyone else in the future.

The solution involves a little gem called AttributeUsage, which allows us to specify an attribute on the controller (and even any base controllers!) and then override (ignore/remove) on individual actions or sub-controllers as needed. They will "cascade" down to where only the most granular attribute actually fires: i.e., they go from least-specific (base controllers), to more-specific (derived controllers), to most-specific (action methods).

Here's how:

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method, Inherited=true, AllowMultiple=false)]
public class MyCustomFilterAttribute : ActionFilterAttribute
{

    private MyCustomFilterMode _Mode = MyCustomFilterMode.Respect;        // this is the default, so don't always have to specify

    public MyCustomFilterAttribute()
    {
    }
    public MyCustomFilterAttribute(MyCustomFilterMode mode)
    {
        _Mode = mode;
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (_Mode == MyCustomFilterMode.Ignore)
        {
            return;
        }

        // Otherwise, respect the attribute and work your magic here!
        //
        //
        //
    }

}

public enum MyCustomFilterMode
{
    Ignore = 0,
    Respect = 1
}

(I heard you like attributes, so I put some attributes on the attribute! That's really what makes the magic work here at the very top: Allowing them to inherit/cascade, but only allowing one of them to execute.)

Here's how it is used now:

[MyCustomFilter]
public class MyBaseController : Controller
{
    // I am the application's base controller with the filter,
    // so any derived controllers will ALSO get the filter (unless they override/Ignore)
}

public class HomeController : MyBaseController
{
    // Since I derive from MyBaseController,
    // all of my action methods will also get the filter,
    // unless they specify otherwise!

    public ActionResult FilteredAction1...
    public ActionResult FilteredAction2...

    [MyCustomFilter(Ignore)]
    public ActionResult MyIgnoredAction...    // I am ignoring the filter!

}

[MyCustomFilter(Ignore)]
public class SomeSpecialCaseController : MyBaseController
{
    // Even though I also derive from MyBaseController, I can choose
    // to "opt out" and indicate for everything to be ignored

    public ActionResult IgnoredAction1...
    public ActionResult IgnoredAction2...

    // Whoops! I guess I do need the filter on just one little method here:
    [MyCustomFilter]
    public ActionResult FilteredAction1...

}

I hope this compiles, I yanked it from some similar code and did a little search-and-replace on it so it may not be perfect.

Sign up to request clarification or add additional context in comments.

3 Comments

P.S., the advantage with this approach in my opinion, which I forgot to explicitly state, is that you don't have magic strings representing action names, nor need to remember to keep the mapping up-to-date at the top of everything: you just decorate as you go, flipping the Ignore/Respect switch back and forth as needed the further down you go. Good luck!
I tried implementing this, but even if i put [ValidateStandardUser(ValidationMode.Ignore)] in my method, i keep getting ValidationMode.Enforce in my attribute, and i am unable to skip it...any ideas why?
4

You have to override/extend the default attribute and add a custom constructor to allow exclusion. Or you can create your custom attribute for exclusion (in your example is the [Remove_MyAttribute]).

1 Comment

thanks for the point in the right direction. ive upvoted your answer then provided the solution in my answer below.
3

Johannes gave the correct solution and here is how I coded it... hope it helps other people.

[MyFilter("MyAction")]
public class HomeController : Controller
{
    public ActionResult Action1...
    public ActionResult Action2...
    public ActionResult MyAction...
}

public class CompressFilter : ActionFilterAttribute
{
    private IList _ExcludeActions = null;

    public CompressFilter()
    {
        _ExcludeActions = new List();
    }

    public CompressFilter(string excludeActions)
    {
        _ExcludeActions = new List(excludeActions.Split(','));
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        HttpRequestBase request = filterContext.HttpContext.Request;

        string currentActionName = (string)filterContext.RouteData.Values["action"];

        if (_ExcludeActions.Contains(currentActionName))
            return;

        ...
    }

Comments

2

You could exclude a specific action by passing it to the main attribute:

 [MyAttribute(Exclude="Action3")]

EDIT

My example was from the head (as you can see the following is VB.NET, maybe that's where it went wrong), this is how I implemented:

<Models.MyAttribute(Exclude:="Action3")> _
Public Class MyController
Inherits System.Web.Mvc.Controller

End Class

Comments

2

The usual pattern for what you are trying to do is to have and attribute with a boolean parameter that indicates if the attribute is applied or not.

Ex:

[ComVisible] which is equivalent with [ComVisible(true)]

or 

[ComVisible(false)]

inf your case you would have:

[MyAttribute] // defaults to true

and

[MyAttribute(false)] for applying the attribute on excluded members

2 Comments

So you define it at the Controller level then effectively override it on the Action itself... i like that idea and is simpler than comparing it to action names. thanks for your solution.
I am using RenderAction from a Helper method which the MyAttribute(false) is defined on however when I debug eventhough it calls the correct constructor the private variable inside my attribute keeps getting reset. Any ideas?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.