Exercise 2: Creating a Global Dynamic Filter
Exercise 2: Creating a Global Dynamic Filter

In this exercise you will learn how to create a global dynamic filter that will get executed or not depending on the context. This means that you could set any filter as global, but use it only for the controllers or actions that satisfy a certain condition. For that purpose you will implement GetFilters method in your custom filter provider to add your own logic behind the filter retrieval.

Task 1 – Creating a Filter Provider

In this task you learn how custom Filter Providers manage the global behavior of any MVC Filter. You will create a custom Filter Provider that stores the controller actions to log and applies the filter when an action is in the list.

  1. Open Begin solution from \Ex02 – Global Dynamic Filter\Begin
  2. Create a new C# class in Filters folder and rename it ActionLogFilterProvider.cs
  3. Add the reference to the System.Web.Mvc namespace
    C#Copy Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    
    namespace MvcMusicStore.Filters
    {
        public class ActionLogFilterProvider
        {
        }
    }
    

  4. Make ActionLogFilterProvider implement the interface IFilterProvider
    C#Copy Code
    public class ActionLogFilterProvider : IFilterProvider
    

  5. Define the internal class ControllerAction inmediatly after ActionLogFilterProvider class definition that will have a controller and action names as the members.

    (Code Snippet – ASP.NET Global and Dynamic Action Filters – Ex2 ControllerAction – CSharp)

    C#Copy Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    
    namespace MvcMusicStore.Filters
    {
        public class ActionLogFilterProvider
        {
        }
    
        internal class ControllerAction
        {
            internal string ControllerName { get; set; }
            internal string ActionName { get; set; }
        }
    }
    

  6. Then define a list of ControllerAction elements as new ActionLogFilter property:

    (Code Snippet – ASP.NET Global and Dynamic Action Filters – Ex2 Actions List – CSharp)

    C#Copy Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    
    namespace MvcMusicStore.Filters
    {
        public class ActionLogFilterProvider : IFilterProvider
        {
            private IList<ControllerAction> actions = new List<ControllerAction>();
        }
    
        internal class ControllerAction
        {
            internal string ControllerName { get; set; }
            internal string ActionName { get; set; }
        }
    }
    

  7. Create an Add method to ActionLogFilterProviderClass to store in the list all the controllers and actions that will be logged:

    (Code Snippet – ASP.NET Global and Dynamic Action Filters – Ex2 Add Method – CSharp)

    C#Copy Code
    …
    private IList<ControllerAction> actions = new List<ControllerAction>();
    
    public void Add(string controllername, string actionname)
    {
        actions.Add(new ControllerAction() { ControllerName = controllername, ActionName = actionname });
    }

  8. Implement in ActionLogFilterProvider.cs class the method GetFilters that will read the list of controller actions and evaluate which ones apply to the filter.

    (Code Snippet – ASP.NET Global and Dynamic Action Filters – Ex2 GetFilters – CSharp)

    C#Copy Code
    public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        foreach (ControllerAction action in actions)
            if ((action.ControllerName == actionDescriptor.ControllerDescriptor.ControllerName || action.ControllerName == "*")
                && (action.ActionName == actionDescriptor.ActionName || action.ActionName == "*")) {
                    yield return new Filter(new ActionLogFilterAttribute(), FilterScope.First, null);
                break;
            }
    
        yield break;
    }
    

    Note:
     This implementation of GetFilters method checks if the controller name and the action name are in the list and returns a new instance of the custom action filter class ActionLogFilterAttribute. You could also use the Controller Context parameter to add more complex logic to this method.
    The "*" will be used as a wildcard for not filtering by a controller name or an action name. You can find ControllerContext class reference in this article at msdn.


  9. Your filter provider class should finally look like this:
    C#Copy Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    
    namespace MvcMusicStore.Filters
    {
        public class ActionLogFilterProvider : IFilterProvider
        {
            private IList<ControllerAction> actions = new List<ControllerAction>();
    
            public void Add(string controllername, string actionname)
            {
                actions.Add(new ControllerAction() { ControllerName = controllername, ActionName = actionname });
            }
    
            public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
            {
                foreach (ControllerAction action in actions)
                    if ((action.ControllerName == actionDescriptor.ControllerDescriptor.ControllerName || action.ControllerName == "*")
                        && (action.ActionName == actionDescriptor.ActionName || action.ActionName == "*"))
                    {
                        yield return new Filter(new ActionLogFilterAttribute(), FilterScope.First, null);
                        break;
                    }
    
                yield break;
            }
        }
    
        internal class ControllerAction
        {
            internal string ControllerName { get; set; }
            internal string ActionName { get; set; }
        }
    }
    

    In the following step you will add this provider to the global filter provider collection.


Task 2 – Registering a Global Filter

In this task you will register a custom filter provider as global in the Global.asax.cs.

  1. Add an instance of your filter provider into MVC FilterProvider collection, in the method RegisterGlobalFilters at Global.asax.cs class:

    (Code Snippet – ASP.NET Global and Dynamic Action Filters – Ex2 LogProviders – CSharp)

    C#Copy Code
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
        ActionLogFilterProvider provider = new ActionLogFilterProvider();
        provider.Add("Account", "LogOn");
        provider.Add("Store", "*");
        provider.Add("*", "Index");
        FilterProviders.Providers.Add(provider);
    }
    
    

    Note:
    ActionLogFilterProvider Add method receives the controller name and the action name parameters of the elements that will perform logging. In this solution the elements are:
    1. “Account” ,“LogOn”: By sending the controller and the view you add logging to a specific functionality
    2. “Store”, “*”: Using the wildcard “*” at action name, All the actions from “Store” are logged.
    3. “*”, “index”: Using the wildcard “*” at controller name, All actions “index” at any controller are logged.

Task 3 – Running the application

In this task you will check that the controllers added to the filter provider list are actually performing the log.

  1. Press F5 to run the application
  2. Browse to /ActionLog to see the initial status of the log:

    Figure 3
    ActionLogView Initial state


  3. Browse /StoreManager to see the LogOn view, which is selected for the log filter
  4. Browse /Account/Register view, which is not selected for the log filter.
  5. Browse /ActionLog and press F5 to see the logged information:

    Figure 4
    ActionLogView: logged activity only for the selected views


Summary