12.11.2009

IJoinedFilter

We have been using ASP.Net MVC for a few projects at work and the standard set of cross cutting concerns are popping up, as usual. A lot of samples exist to create filters for the scenarios (logging, exception handling, mapping, output transformations etc). We have been using many of these and they are adding a lot of excitement to the development process.

However, I smell a bit of a problem with all these attributed filters. The smell of configuration over convention. Configuration isn’t always a bad thing, it’s actually pretty useful when you have niche situations, but when you have repetitive, cross cutting concerns that need the application of the same filters, it gets to be a burden to remember what to apply. The MVC framework is designed with just the right amount of convention over configuration in several other areas, I felt like it was time to do the same for applying filters.

Stealing a page out of the playbook of interception, I felt it was time to design filters to join to actions/controllers when certain criteria are met. Filters themselves are interceptors, so naturally, a filter could define what it joins to, it’s join point (from the interceptor world). This way we have filters apply themselves to actions instead of vice versa! This would be great, for example, to create standard sets of exception filters and have them attached to all actions, no need for someone to decorate every controller and no chance they would forget! The same is true for any type of filter (action, result, authorization or exception).

Note: I also see a need for some SRP with join points and the interceptors and will look into taking existing filters and applying a join point to attach them, instead of the filter needing to define it’s join points.

IJoinedFilter

So enough with the motivation, and on to how this works. IJoinedFilter is an interface to define how a filter should apply to an action and the context:

public interface IJoinedFilter
{
  bool JoinsTo(ControllerContext controllerContext, ActionDescriptor actionDescriptor);
}

The method JoinsTo determines if the filter should apply to the current action being executed. Both the controller context and action descriptor are passed to allow for coarse to very fine grained application. Before I explain how to wire up the infrastructure, let's look at an example of how this can be used. Let's say I have a simple controller with two actions, About and World:

public class HomeController : Controller
{
  public ActionResult About()
  {
    var data = new
               {
                message = "about"
               };

return Json(data);

}

public ActionResult World() { var data = new { message = "world" };

return Json(data);

} }

To keep this simple, they both just return json results. The output right now looks like the following:

image image


Now, let’s say I want to use a filter to change my output on the World action to return “Hello world”. Normally I would create a new ActionFilterAttribute and manually apply this attribute:

public class HelloWorldFilter : ActionFilterAttribute
{
  public override void OnActionExecuted(ActionExecutedContext filterContext)
  {
    var result = new JsonResult
    {
      Data = new
      {
        message = "Hello World!"
      }
    };

filterContext.Result = result;

} }

public class HomeController : Controller { public ActionResult About() { var data = new { message = "about" };

return Json(data);

}

[HelloWorldFilter] public ActionResult World() { var data = new { message = "world" };

return Json(data);

} }

HelloWorldFilter

But now, with joined filters I can avoid the step of applying the attribute! Instead I can simply implement IActionFilter with IJoinedFilter to get the same result:

public class HelloWorldFilter : IActionFilter, IJoinedFilter
{
  public bool JoinsTo(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
  {
    return actionDescriptor.ActionName == "World";
  }

public void OnActionExecuting(ActionExecutingContext filterContext) { }

public void OnActionExecuted(ActionExecutedContext filterContext) { var result = new JsonResult { Data = new { message = "Hello World!" } };

filterContext.Result = result;

} }

imageJoinsTo is set to only apply to actions with a name of "World". Now, I can replace my World action with a result of "Hello World!" by simply joining to the specific action I want (convention) instead of attributing it (configuration). This is a rather “cheesy” example but I am working on a subsequent blog post to show a few really cool, practical examples!

Infrastructure

Now for how the magic happens. I am going to need to find a spot to discover the joined filters and apply them to an action. The best spot to do this is to create a custom action invoker. I started with the WindsorControllerFactory in MVCContrib, so I also get IoC while I am at it :). This requires overriding the GetControllerInstance. If the container has an IActionInvoker registered, then resolve it and use it instead of the default action invoker.

public class ExtendedWindsorControllerFactory : WindsorControllerFactory
{
  public ExtendedWindsorControllerFactory(IWindsorContainer container) : base(container)
  {
    Container = container;
  }

protected IWindsorContainer Container { get; set; }

protected override IController GetControllerInstance(Type controllerType) { var controller = base.GetControllerInstance(controllerType) as Controller;

if (Container.Kernel.HasComponent(typeof (IActionInvoker)))
{
  controller.ActionInvoker = Container.Resolve<IActionInvoker>();
}

return controller;

} }

Now that I can inject an IActionInvoker, it is time to make one that can find my dynamic filters! I am calling this a LocatorActionInvoker as it resolves a list of IFilterLocator. Each one of these could find filters in it’s own way, this is just for SRP. For each locator the invoker will give it the controller context and the action descriptor and ask it to return filters (FilterInfo). It merges the results of each IFilterLocator into the set of filters to use!

public interface IFilterLocator
{
  FilterInfo FindFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor);
}

