On Filters in MVC3 April, 2011
I've been digging into the new filter functionality in MVC3 and this is a brain dump of what I've learned and some thoughts.
Filters currently must implement one or more of these interfaces: IActionFilter, IResultFilter, IExceptionFilter, and IAuthorizationFilter. The IMvcFilter interface allows you to tack on some additional metadata like the sort order and multiplicity. There are a couple of convenience classes that make creating filter attributes easier: FilterAttribute, ActionFilterAttribute, AuthorizeAttribute, HandleErrorAttribute and a few more. FilterAttribute implements IMvcFilter and so provides that metadata out of the box. When deriving from any of these attribute classes its important to understand that disallowing multiple instances of a filter is accomplished by applying the
Originally filters (Which is a terrible name BTW. What do they filter??) were applied as .NET attributes to actions or controllers. At the beginning of the request, the controller and action filter attributes were pulled from the action method and controller class and run. One thing to note is that .NET attributes are only created by the CLR once (they are essentially singletons) so every time you reflect them in a particular app domain you will get the same object instances. I think the whole attribute approach for filters is badly implemented. One of the problems is that if you want to inject dependencies into an filter you have to rig up property injection. Additionally since they are attributes, and essentially singletons, they cannot hold any request specific state. This may be fine for something like authentication but difficult for transaction management as you will not be able to use IoC to inject request specific dependencies. I think a better design would have been to make the attributes more like WCF behaviors where the attribute itself is not the filter but simply acts as a way to create the filter. But that's neither here nor there.
Now moving on to MVC3; things have changed a bit. We now have this concept of the IFilterProvider interface. This interface has one method, GetFilters() which as you can probably guess returns filters. Filter providers are now the source of all filters. There are three filter providers registered out of the box:
- GlobalFilters.Filters: This is simply a place where you can ad-hoc register individual filters that will be applied globally. These should be stateless as they will be reused for each request.
- FilterAttributeFilterProvider: This provider uses reflection to pull filters applied as attributes to controller classes and action methods; it basically does the job that the pre MVC3 code did. So the filters that this provider returns are not global, they are scoped to the controller and action. Since they are attributes they are essentially singletons and will be reused for each request so they should be stateless. Also keep in mind that the FilterAttributeFilterProvider will only discover attributes that derive from FilterAttribute. So deriving from Attribute and then implementing a filter interface will not get your attribute picked up by this provider.
- ControllerInstanceFilterProvider: This provider allows the controller servicing the request to act as a filter (ee gads!).
MVC calls the FilterProviders.Providers.GetFilters() method, to aggregate all the filters from all the filter providers, for each individual request. You can also register your own filter providers by adding an instance of a type that implements IFilterProvider to the FilterProviders.Providers collection. FilterProviders wrap filters in the Filter class which adds some additional information such as the scope and order. You can override the order in the Filter class constructor if you are writing your own filter provider or extending an existing one. If you don't pass in an order it will check and see if the filter implements IMvcFilter and get the order from there otherwise it defaults to -1. One interesting thing about the code that aggregates all these is that it removes duplicate filters for those filters that do not allow duplicates (Exposed by the IMvcFilter.AllowMultiple property). The way it determines which ones to remove is by sorting the filters by the order and then by the scope and preserving the first instance it encounters and rejecting the subsequent instances. This means that higher orders win and then within an order, narrower scopes will win (Which is actually a higher scope number).
This post demonstrates how to pull global filters from your favorite IoC container.