Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Fluent Filters - Global Action Filters for ASP.NET MVC 2

0.00/5 (No votes)
8 Nov 2010 1  
This article describes a small library for ASP.NET MVC, that can be used as a facility for registering global filters. It is similar to Global Filter in ASP.NET MVC 3, but with different implementation and features.
Fluent Filters

Introduction

Once in a project that was written in ASP.NET MVC 2, I needed to implement a means to register action filters for all controllers in the application, because it is not very convenient to specify the attributes for each class of controller if you want to perform the same operation globally, and as such, there is no flexibility. Later, I found mention of this only in ASP.NET MVC 3.

After that, I decided to write a similar functionality on my own, setting myself a series of requirements that had to be performed:

  1. Ease of registering a filter in the application
  2. Ability to add criteria to filter
  3. Ability to use dependency injection for filters

I have got an interesting approach and I decided to combine all this into a separate library to share it with others.

You can find the latest version of binary and source code on the project page on CodePlex. Working application examples are located in src\Samples folder of source code.

Using the Code

FluentFilters helps you to implement a global filters functionality in your ASP.NET MVC application. A global filter is a filter that is going to run for every single action on every single controller. Creating a custom filter for usage globally is very simple. All you need to do is to implement your custom filter class with one or more interfaces which are the four types of ASP.NET MVC filters:

  • IAuthorizationFilter
  • IActionFilter
  • IResultFilter
  • IExceptionFilter

For example, a filter can look like:

public class CustomFilter: IResultFilter, IAuthorizationFilter
{
    #region IResultFilter Members

    public void OnResultExecuted(ResultExecutedContext filterContext)
    {
        // Some logic here ...
    }

    public void OnResultExecuting(ResultExecutingContext filterContext) 
    {
        // Or here ...
    }

    #endregion

    #region IAuthorizationFilter Members

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        // May be here?
    }

    #endregion
}

For working with filters, you should use class FluentFiltersBuilder. This is the recommended usage if you build MVC application without Inversion of Control(IoC) container. Using FluentFilters with IoC container is preferred rather than using the FluentFiltersBuilder. In this case, you can manage creation and lifetime of filter object and use Dependency Injection. To achieve this, you need to do several steps.

  1. Implement custom class which inherits FilterRegistry class and overrides GetFilterInstance method to resolve object by type instead of creating a new one. This class will be used instead FluentFiltersBuilder for registering filters.
  2. Create a custom Controller Factory for supporting IoC container and registering it.

You can find the sample application with IoC container in src\Samples\FluentFilters.Samples.IoC folder of the source code.

For basic configuration, it is sufficient to edit Global.asax.cs file like below:

// Global.asax.cs

using System.Web.Mvc;
using System.Web.Routing;
using FluentFilters;
using FluentFilters.Criteria;
using Website.Core.Filters;
 
namespace Website
{
    public class MvcApplication : System.Web.HttpApplication
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
            routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", 
		id = UrlParameter.Optional } // Parameter defaults
            );
        }
 
        private static void RegisterFluentFilters()
        {
            // Register global filter
            FluentFiltersBuilder.Current.Add();
        }
 
        private static void RegisterControllerFactory()
        {
            // Set controller factory
            ControllerBuilder.Current.SetControllerFactory
			(new FluentFiltersControllerFactory());
        }
 
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
 
            RegisterRoutes(RouteTable.Routes);
 
            RegisterFluentFilters();
 
            RegisterControllerFactory();
        }
    }
}

You can see in the method RegisterFluentFilters registered filter BrowserDetectionFilter.

Later, when you added global filters for usage, you can set chain criteria for each of those. By criteria, you can set whether to execute filter or not. Create criteria is very simple too. You need to inherit your custom criteria class from IFilterCriteria interface and implement only method Match. The library already provides three criteria for use:

  • ActionFilterCriteria - Filter by specified action
  • AreaFilterCriteria - Filter by specified area
  • ControllerFilterCriteria - Filter by specified controller

For example, the code snippet below shows the source of the criteria ActionFilterCriteria:

public class ActionFilterCriteria : IFilterCriteria
{
    private readonly string _actionName;
 
    /// <summary>

    /// Filter by specified action
    /// </summary>
    /// <param name="actionName">Name of the action</param>

    public ActionFilterCriteria(string actionName)
    {
        _actionName = actionName;
    }
 
    #region Implementation of IActionFilterCriteria
 
    public bool Match(FilterRegistryContext context)
    {
        return string.Equals(_actionName, 
	context.ControllerContext.RouteData.GetRequiredString("action"), 
	StringComparison.OrdinalIgnoreCase);
    }
 
    #endregion

}

For each filter, you can add two chains criteria. This is the criteria that is required and which should be excluded. If criteria are not specified, the filter will be executed for the entire application.

In the code below, we added filter BrowserDetectionFilter for area "Blog" and excluded for controller "Account":

FluentFiltersBuilder.Current.Add
(c =>

{
    c.Require(new AreaFilterCriteria("Blog")); // Execute if current area "Blog"
    c.Exclude(new ControllerFilterCriteria("Account")); // Ignore if current 
						// controller "Account"

});

You can build the Chain of criteria by methods And(IFilterCriteria criteria) and Or(IFilterCriteria criteria). Methods work as conditional logical operators && and ||.

Contrived example:

registry.Add<displaytopbannerfilter>(c =>
{
    c.Require(new IsFreeAccountFilterCriteria()).Or(new AreaFilterCriteria("Blog")).
	Or(new AreaFilterCriteria("Forum")).And(new IsMemberFilterCriteria());
    c.Exclude(new AreaFilterCriteria("Administrator")).
	Or(new ControllerFilterCriteria("Account")).
	And(new ActionFilterCriteria("LogOn"));
});

If speaking in C# language, then the code above can be understood as (like pseudocode):

if( IsFreeAccountFilterCriteria() || area == "Blog" || 
	(area == "Forum" && IsMemberFilterCriteria()) ) 
{
    if(area != "Administrator")
    {
        DisplayTopBannerFilter();
    }
    else if(controller != "Account" && action != "LogOn")
    {
        DisplayTopBannerFilter();
    }
}

History

  • 4th November, 2010: Initial version
  • 5th November, 2010: Updated article

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here