Jul 20, 2010

Building an ActionFilter - BeforeFilter for ASP .Net MVC, Part 3

The last feature that I want to add to my BeforeFilter is an exclusion list that specifies controller actions for which not to execute the filter methods. Assume that you have Edit/Create/Display views; the Edit and Create views could – for example - require additional data to render drop down lists. If we populate this data with a before filter we should have the ability to exclude the Display view from the filter where this additional data is not necessary.

Attribute parameters can only be constant expressions so we have that restriction to contend with. The usage should be friendly and easy to use – a little syntactic sugar doesn’t hurt. I was contemplating something similar to the snippet below:

    [BeforeFilter("Initialize", Except = new [] {"Index", "Details"} )]
    public class ItemController : Controller
    {
        ...
    }

This looks pretty straight forward and the implementation should not be too difficult.

How are we going to know the action that is about to be executed? The OnActionExecuting method is passed an ActionExecutingContext. It contains an ActionDescriptor object that has various interesting properties including the ActionName.

Naturally we begin with a test. Our test setup will changes slightly so that we can mock the ActionDescriptor and create the context with the mocked out version. The test will include a setup for the ActionName property and verify that the filter methods are not invoked for the actions specified in our list. The setup and the test code is below:

    [SetUp]
    public void Before()
    {
        controller = new Mock<TestController>();
        descriptor = new Mock<ActionDescriptor>();
        context = new ActionExecutingContext
                        {
                            Controller = controller.Object, 
                            ActionDescriptor = descriptor.Object
                        };
    }

    [Test]
    public void should_not_call_specified_controller_method_given_an_action_is_an_exception()
    {
        descriptor.SetupGet(x => x.ActionName).Returns("Display");
        var attribute = new BeforeFilterAttribute("initialize", "logaction")
                            {
                                Except = new [] {"Display"}
                            };

        attribute.OnActionExecuting(context);

        controller.Verify(x => x.initialize(), Times.Never());
        controller.Verify(x => x.logaction(), Times.Never());
    }

The implementation for OnActionExecuting changes very little, we simply check if the ActionName is on the exception list before invoking the filter methods:

    if(!Except.Contains(filterContext.ActionDescriptor.ActionName))
        controllerMethod.Invoke((object)controller, new object[] { });

And that’s that, we now have a fairly complete BeforeFilterAttribute we can use to keep controllers clean.