As you know ActionFilters allow to execute your code when action is executed. It is very convenient extension point. But what makes it painful – is how to provide dependencies into your custom ActionFilter. There are some solutions for it. E.g. here mentioned solution for constructor injection. But it has a drawback. Instead of natural syntax like:
1: [AuthorizationFilter]
2: public ActionResult SomeAction()
3: {
4: ...
5: }
you need to write:
1: [Filter(typeof(AuthorizationFilter))]
2: public ActionResult SomeAction()
3: {
4: ...
5: }
which is quite unnatural. Another solution uses property injection. It works but once you use auto-wiring with constructor injection this method also looks not very good.
When you have auto-wiring you become a bit lazy – you just want your IoC container wires all dependencies for you. So what is the problem? Suppose we have custom ActionFilter which has constructor with parameter. In this case you will not be able to write [YourCustomAttribute] as compiler will warn you that it has a parameter in constructor and you need to specify it. But if this parameters come from IoC – then the only option for you is to leave parameter-less constructor and call Container.Resolve() in the body of constructor.
So is there a way to inject constructor dependencies in ActionFilters using auto-wiring without dependency on IoC container? As ASP.Net is quite extensible framework – the answer is yes. Suppose that we need AdminOnlyAttribute (which is not IActionFilter actually, but IAuthorizationFilter. But for this post it is not important) which has 1 dependency ISecurityService:
1: [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
2: public class AdminOnlyAttribute : FilterAttribute, IAuthorizationFilter
3: {
4: private ISecurityService securityService;
5:
6: public AdminOnlyAttribute()
7: {
8: }
9:
10: public AdminOnlyAttribute(ISecurityService securityService)
11: {
12: this.securityService = securityService;
13: }
14:
15: public void OnAuthorization(AuthorizationContext filterContext)
16: {
17: if (!this.securityService.IsCurrentUserAdmin())
18: {
19: filterContext.Result = new HttpUnauthorizedResult();
20: }
21: }
22: }
Note that it has 2 constructors: parameter-less and constructor with parameter of type ISecurityService. We need parameter-less constructor in order to have possibility to write just [AdminOnly] without compiler errors:
1: [AdminOnly]
2: public class AdminController : Controller
3: {
4: ...
5: }
In this case MVC will create AdminOnlyAttribute using parameter-less contructor. But OnAuthorization() method supposes that 2nd contructor was called and that securityService is not null. So how can we provide not null securityService if MVC creates it with securityService = null? The answer is – custom ControllerActionInvoker. We will analyze action filters for action before to call it and use well known feature of IoC containers which by default try to create object using maximum number of dependencies:
1: public class MostMatchedControllerActionInvoker : ControllerActionInvoker
2: {
3: private IContainer container;
4:
5: public MostMatchedControllerActionInvoker(IContainer container)
6: {
7: this.container = container;
8: }
9:
10: protected override AuthorizationContext InvokeAuthorizationFilters(
11: ControllerContext controllerContext, IList<IAuthorizationFilter> filters,
12: ActionDescriptor actionDescriptor)
13: {
14: var mostMatchedFilters = this.getMostMatchedFilters(filters);
15: return base.InvokeAuthorizationFilters(controllerContext, mostMatchedFilters,
16: actionDescriptor);
17: }
18:
19: protected override ActionExecutedContext InvokeActionMethodWithFilters(
20: ControllerContext controllerContext, IList<IActionFilter> filters,
21: ActionDescriptor actionDescriptor, IDictionary<string, object> parameters)
22: {
23: var mostMatchedFilters = this.getMostMatchedFilters(filters);
24: return base.InvokeActionMethodWithFilters(controllerContext, mostMatchedFilters,
25: actionDescriptor, parameters);
26: }
27:
28: private IList<T> getMostMatchedFilters<T>(IList<T> originalFilters)
29: {
30: if (originalFilters == null)
31: {
32: return null;
33: }
34:
35: var mostMatchedFilters = new List<T>();
36: foreach (var filter in originalFilters)
37: {
38: mostMatchedFilters.Add((T)this.container.GetInstance(filter.GetType()));
39: }
40: return mostMatchedFilters;
41: }
42: }
I.e. for each filter we call container.GetInstance(filter.GetType())) method of IoC container (I used StructureMap, but particular type of container is not important here) which in turn will call constructor with maximum number of dependencies. So we will override AdminOnlyAttribute which was created by MVC with parameter-less constructor by another instance which is created using constructor with parameter.
This code however has a problem: each filter is resolved twice. Once by MVC and second time by our code. It can lead to performance issues. In order to avoid this problem the following technique can be used. Add a mixing interface IFilterWithMostMatchedConstructor:
1: public interface IFilterWithMostMatchedConstructor
2: {
3: }
Then add this interface to our AdminOnlyAttribute class:
1: [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
2: public class AdminOnlyAttribute : FilterAttribute, IAuthorizationFilter,
3: IFilterWithMostMatchedConstructor
4: {
5: ...
6: }
and slightly change getMostMatchedFilters() method of MostMatchedControllerActionInvoker:
1: private IList<T> getMostMatchedFilters<T>(IList<T> originalFilters)
2: {
3: if (originalFilters == null)
4: {
5: return null;
6: }
7:
8: var mostMatchedFilters = new List<T>();
9: foreach (var filter in originalFilters)
10: {
11: if (filter is IFilterWithMostMatchedConstructor)
12: {
13: mostMatchedFilters.Add((T)this.container.GetInstance(filter.GetType()));
14: }
15: else
16: {
17: mostMatchedFilters.Add(filter);
18: }
19: }
20: return mostMatchedFilters;
21: }
So we only replace those filters which implement IFilterWithMostMatchedConstructor interface. We minimized performance overhead. Remaining overhead is just creation of AdminOnlyAttribute with parameter less constructor by MVC. But I think this is acceptable compromise in order to have auto-wiring with constructor injection for ActionFilters.