public class LocatorActionInvoker : ControllerActionInvoker { protected IWindsorContainer Container;

public LocatorActionInvoker(IWindsorContainer container) { Container = container; }

protected override FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) { var filters = base.GetFilters(controllerContext, actionDescriptor);

var filterFinders = Container.ResolveAll<IFilterLocator>();

var foundFilters = filterFinders.Select(f => f.FindFilters(controllerContext, actionDescriptor));

foundFilters.ForEach(f => AddFilters(filters, f));

return filters;

}

private void AddFilters(FilterInfo filters, FilterInfo mergeFilters) { mergeFilters.ActionFilters.ForEach(filters.ActionFilters.Add); mergeFilters.ExceptionFilters.ForEach(filters.ExceptionFilters.Add); mergeFilters.AuthorizationFilters.ForEach(filters.AuthorizationFilters.Add); mergeFilters.ResultFilters.ForEach(filters.ResultFilters.Add); } }

Now I need to implement IFilterLocator to find my IJoinedFilters:

public class JoinedFilterLocator : IFilterLocator
{
  private IWindsorContainer Container;

public JoinedFilterLocator(IWindsorContainer container) { Container = container; }

public FilterInfo FindFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) { var filters = new FilterInfo();

var joinedFilters = Container.ResolveAll<IJoinedFilter>()
  .Where(i => i.JoinsTo(controllerContext, actionDescriptor)).ToList();

if (joinedFilters != null)
{
  joinedFilters.OfType<IActionFilter>().ForEach(filters.ActionFilters.Add);
  joinedFilters.OfType<IExceptionFilter>().ForEach(filters.ExceptionFilters.Add);
  joinedFilters.OfType<IAuthorizationFilter>().ForEach(filters.AuthorizationFilters.Add);
  joinedFilters.OfType<IResultfilter>().ForEach(filters.ResultFilters.Add);
}

return filters;

} }

JoinedFilterLocator uses the container to resolve a list of IJoinedFilter. It filters this list for only filters that apply to the given ActionDescriptor and ControllerContext using the IJoinedFilter.JoinsTo method. If any filters match, it returns them in a new FilterInfo instance, which will be merged in LocatorActionInvoker with the rest of the filters.

Registration

That is it for custom components for the infrastructure of joined filters. All that is left is to configure the application to use the components and to register my components. First I add a container to my application:

public class MvcApplication : HttpApplication
{
  public static IWindsorContainer Container;

Then, in the startup of the application I initialize my container and register my components:

protected void Application_Start()
{
  RegisterRoutes(RouteTable.Routes);
  if (InitializeContainer())
  {
    RegisterControllers();
    SetControllerFactory();
    SetActionInvoker();
    RegisterJoinedActionFilters();
  }
}

Initialize container sets up a Windsor container for the application:

private bool InitializeContainer()
{
  lock (_lock)
  {
    if (Container != null)
    {
      return false;
    }
    Container = new WindsorContainer();
    Container.Register(
      Component.For<IWindsorContainer>()
        .Instance(Container)
        .LifeStyle.Singleton
      );
  }
  return true;
}

If the container is being initialized for the first time, then I register components. RegisterControllers just scans for controllers. SetControllerFactory registers ExtendedWindsorControllerFactory, resolves and sets it as the controller factory for MVC to use.

private void SetControllerFactory()
{
  Container.Register(Component
                      .For<IControllerFactory>()
                      .ImplementedBy<ExtendedWindsorControllerFactory>()
                      .LifeStyle.Transient);

var factory = Container.Resolve<IControllerFactory>();

ControllerBuilder.Current.SetControllerFactory(factory); }

SetActionInvoker registers my LocatorActionInvoker:

private void SetActionInvoker()
{
  Container.Register(Component.For<IActionInvoker>().ImplementedBy<LocatorActionInvoker>()
                      .LifeStyle.Transient);
}

Finally, RegisterJoinedActionFilters registers my JoinedFilterLocator and scans for IJoinedFilter types. You may want to change how this scans based on your project structure, in my simple example I have filters in my MVC app (bad practice but great for samples :)).

private void RegisterJoinedActionFilters()
{
  Container.Register(
    Component.For<IFilterLocator>().ImplementedBy<JoinedFilterLocator>().LifeStyle.Transient
    );

Container.Register( AllTypes.Of<IJoinedFilter>() .FromAssembly(Assembly.GetExecutingAssembly()) .ConfigureFor<IJoinedFilter>(c => c.LifeStyle.Transient) ); }

That is all for now, pretty easy way to setup joined filters and start creating convention based, joined filters instead of manually configuring them via attributes!

Download Sample

If you want to download the sample and try it out, feel free. You probably want an in browser json viewer with this sample, I like using JSONView with FireFox.

Note: due to issues building the latest MVCContrib against MVC 1.0 and/or getting castle version mismatches to work and me being lazy, I simply copied the WindsorControllerFactory into my project for now, cheap yes, but hey my time isn’t unlimited :)

-Wes





comments powered by Disqus