Posts from  December 2009


12.19.2009

Testing tips (That is testability, not TDD part 2)

This is a continuation of my last post That is testability, not Test Driven Development! In it, I analyzed an article on the ADO.Net team blog Walkthrough: Test-Driven Development with the Entity Framework 4.0. I wanted to take that analysis a step further and share some insight from the testing perspective itself. There are two tests to review to produce a list of testing tips.

Update: Some of these test refactorings won’t work in the existing sample as they take dependencies to NUnit, you will need to add that in if you want to run these tests.

  1. Don’t setup what we don’t need. Before the first test case was even outlined, a large amount of unnecessary setup was created, some of which was never used. Tip: only write setup specifically for the given test case, in other words write the test case first and then the setup.
    namespace BlogTests
    {
        [TestClass]
        public class CommentTests
        {
            private IBloggingEntities _context;

        [TestInitialize]
        public void TestSetup()
        {
            Person p = new Person()
            {
                fn = "Jonathan",
                ln = "Aneja",
                email = "jonaneja@microsoft.com",
                User = new User() { Password = "password123" }
            };
    
    
            Blog b = new Blog()
            {
                Name = "Entity Framework Team Blog",
                Url = "http://blogs.msdn.com/adonet",
                User = p.User
            };
    
            Post post = new Post()
            {
                ID = 1,
                Blog = b,
                Created = DateTime.Now,
                Posted = DateTime.Now,
                Title = "Walkthrough: Test-Driven Development in Entity Framework 4.0",
                User = p.User,
                User1 = p.User,
                PermaUrl = b.Url + "/walkthrough-test-driven-development-in-Entity-Framework-4.0.aspx",
                Body = "This walkthrough will demonstrate how to..."
            };
    
            _context = new FakeBloggingEntities();
    
            var repository = new BlogRepository(_context);
    
            repository.AddPerson(p);
            repository.AddBlog(b);
            repository.AddPost(post);
    
            repository.SaveChanges();
        }
    }
    

    }

  2. Focus on the unit that is being tested, do not test it indirectly. In this example, the first test is to validate that the same comment isn’t added twice, however I see no call to any validate method in the test case!
            [TestMethod]
            [ExpectedException(typeof(InvalidOperationException))]
            public void AttemptedDoublePostedComment()
            {
                var repository = new BlogRepository(_context);

            Comment c = new Comment()
            {
                ID = 45,
                Created = DateTime.Now,
                Posted = DateTime.Now,
                Body = "EF4 is cool!",
                Title = "comment #1",
                Person = repository.GetPersonByEmail("jonaneja@microsoft.com"),
                Post = repository.GetPostByID(1),
            };
    
            Comment c2 = new Comment()
            {
                ID = 46,
                Created = DateTime.Now,
                Posted = DateTime.Now,
                Body = "EF4 is cool!",
                Title = "comment #1",
                Person = repository.GetPersonByEmail("jonaneja@microsoft.com"),
                Post = repository.GetPostByID(1),
            };
    
            repository.AddComment(c);
            repository.AddComment(c2);
    
            repository.SaveChanges();
        }
    
    public class BlogRepository
    {
        private IBloggingEntities _context;
        ...
        public void SaveChanges()
        {
            _context.SaveChanges();
        }
    }
    
    public class FakeBloggingEntities : IBloggingEntities, IDisposable
    {
        public int SaveChanges()
        {
            foreach (var comment in Comments)
            {
                ((IValidate)comment).Validate(ChangeAction.Insert);
            }
            return 1;
        }
    }
    
    public partial class Comment : IValidate
    {
      ...
      void IValidate.Validate(ChangeAction action) {
            if (action == ChangeAction.Insert)
            {
                //prevent double-posting of Comments
                if (this.Post.Comments.Count(c => c.Body == this.Body && c.Person.User == this.Person.User) > 1)
                    throw new InvalidOperationException("A comment with this exact text has already been posted to this entry");
            }
        }
    }</pre>The call to validate is indirectly tested through BlogRepository.SaveChanges which calls IBloggingEntities.SaveChanges which is tested with FakeBloggingEntities. FakeBloggingEntities is a stub class and not real code, it’s faking the infrastructure of the EF, when we should be testing our validate method directly. We should rewrite this test as follows and avoid the need for a repository expectation: <pre class="brush: c#">        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException))]
        public void AttemptedDoublePostedComment()
        {
            var repository = new BlogRepository(_context);
    
            Comment c = new Comment()
            {
                ID = 45,
                Created = DateTime.Now,
                Posted = DateTime.Now,
                Body = "EF4 is cool!",
                Title = "comment #1",
                Person = repository.GetPersonByEmail("jonaneja@microsoft.com"),
                Post = repository.GetPostByID(1),
            };
    
            Comment c2 = new Comment()
            {
                ID = 46,
                Created = DateTime.Now,
                Posted = DateTime.Now,
                Body = "EF4 is cool!",
                Title = "comment #1",
                Person = repository.GetPersonByEmail("jonaneja@microsoft.com"),
                Post = repository.GetPostByID(1),
            };
    
            c.Validate(ChangeAction.Insert);
        }</pre>
    

  3. Within a test, avoid unnecessary setup in the scenario (arrange portion). We do not need to set ID,Created,Posted and title for this test case.
            [TestMethod]
            [ExpectedException(typeof(InvalidOperationException))]
            public void AttemptedDoublePostedComment()
            {
                var repository = new BlogRepository(_context);

            Comment c = new Comment()
            {
                Body = "EF4 is cool!",
                Person = repository.GetPersonByEmail("jonaneja@microsoft.com"),
                Post = repository.GetPostByID(1),
            };
    
            Comment c2 = new Comment()
            {
                Body = "EF4 is cool!",
                Person = repository.GetPersonByEmail("jonaneja@microsoft.com"),
                Post = repository.GetPostByID(1),
            };
    
            c.Validate(ChangeAction.Insert);
        }</pre>
    

  4. If there are other dependencies for a test case, we should avoid setting them up as global variables. Instead, create them as needed with helper methods. This increases readability and maintainability as we can quickly look at how the person and post are created without going to the setup method. It also decouples the test from the test class so it can be moved if needed when refactoring. Notice we no longer need the repository to pass setup from the setup method to the test case! This is production code that isn’t needed but was added without even testing it! (BlogRepository.GetPersonByEmail & BlogRepository.GetPostByID)
            [TestMethod]
            [ExpectedException(typeof(InvalidOperationException))]
            public void AttemptedDoublePostedComment()
            {
                var person = GetNewPerson();
                var post = GetNewPost();
                Comment c = GetNewComment();
                c.Body = "EF4 is cool!";
                c.Person = person;
                c.Post = post;
                Comment c2 = GetNewComment();
                c2.Body = "EF4 is cool!";
                c2.Person = person;
                c2.Post = post;

            c.Validate(ChangeAction.Insert);
        }
    
        private Comment GetNewComment()
        {
            return new Comment();
        } 
    
        private Person GetNewPerson()
        {
            return new Person { User = new User() };
        } 
    
        private Post GetNewPost()
        {
            return new Post();
        } </pre>
    

  5. Avoid duplicated strings. If they have meaning, then share that with a common variable. In this example the body is duplicated for testing validation, so duplicatedBody helps convey that in the name of the variable. Also, avoid meaningful variable values unless they are specific to the test case, to avoid distracting future readers. In this case we should replace duplicatedBody with a valid and simple value to imply that it isn’t important to the test case (“EF4 is cool!” now becomes “A”).
            [TestMethod]
            [ExpectedException(typeof(InvalidOperationException))]
            public void AttemptedDoublePostedComment()
            {
                var person = GetNewPerson();
                var post = GetNewPost();
                var duplicatedBody = "A";
                Comment c = GetNewComment();
                c.Body = duplicatedBody;
                c.Person = person;
                c.Post = post;
                Comment c2 = GetNewComment();
                c2.Body = duplicatedBody;
                c2.Person = person;
                c2.Post = post;

            c.Validate(ChangeAction.Insert);
        }</pre>
    

  6. Use AAA test naming to help convey what you are testing and give all variables meaningful names according to the scenario they represent. This method is testing the Validate operation (Act in AAA), the scenario (Arrange in AAA) is a duplicated comment, and the expectation (Assert in AAA) is an InvalidOperationException. The Act_Arrange_Assert naming style from Roy Osherove's The Art of Unit Testing allows test case comprehension without looking at the test code, much like an interface should tell the behavior regardless of implementation! We should rename the comment variables from c and c2 to firstComment and duplicatedComment (Note: if you cannot use var for your type and still understand what the variable represents then you have a misnamed variable).
            [TestMethod]
            [ExpectedException(typeof(InvalidOperationException))]
            public void Validate_DuplicatedComment_ThrowsInvalidOperationException()
            {
                var person = GetNewPerson();
                var post = GetNewPost();
                var duplicatedBody = "A";
                var firstComment = GetNewComment();
                firstComment.Body = duplicatedBody;
                firstComment.Person = person;
                firstComment.Post = post;
                var duplicatedComment = GetNewComment();
                duplicatedComment.Body = duplicatedBody;
                duplicatedComment.Person = person;
                duplicatedComment.Post = post;

            firstComment.Validate(ChangeAction.Insert);
        }</pre>
    

  7. Avoid attributed exception assertions as any code in the test could fulfill the assertion. Instead, use inline assertions to test this. In the example below we can setup an action (delegate) that will execute the method under test, then pass that to a method that runs it and will return a failing test if the exception is not thrown (Note: inline exception assertions are a feature in NUnit and several other test frameworks but not in MSTest that I know of).
            [TestMethod]
            public void Validate_DuplicatedComment_ThrowsInvalidOperationException()
            {
                var person = GetNewPerson();
                var post = GetNewPost();
                var duplicatedBody = "A";
                var firstComment = GetNewComment();
                firstComment.Body = duplicatedBody;
                firstComment.Person = person;
                firstComment.Post = post;
                var duplicatedComment = GetNewComment();
                duplicatedComment.Body = duplicatedBody;
                duplicatedComment.Person = person;
                duplicatedComment.Post = post;

            Action validate = () =&gt; firstComment.Validate(ChangeAction.Insert);
    
            Assert.Throws&lt;InvalidOperationException&gt;(validate);
        }</pre>
    

  8. Arrange test code into three sections, Arrange, Act and Assert (AAA syntax). You will notice we already refactored to this when applying the other tips above. This way the first block of code is always the scenario (arrange), the second block is the action, or deferred action when testing exceptions. Finally the last block is the assertion. Notice how these flow naturally from the AAA test case naming convention. One of the things Roy points out in his book is that if you cannot name a method with AAA syntax then you have not thought through what the method is actually testing. If the authors of the blog post would have named the method with A_A_A syntax and organized it into AAA blocks, they would have noticed the operation under test (Validate) was not mirrored in the test code!
            [TestMethod]
            public void Validate_DuplicatedComment_ThrowsInvalidOperationException()
            {
                // Arrange
                var person = GetNewPerson();
                var post = GetNewPost();
                var duplicatedBody = "A";
                var firstComment = GetNewComment();
                firstComment.Body = duplicatedBody;
                firstComment.Person = person;
                firstComment.Post = post;
                var duplicatedComment = GetNewComment();
                duplicatedComment.Body = duplicatedBody;
                duplicatedComment.Person = person;
                duplicatedComment.Post = post;

            // Act
            Action validate = () =&gt; firstComment.Validate(ChangeAction.Insert);
    
            // Assert
            Assert.Throws&lt;InvalidOperationException&gt;(validate);
        }</pre>
    

  9. Here is their other test, before applying any of the tips above:
            [TestMethod]
            [ExpectedException(typeof(InvalidOperationException))]
            public void AttemptBlankComment()
            {
                var repository = new BlogRepository(_context);

            Comment c = new Comment()
            {
                ID = 123,
                Created = DateTime.Now,
                Posted = DateTime.Now,
                Body = "",
                Title = "some thoughts",
                Person = repository.GetPersonByEmail("jonaneja@microsoft.com"),
                Post = repository.GetPostByID(1),
            };
    
            repository.AddComment(c);
            repository.SaveChanges();
        }</pre>Here is the test after. The Person and Post properties aren't needed for this test but they are required as the Validate tests with these things for our other test case. If these are part of a “default” test object for a comment we could move them to GetNewComment() and have them available for any test case. <pre class="brush: c#">        [TestMethod]
        public void Validate_BlankComment_ThrowsInvalidOperationException()
        {
            var blankComment = GetNewComment();
            blankComment.Body = string.Empty;
            blankComment.Person = GetNewPerson();
            blankComment.Post = GetNewPost();
    
            Action validate = () =&gt; blankComment.Validate(ChangeAction.Insert);
    
            Assert.Throws&lt;InvalidOperationException&gt;(validate);
        }</pre>
    

  10. Going back to focusing on the unit itself, needing a post to validate duplicated comments is a smell. This highlights that the concern for validating a duplicated comment should be moved to the post class.
    public partial class Post : IValidate 
    { 
      ... 
      void IValidate.Validate(ChangeAction action) 
      { 
        //prevent double-posting of Comments 
        if (this.Comments.Any(c => this.Comments.Any(other => other != c && other.Body == c.Body && other.Person.User == c.Person.User))) 
          throw new InvalidOperationException("A comment with this exact text has already been posted to this entry");
       } 
    } 

    (Note: this is going to validate existing comments that are duplicated in addition to new ones, but it points out the issue of SRP for validation and that it really belongs here, maybe someone else can point out how we can work with change sets to determine if the duplication was already in place or is new)

    The first test would be moved to PostTests test class. We no longer need the relationship from Comment->Post in our domain, this is domain distillation! Notice how easy it would be to move our first test to a new PostTests class as we don't have a dependency to the setup method! If we needed to share the GetNewXyz() methods we could even move those to a base test fixture.
            // PostTests.cs
            [TestMethod]
            public void Validate_DuplicatedComment_ThrowsInvalidOperationException()
            {
                var person = GetNewPerson();
                var post = GetNewPost();
                var duplicatedBody = "A";
                var firstComment = GetNewComment();
                firstComment.Body = duplicatedBody;
                firstComment.Person = person;
                post.AddComment(firstComment);
                var duplicatedComment = GetNewComment();
                duplicatedComment.Body = duplicatedBody;
                duplicatedComment.Person = person;
                post.AddComment(duplicatedComment);

            Action validate = () =&gt; post.Validate(ChangeAction.Insert);
    
            Assert.Throws&lt;InvalidOperationException&gt;(validate);
        }
    
        // CommentTests.cs
        [TestMethod]
        public void Validate_BlankComment_ThrowsInvalidOperationException()
        {
            var blankComment = GetNewComment();
            blankComment.Body = string.Empty;
    
            Action validate = () =&gt; blankComment.Validate(ChangeAction.Insert);
    
            Assert.Throws&lt;InvalidOperationException&gt;(validate);
        }</pre>
    

    Look how simple the second test case is now! Beautiful, the intent is so clear!

  11. Don't write code beyond what a test case expects. In the sample above, we have ChangeAction.Insert as an input to validate but the test doesn’t dictate that in the scenario. We might want this test case to cover both insert and update. We could parameterize the test for both ChangeAction.Insert and ChangeAction.Update. This is using the NUnit framework for parameterized tests. I like to give a distinct name to each of the scenarios when I parameterize a test, so they are explicit when a test runner reports the output of a failure. If we wanted different behavior, we could create two tests but in this case we don't. Be very cautious not to go over board with parameterized tests, they should only be used in the very simplest of test case duplication.
            // CommentTests.cs
            [Test]
            [TestCase(ChangeAction.Insert, TestName="Validate_InsertBlankComment_ThrowsInvalidOperationException")]
            [TestCase(ChangeAction.Update, TestName="Validate_UpdateBlankComment_ThrowsInvalidOperationException")]
            public void Validate_BlankComment_ThrowsInvalidOperationException(ChangeAction changeAction)
            {
                var blankComment = GetNewComment();
                blankComment.Body = string.Empty;

            Action validate = () =&gt; blankComment.Validate(changeAction);
    
            Assert.Throws&lt;InvalidOperationException&gt;(validate);
        }</pre></li></ol>
    

    These are a few testing tips that I use to write and maintain tests every day.

    Happy Testing!

