Wednesday, January 13, 2010

10 Advanced Windsor Tricks – 3. How to resolve arrays

Here’s part three of (at least) 10 Advanced Windsor Tricks.

By default Windsor will not resolve arrays. That is, if you have a number of components registered with the same service, resolving an array of that service will result in an error saying that it cannot satisfy the array dependency.

However there’s a simple solution. Windsor comes with an optional sub-dependency-resolver that can resolve arrays; the ArrayResolver. Here’s how you use it:

First let’s meet our old friends the Things:

public class ThingOne : IThing
{
    public string SayHello(string name)
    {
        return string.Format("ThingOne says hello to {0}", name);
    }
}

public class ThingTwo : IThing
{
    public string SayHello(string name)
    {
        return string.Format("ThingTwo says hello to {0}", name);
    }
}

public class ThingThree : IThing
{
    public string SayHello(string name)
    {
        return string.Format("Hello {0} from ThingThree", name);
    }
}

And here’s a class with a dependency on an array of IThing:

public class UsesThingArray
{
    public IThing[] Things { get; private set; }

    public UsesThingArray(IThing[] things)
    {
        Things = things;
    }
}

I like to wrap the ArrayResolver in a little facility (it would be nice if this was in Windsor), but you don’t have to do this:

public class ArrayFacility : IFacility
{
    public void Init(IKernel kernel, IConfiguration facilityConfig)
    {
        kernel.Resolver.AddSubResolver(new ArrayResolver(kernel));
    }

    public void Terminate(){ }
}

Now you can use fluent registration to register the ArrayFacility and components:

var container = new WindsorContainer()
    .AddFacility<ArrayFacility>()
    .Register(
        AllTypes
            .Of<IThing>()
            .FromAssembly(Assembly.GetExecutingAssembly())
            .WithService.FirstInterface(),
        Component.For<UsesThingArray>()
    );

And finally, this code now works with all three things having their ‘SayHello’ method executed:

var usesThingArray = container.Resolve<UsesThingArray>();
foreach (var thing in usesThingArray.Things)
{
    Console.WriteLine(thing.SayHello("Leo"));
}

The nice thing about this is that if I want to add another element to the array, I simply create a new class that implements IThing which automatically gets registered and handed to UsesThingArray without a single change to any existing code. It’s craftier than a fox who has just graduated from Ruse University with a Degree in cunning!

There is a caveat; the ArrayResolver voids Windsor’s circular dependency detection. See my previous post on this for more detail.

9 comments:

  1. How does this differ from ResolveAll. Not terribly familair with windosr but I thought that would return an array of registered types matching an interface?

    ReplyDelete
  2. Mike - difference is like between pushing and pulling

    ReplyDelete
  3. Mike, you are correct, ResolveAll would return the same array of IThing, but it requires a dependency on the container. One of the primary rules of IoC container use is that you should avoid having a reference to the container. The whole point of the container is to automatically provide dependencies described using Dependency Injection.

    ReplyDelete
  4. Thanks agian for this good blog series. I saw a code technique using StructureMap's PluginFamilyAttribute I wanted to do in Windsor. This facility is what it takes.

    ReplyDelete
  5. I'm trying to replace this code,
    http://pastebin.com/N16Bu4cs
    with something using Windsor.

    Is it possible using this code (or something similar)?

    ReplyDelete
  6. Hi Dan,

    Yes, Windsor is ideal for that kind of thing. See

    http://mikehadlow.blogspot.com/2010/10/experimental-aspnet-mvc-add-ins-updated.html

    For an example of dynamically loaded assemblies with Windsor.

    ReplyDelete
  7. Actually, I'm more interested in instantiating the types that implement a certain interface and then run a method on that interface.

    In my case, the IConfigurable.GetColumns() just reads the properties that are marked with a certain attribute.
    After I have the list, I cache it and I don't need the assembly anymore.

    ReplyDelete
  8. That's an even better use case for a cotainer. I've pretty much described how to do it in this post.

    ReplyDelete
  9. Nice article! How would that translate to xml configuration?

    ReplyDelete

Note: only a member of this blog may post a comment.