PublicAuthorizationFilterProvider

The final demo that I gave at the Perth .NET Community of Practice last week was creating a filter provider (code below). In MVC a “filter” is a class that executes some code either before or after your action method (or before/after your result, or when an error occurs but that is starting to get away from the point :p). Historically these have been attributes that get applied to the action method itself or to the controller:

[HandleErrors] // <-- Filter added at the controller level
public class HomeController : Controller
{
    [Authorize] // <-- Filter added at the action method level
    public ActionResult Index()
    {
        return View();
    }
}

This is all well and good but it means that if you want to have functionality across the entire site you have to get creative. A common way of dealing with the problem is to create a base controller which has all of the common filters on it and then inherit from that. That gets messy very quickly, especially when you start to have special cases and conditional logic dictating which filters to apply.

We had just such an issue on our current project. We had a simple condition that seemed innocuous enough at the time but caused us grief in practice:

All methods require the user to be authorized unless we explicitly say they are public

We solved this by creating our own uber-filters (called PostFilters and GetFilters respectively) and having a unit test to ensure we had one or the other. These master-filters are able to turn authorization on or off with a flag and are annoyingly complex internally (I don’t recommend this approach at all because the Filter execution mechanism provides some things that we didn’t such as robust exception handling, short-circuit evaluation, …):

public class DemoController {
    [GetFilters(Authorized = false)]
    public ActionResult Foo() { ... }

    [HttpPost, PostFilters(Authorized = false)]
    public ActionResult Foo(FormCollection form) { ... }
}

This gets tedious pretty quickly and is error prone. Luckily MVC 3 has a better way in the form of Filter Providers. As you might guess from the name, a filter provider is a class that determines which filter should be applied to an action method. Our original requirement was that all methods be private unless explicitly declared public. The convention in MVC works the other way but we can easily create a Filter Provider that reverses that convention. To start with we’ll need an Attribute to mark action methods (and whole controllers) as being public:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class PublicAttribute : Attribute { }

Next we'll need our Filter Provider which will check for the presence of a PublicAttribute (on the method or the controller) and if it doesn't find it, it will add an AuthorizeAttribute filter:

class PublicAttributeFilterProvider : IFilterProvider
{
    public IEnumerable GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        var isPublicMethod = actionDescriptor.IsDefined(typeof(PublicAttribute), true);
        var isPublicController = actionDescriptor.ControllerDescriptor.IsDefined(typeof(PublicAttribute), true);
        if(!isPublicMethod && !isPublicController)
            yield return new Filter(new AuthorizeAttribute(), FilterScope.Global, null);
    }
}

There's really only the one 4-line method here. Lines 1 and 2 we check for the PublicAttribute on the action method and the controller respectively. Then if neither the action or the controller are marked as public we create a new Filter and return it (to be applied against our action method). The Filter constructor takes 3 arguments. The first is a class that contains the actual logic of the filter (in this case an AuthorizeAttribute), the 2nd and 3rd are the Scope and Order respectively. These 2 parameters are used to determine the order in which Filters are run (more on that in a later post).

The last thing we need to do is register our filter provider so the MVC runtime knows to call it. Somewhere within the Application_Start method of your global.asax.cs file add the following to register the filter provider:

FilterProviders.Providers.Add(new PublicAttributeFilterProvider());

Before running the app remember to mark any login/register pages you might have as being Public (or your app will go into a redirect-loop trying to get to a login page that you aren't authorized to see). And presto, every action method you add will now require Authorization by default. Even better, if you mark a whole controller as being Public you can still apply an AuthorizeAttribute at the individual action method level.

There are 3 Filter Providers already registered by default in a new MVC application that are used to provide the default behaviour:

  • ControllerInstanceFilterProvider - Adds the controller itself as a filter. Any filter methods supplied by the controller get run before any other filters.
  • FilterAttributeFilterProvider - Supplies the filters that come from the standard attributes. You can even create custom attributes and this provider will pick them up as long as they implement the correct interfaces
  • GlobalFilters.Filters - Actually an implmentation of GloablFilterCollection, this collection will just spit back out any filters you put into it. In effect, every action method will get all of the filters you add
mvc3
Posted by: Mike Minutillo
Last revised: 27 May, 2011 03:42 PM History

Comments

No comments yet. Be the first!

No new comments are allowed on this post.