12.19.2009

That is testability, not Test Driven Development!

I applaud efforts to encourage test driven development, however I find myself cringing at the examples being produced by the framework designers we are supposed to look up to. I've noticed this with the buzz around TDD and ASP.Net MVC and now that buzz is transferring to the Entity Framework. I think it is wonderful that these frameworks are designed with testability in mind. However, it is up to the developer to actually employ TDD when composing their applications. There are other ways to test, such as TLD (test last) or just simple TFD (test first) without the extra intricacies of TDD. So enough with TDD and frameworks, just show us the testability! Testability of course being able to test our code that uses your framework, meaning we can isolate your infrastructure from our components.

If you want to demonstrate that for us, just demonstrate it with simple tests of stubs. It’s rather crazy to try to show it through TDD! TDD is a very incremental process that is almost impossible to blog an example about. Anyone who has read Kent Beck's book, yes I said book not blog, knows that by the end he barely produces a handful of tests! This is the very nature of documenting the process, step by step, of using TDD. I guess blog authors just love to get TLAs into their posts, especially TDD, look I did it too! This often leads to confusion about what TDD is and leads many to think it is synonymous with testing.

A very recent example is with the entity framework article “Walkthrough: Test-Driven Development with the Entity Framework 4.0.” This post, from the title, seems to be an example of TDD with EF4. Reading through it, the first test doesn’t get written until step 14! Prior to that a bunch of production code is composed. This is known as TLD (test last). A bunch of unrelated test code is produced before the heart of what they are testing is addressed in step 22. This happens because the author is taking an integration style approach to testing validation from the repository level and not from the class that implements the concern of validation. This leads to all the excess code produced before step 14 and from 15 to 21, not related to the test cases. I can imagine this happened because the author wanted to demonstrate stubbing with a fake repository in EF, which is a good thing to demonstrate, but doesn’t require TDD to demonstrate, SRP of demonstrations please! This post would probably be better named “Walkthrough: Testability with the EF 4.0.” No offense to the author, but please help us readers know what we are getting into, especially if we are learning something new!

