MvcExtensions - ActionFilter
- by kazimanzurrashid
One of the thing that people often complains is dependency injection in Action Filters. Since the standard way of applying action filters is to either decorate the Controller or the Action methods, there is no way you can inject dependencies in the action filter constructors. There are quite a few posts on this subject, which shows the property injection with a custom action invoker, but all of them suffers from the same small bug (you will find the BuildUp is called more than once if the filter implements multiple interface e.g. both IActionFilter and IResultFilter). The MvcExtensions supports both property injection as well as fluent filter configuration api. There are a number of benefits of this fluent filter configuration api over the regular attribute based filter decoration. You can pass your dependencies in the constructor rather than property. Lets say, you want to create an action filter which will update the User Last Activity Date, you can create a filter like the following: public class UpdateUserLastActivityAttribute : FilterAttribute, IResultFilter
{
public UpdateUserLastActivityAttribute(IUserService userService)
{
Check.Argument.IsNotNull(userService, "userService");
UserService = userService;
}
public IUserService UserService
{
get;
private set;
}
public void OnResultExecuting(ResultExecutingContext filterContext)
{
// Do nothing, just sleep.
}
public void OnResultExecuted(ResultExecutedContext filterContext)
{
Check.Argument.IsNotNull(filterContext, "filterContext");
string userName = filterContext.HttpContext.User.Identity.IsAuthenticated ?
filterContext.HttpContext.User.Identity.Name :
null;
if (!string.IsNullOrEmpty(userName))
{
UserService.UpdateLastActivity(userName);
}
}
}
As you can see, it is nothing different than a regular filter except that we are passing the dependency in the constructor. Next, we have to configure this filter for which Controller/Action methods will execute:
public class ConfigureFilters : ConfigureFiltersBase
{
protected override void Configure(IFilterRegistry registry)
{
registry.Register<HomeController, UpdateUserLastActivityAttribute>();
}
}
You can register more than one filter for the same Controller/Action Methods:
registry.Register<HomeController, UpdateUserLastActivityAttribute, CompressAttribute>();
You can register the filters for a specific Action method instead of the whole controller:
registry.Register<HomeController, UpdateUserLastActivityAttribute, CompressAttribute>(c => c.Index());
You can even set various properties of the filter:
registry.Register<ControlPanelController, CustomAuthorizeAttribute>( attribute => { attribute.AllowedRole = Role.Administrator; });
The Fluent Filter registration also reduces the number of base controllers in your application. It is very common that we create a base controller and decorate it with action filters and then we create concrete controller(s) so that the base controllers action filters are also executed in the concrete controller. You can do the same with a single line statement with the fluent filter registration:
Registering the Filters for All Controllers:
registry.Register<ElmahHandleErrorAttribute>(new TypeCatalogBuilder().Add(GetType().Assembly).Include(type => typeof(Controller).IsAssignableFrom(type)));
Registering Filters for selected Controllers:
registry.Register<ElmahHandleErrorAttribute>(new TypeCatalogBuilder().Add(GetType().Assembly).Include(type => typeof(Controller).IsAssignableFrom(type) && (type.Name.StartsWith("Home") || type.Name.StartsWith("Post"))));
You can also use the built-in filters in the fluent registration, for example:
registry.Register<HomeController, OutputCacheAttribute>(attribute => { attribute.Duration = 60; });
With the fluent filter configuration you can even apply filters to controllers that source code is not available to you (may be the controller is a part of a third part component).
That’s it for today, in the next post we will discuss about the Model binding support in MvcExtensions. So stay tuned.