Fluent-API to add ActionFilters to Controller in ASP.NET MVC
Note: The name of the classes and the methods are just
temporary and may change, I’m so bad when it comes to
naming classes and methods. The source code is simple and
haven’t done so much refactoring etc. Just wanted to see
if I could get it to work, so please have that in
mind.
EDIT: Working with new methods to get a better overview of Action Filters added and also reusing AcitonFitlers, you can read about it here.
When we create controllers for our ASP.NET MVC
application we can also add Action Filters to handle
cross-cutting concerns, like Authorization, Error handling
and Caching etc. If we want to have Error handling on every
controller we need to add the HandleErrorAttribute to all
controllers, like this:
[HandleError] public MyController : Controller { }
By adding Action Filters by using attributes it can be
hard to get a good overview of which controllers that has
the HandleErrorAttribute. The same regarding Action methods,
for example if we have a lot of Controllers and want to see
see all the Action methods that uses for example the
OutputCacheAttribute, we need to go through all Controllers
and methods, there is no easy way to get a simple overview
of them.
Adding Action Filters to Action methods and Controllers also add some sort of “dependency” to action filters (not a big deal, though). I decided to try a way to add Action Filters to Controllers and Action methods in one single file, so I could get a better overview of which Controllers and Action methods uses what ActionFilter etc. Because ActionFitlers contains cross-cutting concerns I also wanted to move it away as attributes from the Controllers and Action methods so developers don’t need to care about the cross-cutting concerns during the creation of Controllers. instead add them later.
I sort of used a Fluent-API for the configuration of
ActionFilters, and the configuration is added to the
Global.asax’s Application_Start event. Here is an example
where I add the ErroHandler Action Filter to all Controllers
Action Methods:
ConfigActionFilter.ConfigController<Controller>()
.AddFilterToController(new HandleErrorAttribute());
If I want to add an Action Filters to a specific
Controller I just use the type of the Controller:
ConfigActionFilter.ConfigController<HomeController>()
.AddFilterToController(new HandleErrorAttribute());
Note: The reason I don’t use XML and a XML meta data provider as a configuration is because I wanted to make the configuration type safe. If we use XML we can only get an exception if we spell something wrong at runtime.
If I want to add Action Filters to Action Methods I
can write something like this:
ConfigActionFilter.ConfigController<HomeController>()
.AddFilterToController(new HandleErrorAttribute())
.AddFilterToAction(c => c.About(),
new MyActionFilterAttribute(),
new MyOtherActionFilter() { Metadata=10 })
.AddFilterToAction(c => c.Index(),
new MyActionFilterAttribute());
I didn’t wanted to use a string for the ActionMethod (AddFilterToAction(“About”)) because it would not be type safe. I want to get a warning or error while typing the code, so instead I created an Expression. The AddFilterToAction takes a params of FilterAttributes, so I can easy add several of Action Filters to an Action Method. The AddFilterToController method will add Action Filter to the Controller, just like adding an Action Filter attribute to the class definition, so all Action Methods within the Controller will use the Action Filter.
I created a Custom ControllerFactory so I could add my
Custom ControllerActionInvoker to the Controller:
public class ActionFilterConfigControllerFactory : DefaultControllerFactory { public override IController CreateController(RequestContext requestContext, string controllerName) { ...
controllerInstance.ActionInvoker = new ActionFilerConfigControllerActionInvoker(); return controller; } }
The custom ControllerActionInvoker will make sure the Action
Filter added by using my solution is added to the FilterInfo
class. This is done by overriding the GetFilters method:
public class ActionFilerConfigControllerActionInvoker : ControllerActionInvoker { protected override FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) { var filters = base.GetFilters(controllerContext, actionDescriptor); ... if (ConfigActionFilter.Config.ContainsKey(controllerName)) AddFiltersToFilerList(actionDescriptor, filters, controllerName); return filters; }
}
This project only a fun thing to do and I like the idea of having different options to add Action Filters, and this solution will make the Controller clean from Attributes and also have one place to add them. When I create my Controller I don’t need to worry or think about the cross-cutting concerns, I just add them later and into the Global.asax.
I want to thanks
Mikael Söderström
for taking time to discuss this solution with him, and get
some feedback and also “host” the source code for me.
You
can download the source code with an example here:
http://vinkr.net/misc/ActionFilterConfig.zip