Configurable Global Action Filters for ASP.NET MVC

Tuesday, January 18, 2011

ASP.NET MVC 3.0 introduces global action filters - an easy way to apply an action filter to every action in an MVC application. All you need to do is register the filters during application startup:

protected void Application_Start()
{
    ...
    GlobalFilters.Filters.Add(new HandleErrorAttribute());
    GlobalFilters.Filters.Add(new FooFilter());
    GlobalFilters.Filters.Add(new BarFilter());
    ...
}

But what if you wanted to add (or remove) filters through configuration?

<configSections>
  <section name="filters" 
           type="ConfigurableFilters.FiltersSettings, AssemblyName "/> 
</configSections>
...
<filters>
  <add type="System.Web.Mvc.HandleErrorAttribute, System.Web.Mvc..." />
  <add type="ConfigurableFilters.BarFilter, AssemblyName" />
  <add type="ConfigurableFilters.FooFilter, AssemblyName" />
</filters>

In that case you'll need a ConfigurationElement.

public class FilterAction : ConfigurationElement
{
    [ConfigurationProperty("type", IsRequired = true, IsKey = true)]
    public string Type 
    {
        get { return base["type"] as string; }
        set { base["type"] = value; }
    }
}

And a ConfigurationElementCollection.

public class FilterActionCollection : ConfigurationElementCollection
{       
    protected override ConfigurationElement CreateNewElement()
    {
        return new FilterAction();
    }

    protected override object GetElementKey(ConfigurationElement element)
    {            
        return ((FilterAction) element).Type;
    }
}

And a ConfigurationSection.

public class FiltersSettings : ConfigurationSection
{
    public static FiltersSettings Settings
    {
        get
        {
            var section = ConfigurationManager.GetSection("filters")
                          as FiltersSettings;
            return section ?? new FiltersSettings();                
        }
    }

    [ConfigurationProperty("", IsDefaultCollection = true)]
    public FilterActionCollection Filters
    {
        get
        {
            return base[_filtersProperty] as FilterActionCollection;
        }
        set
        {
            base[_filtersProperty] = value;
        }
    }

    private readonly ConfigurationProperty _filtersProperty = 
        new ConfigurationProperty(
            null, typeof (FilterActionCollection), null, 
            ConfigurationPropertyOptions.IsDefaultCollection);
}

One way to apply the configured filters is to use the following code during application startup:

var filters = FiltersSettings.Settings.Filters;
foreach (var filter in filters.Cast<FilterAction>())
{
    var filterType = Type.GetType(filter.Type);
    GlobalFilters.Filters.Add(Activator.CreateInstance(filterType));
}

Another approach is to implement a filter provider. We'll take a look at the provider approach tomorrow.


Comments
gravatar Domenic Denicola Tuesday, January 18, 2011
Noteworthy related reading:

bradwilson.typepad.com/...

Strangely enough ASP.NET MVC 3 doesn't use service location for global filters, so you do need stuff like this. (Which is sad, because I hate those configuration-related classes.) But I think manually locating them might be better:

public interface IGlobalFilter {}

...

var filters = ServiceLocator.Current.GetAllInstances<IGlobalFilter>();

foreach (var filter in filters)
{
GlobalFilters.Filters.Add(filter);
}

...

Then configure your DI container to map IGlobalFilter to the appropriate set of types. Modify code to fit if you're not using the Comon Service Locator API.
gravatar scott Tuesday, January 18, 2011
@Domenic - yes, this is the subject of tomorrow's post (and the day after, too, actually).
gravatar Jeff Friday, January 28, 2011
Ah, haven't seen filters used in this way yet. Given this, your previous post makes a lot more sense now. Cool, thanks :)
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!