Jul 13, 2010

Building an ActionFilter: BeforeFilter for ASP .Net MVC2

I've broken this up into three parts:

MVC2 has a nice feature called filters. They let you keep your controllers clean and factor out common code. This looks like it works particularly well for cross cutting concerns like security, logging, caching, etc. I’ve used in in one instance when I needed to populate the view bag with some common reference data used on the view to render a drop down list. With some lines omitted for brevity code for the controller looked something like this:

    [HandleError]
    public class ItemController : Controller
    {
        public ActionResult Edit()
        {
            ViewData["categories"] = repository.FindAll();

            return View();
        }
        
        public ActionResult Create()
        {
            ViewData["categories"] = repository.FindAll();

            return View();
        }
    }

We can quickly extract a method for the common code and call it InitializeCategories:

    private void InitializeCategories()
    {
        ViewData["categories"] = repository.FindAll();
    }

So you do this for a couple of controllers and you start to wonder if there is a more generic solution to this. We could try to extract a base class but that may not be suitable to all controllers or it could lead to some unpleasant inheritance hierarchy. This is when action filters come to mind. What if we could declaratively specify some method to execute before each controller action? We would like to end up with code similar to this:

    [HandleError]
    public class ItemController : Controller
    {
        [BeforeFilter("InitializeCategories")]
        public ActionResult Edit()
        {
            return View();
        }

        [BeforeFilter("InitializeCategories")]
        public ActionResult Create()
        {
            return View();
        }

        public void InitializeCategories()
        {
            ViewData["categories"] = repository.FindAll();
        }
    }

Writing an ActionFilter to handle this is relatively easy. There are a few types of filters available already, but for our purpose we can simply subclass ActionFilterAttribute and override the OnActionExecuting method. This method is called before out controller action executes. The method will be passed an ActionExecutingContext parameter which contains a variety of interesting information about the execution context including the controller instance. We can start by writing a test for our attribute. Lets say that we want to assert that a method on the controller is called if it exists. We make that pass and we write a second tests that asserts we throw a meaningful exception if the specified method does not exists. The tests looks like this:

    [TestFixture]
    public class A_BeforeFilterAttribute
    {
        private Mock<TestController> controller;
        private ActionExecutingContext filterContext;

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

        [Test]
        public void should_call_the_specified_controller_method_given_it_exists()
        {
            controller.Setup(x => x.initialize());
            var attribute = new BeforeFilterAttribute("initialize");

            attribute.OnActionExecuting(filterContext);

            controller.VerifyAll();
        }
        
        [Test]
        public void should_throw_invalid_method_exception_given_it_doesnt_exist()
        {
            var attribute = new BeforeFilterAttribute("nosuchmethod");

            Assert.Throws<Exception>(() => attribute.OnActionExecuting(filterContext));

            controller.VerifyAll();
        }

        public class TestController : Controller
        {
            public virtual void initialize()
            {
            }
        }
    }

I created a test controller so that I can set up an expectation that its method will be called. The mocking framework I used here is Moq. The implementation that makes the tests pass is below:

    public class BeforeFilterAttribute : ActionFilterAttribute
    {
        private readonly string methodName;

        public BeforeFilterAttribute(string methodName)
        {
            this.methodName = methodName;
        }

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var controller = filterContext.Controller;
            var method = controller.GetType().GetMethods().Where(x => x.Name.Equals(methodName)).FirstOrDefault();
            if (method == null)
            {
                throw new Exception(string.Format("The method {0} could not be found on {1}.", methodName, controller.GetType().FullName));
            }
            
            method.Invoke((object) controller, new object[] {});

            base.OnActionExecuting(filterContext);
        }
    }

Kind of a lengthy posts for such short code. This can be expanded a little so that we can specify a list of methods to be called, but I haven’t needed that yet and it’s all about providing value to the customer. That could maybe look something like this:

        [BeforeFilter("InitializeCategories","InitializeProvinces")]
        public ActionResult Edit()
        {
            return View();
        }

We can accomplish this by creating an overloaded constructor for the BeforeFilterAttribute:

        public BeforeFilterAttribute(params string[] methodName)
        {
        }

That’s it for now folks, if you decide to implement the overload please share!