7
votes

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

5 Answers

7
votes

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.

4
votes

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]).

3
votes

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;

        ...
    }
2
votes

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
2
votes

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