First of all, kudos to Scott Gu and the rest of the .NET/MVC team for doing a great job on ASP.NET MVC thus far.  Today I just upgraded a project of mine to Preview 4 and so far so good.

When I first started working with ASP.NET MVC and its built in Action Filtering capability -  which are implemented via Attributes on both the Action methods and the Controller class - I began to run into many situations where a particular filter of mine applied to the majority of actions in my controller, but not necessarily all of them.  One of my pet peeves is code clutter, and I couldn't find it in myself to tag 25 actions with the same filter attribute while leaving those other 3 actions untouched.  I really wanted to just put it on the controller class and somehow tag those 3 actions to ignore my controller-wide filter.

Thus was born my ExceptFilterAttribute class, as shown below.  This filter serves as a base class to a lot of my other filters that gives me the ability to declare actions to which the filter should not apply.  What this allows me to do is to put my filter on the controller class to satisfy those 25 actions I wish to target, and to specify the 3 actions I wish to leave untouched.  While this may incur a small amount of CPU overhead on all actions, it's almost negligent when weighed against its convenience.

using System;
using System.Linq;
using System.Web.Mvc


    public class ExceptFilterAttribute : ActionFilterAttribute
    {
        public string Except { get; set; }

        public bool IsException(ActionExecutingContext filterContext)
        {
            if (Except != null)
            {
                string[] exceptions = Except.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                if (exceptions.Contains(filterContext.ActionMethod.Name))
                    return true;
                else
                    return false;
            }

            return false;
        }
    }

Subclasses take advantage of the ExceptFilterAttribute class by calling its IsException method in an overridden OnActionExecuting event handler.  Below is an example of my own custom RequiresAuthenticationAttribute class that is a subclass of ExceptFilterAttribute:

public class RequiresAuthenticationAttribute : ExceptFilterAttribute
    {
        private bool _redirect = true;
        public bool Redirect
        {
            get { return _redirect; }
            set { _redirect = value; }
        }

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

            //redirect if not authenticated
            if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                //use the current url for the redirect
                string redirectOnSuccess = filterContext.HttpContext.Request.Url.AbsolutePath;

                //send them off to the login page
                string redirectUrl = string.Format("?ReturnUrl={0}", redirectOnSuccess.URLEncode());
                string loginUrl = FormsAuthentication.LoginUrl + redirectUrl;
                filterContext.Cancel = true;
                if (Redirect)
                {
                    filterContext.HttpContext.Response.Redirect(loginUrl, true);
                }
            }
        }
    }

Notice the call to IsException and if it returns true, the filter returns and doesn't execute its primary code.  If your filter handles OnActionExecuted, OnResultExecuting, or OnResultExecuted and you want to implement the ExceptFilterAttribute functionality, simply implement the OnActionExecuting event handler and if IsExecuting returns true, then set the OnActionExecutingContext's Cancel property to true to cancel the filter execution altogether.

Below is an example controller using the RequiresAuthenticationAttribute.  Each action that is to be an exception is put into a comma-delimited string and is case-sensitive.

[RequiresAuthentication(Except = "Login,Logout")]
public class MyController : Controller
{
    public ActionResult MyProtectedAction1()
    { ... }

    public ActionResult MyProtectedAction2()
    { ... }

    public ActionResult MyProtectedAction3()
    { ... }

    public ActionResult Login()
    { ... }

    public ActionResult Logout()
    { ... }
}

The ExceptFilterAttribute class is attached.  It's small and not much to it, but certainly has proven useful for me.

ExceptFilterAttribute.cs (559.00 bytes)