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.
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:
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
ConfigActionFilter.ConfigController<Controller>()
.AddFilterToController(new HandleErrorAttribute());
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
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());
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
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 HandleErrorAttribute(),
new OutputCacheAttribute() { Duration=10, VaryByParam = "none" })
.AddFilterToAction(c => c.Index(),
new HandleErrorAttribute());
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
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;
}
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
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;
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
}
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 Michel 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
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }