ASP.NET MVC 3 Preview 1 – Global and Conditional Filters

Posted by: Malcolm Sheridan , on 9/22/2010, in Category ASP.NET MVC
Views: 35083
Abstract: The following article demonstrates how to use global filters in ASP.NET MVC 3 Preview 1 and also how to use them conditionally.
Another great feature to be introduced into ASP.NET MVC 3 Preview 1 is global filters. Global filters are a way of registering controller or action level filters in the one place. The advantage of this is you no longer need to decorate every controller or action with an attribute that you wanted executed whenever it ran, now you can register is one place and have it executed across multiple controllers and action. To see this in action, you'll need to download ASP.NET MVC 3 Preview 1 which can be found here. For beta software, I always recommend installing this on a virtual machine, just in case something goes wrong.
Open studio 2010 and create a new ASP.NET MVC 3 Web Application (Razor) project. This example doesn’t get into the Razor syntax, but that is the default view engine I am using currently. Let’s begin with creating a new action filter that sends debug data to the HttpResponse object that prints to the screen diagnostic information such as what controller was called, what action was called and how long the code took to execute. I’ve created a class called DebugTimerAttribute. Here’s the code below:
public class DebugTimerAttribute : ActionFilterAttribute
{
readonly Stopwatch _startWatch = new Stopwatch();
       public override void OnActionExecuting(ActionExecutingContext filterContext)
       {
            _startWatch.Start();
       }
 
       public override void OnResultExecuted(ResultExecutedContext filterContext)
       {
            _startWatch.Stop();
            filterContext.HttpContext.Response.Write(string.Format("Controller ({0}) Action ({1}) Ellapsed Time ({2})",
                filterContext.RouteData.Values["controller"],
                filterContext.RouteData.Values["action"],
                _startWatch.ElapsedMilliseconds));
       }
}
 
There’s nothing special about the code above. What is special is how I can now use this. Currently in ASP.NET MVC 1 and 2, I’d need to decorate my controller’s or action’s if I wanted to use this filter:
 
[DebugTimer]
public class HomeController : Controller
{  
        public ActionResult Index()
        {
            return View();
        }
}
 
There’s no issue in doing this. What can happen is you have multiple controllers and action and need this filter applied to all of them. If you didn’t have a base controller, you’d have a tonne of refactoring to do. Thankfully in ASP.NET MVC 3 Preview 1, you can now use global filters. This give you the power to register to filter in one spot and have it applied to all controller’s and actions. To use global filters, open the global.asax file. You’ll find a new static method called RegisterGlobalFilters. This registers filters that you want to apply to all controllers and actions. This is how I’ll add my new filter:
 
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
       filters.Add(new DebugTimerAttribute());           
}
 
 
The code above registers both filters, so when you run the page, you’ll see the debug information printed out on the screen.
 
image_2
 
That’s great if you wanted it applied to all controllers and actions. But what if you wanted it applied only on certain conditions, such as when you’re debugging you need this information, but when you deploy to production, you don’t want your users seeing that. This is when you need to create a custom filter provider. Your class has to implement IFilterProvider and you place your login inside of the GetFilters method. Here’s one way to implement a custom filter provider:
 
public class DebugFilterProvider : IFilterProvider
{
        private readonly List<Func<ControllerContext, object>> _conditions = new List<Func<ControllerContext, object>>();
        public void Add(Func<ControllerContext, object> condition)
        {
            _conditions.Add(condition);
        }
 
        public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
        {
            return from condition in _conditions
                   select condition(controllerContext) into filter
                   where filter != null
                   select new Filter(filter, FilterScope.Global);
        }
}
 
 GetFilters loops through all the conditions, which is a generic list which contains a delegate, and if that delegate is not null, then it will create a new Filter object and return that. So in your global.asax file, you can add this code so this filter only runs when you have debugging enabled:
 
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());           
       var provider = new DebugFilterProvider();
       provider.Add(c => c.HttpContext.IsDebuggingEnabled ? new DebugTimerAttribute() : null);
       FilterProviders.Providers.Add(provider);
}
 
Or if you only wanted it applied to an action named About, you’d do this:
 
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
       var provider = new DebugFilterProvider();
       provider.Add(c => c.RouteData.Values["action"].ToString() == "About" ? new DebugTimerAttribute() : null);
FilterProviders.Providers.Add(provider);
}
 
Global filters are a nice way to centralise functionality in your project.
 
The entire source code of this article can be downloaded over here
Give a +1 to this article if you think it was well written. Thanks!
Recommended Articles
Malcolm Sheridan is a Microsoft awarded MVP in ASP.NET, a Telerik Insider and a regular presenter at conferences and user groups throughout Australia and New Zealand. Being an ASP.NET guy, his focus is on web technologies and has been for the past 10 years. He loves working with ASP.NET MVC these days and also loves getting his hands dirty with jQuery and JavaScript. He also writes technical articles on ASP.NET for SitePoint and other various websites. Follow him on twitter @malcolmsheridan


Page copy protected against web site content infringement by Copyscape


User Feedback
Comment posted by Maxim on Wednesday, September 22, 2010 8:39 AM
Thanks for post. It was very interesting.
Comment posted by TR on Tuesday, May 10, 2011 11:09 AM
What if I want to apply to multiple controllers or Actions? I am not able to do this.

Post your comment
Name:  
E-mail: (Will not be displayed)
Comment:
Insert Cancel