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.
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?
ReplyDeleteMike - difference is like between pushing and pulling
ReplyDeleteMike, 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.
ReplyDeleteThanks 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.
ReplyDeleteI'm trying to replace this code,
ReplyDeletehttp://pastebin.com/N16Bu4cs
with something using Windsor.
Is it possible using this code (or something similar)?
Hi Dan,
ReplyDeleteYes, 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.
Actually, I'm more interested in instantiating the types that implement a certain interface and then run a method on that interface.
ReplyDeleteIn 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.
That's an even better use case for a cotainer. I've pretty much described how to do it in this post.
ReplyDeleteNice article! How would that translate to xml configuration?
ReplyDelete