The basics of TDD that are missing from this post:

  1. Test list – a list of scenarios that you have yet to write, a simple to do list. As TDD is very involved with one test at a time, this is a scratch pad of future tests.
    1. Items are added as you go
    2. Items are crossed off as they are completed
  2. Red/Green/Refactor workflow
    1. Write test code first
      1. Don’t write unnecessary test code (especially in setup)
        1. Look at the setup method in step 11, we are writing setup without even knowing what our first test case is!
          1. Person – fn,ln,password are never used
          2. Blog – name, url are never used
          3. Post – Created,Posted,Title,PermaUrl (has string concatenation!!!) and Body are never used
        2. Look at the setup in step 14 of AttempteddoublePostedComment
          1. Each comment defines Id, Created, Posted, Body and Title which are never used! Note: Body not being empty would trigger a failure when writing the next test, but you don’t deal with that until you write that test, not when TDDing.
        3. Unnecessary test code decreases maintainability and readability of tests
    2. Get it to compile
      1. Compilation errors are failures
      2. Just enough to get it to pass
    3. Get it to run and fail
      1. Run time failures of expectations
    4. Implement just enough production code to get it to pass under the given scenario
      1. This often means incomplete or incorrect production code, just enough to get it to pass
        1. Why does step 19 implement Attach/Detach methods, why does it implement queryable operations?
        2. Why does step 20 implement GetPersonByEmail, GetPostById? These are smells that we are testing too much at once! This is a unit test smell I will talk about in my next post with testing tips. Some of this is needed because of the integration style testing that is going on in this post, my testing tips will tackle and remove this complexity.
          1. We now have code that isn’t tested in production that likely has needs to be tested. Things like GetPersonById and all the other query/add operations on the BlogRepository!
    5. Refactor
      1. Remove duplication between test and production code, or in production code alone
      2. Perform any other optimizations
    6. Make sure test still passes
    7. Repeat

I hope that this helps clear up some of the confusion that might exist around the differences between testability and TDD. If you want to know more I highly suggest Kent Beck’s book “Test Driven Development: By Example”, it is a great introduction to TDD with fully documented walkthroughs!

See my next blog post where I continue an analysis of this article and offer up some testing tips.

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&lt;IActionInvoker&gt;();
}

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&lt;IFilterLocator&gt;();

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

foundFilters.ForEach(f =&gt; 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&lt;IJoinedFilter&gt;()
  .Where(i =&gt; i.JoinsTo(controllerContext, actionDescriptor)).ToList();

if (joinedFilters != null)
{
  joinedFilters.OfType&lt;IActionFilter&gt;().ForEach(filters.ActionFilters.Add);
  joinedFilters.OfType&lt;IExceptionFilter&gt;().ForEach(filters.ExceptionFilters.Add);
  joinedFilters.OfType&lt;IAuthorizationFilter&gt;().ForEach(filters.AuthorizationFilters.Add);
  joinedFilters.OfType&lt;IResultfilter&gt;().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