Friday, May 08, 2009

Parameterised component resolution with the Windsor NamingPartsSubSystem

Here’s a neat little trick I’ve recently learnt that my favourite IoC container can do.

Sometimes you need to resolve different components for the same service depending on some property. This is easy to do in Windsor with the NamingPartsSubSystem.

First register the subsystem with the AddSubSystem method, passing in a new instance of the NamingPartsSubSystem.

You can then give your components names differentiated by any number of parameters. For example we can have several components with the base name ‘thing’ and with a comma separated list of properties, in our case colour and version. The syntax is:

<name>:<key 1>=<value 1>[,<key n>=<value n>]

We can now resolve components based on any combination of those parameters. If more than one component matches, the container simply returns the first one to be registered.

Here’s a little test to demonstrate:

   1: using Castle.MicroKernel;
   2: using Castle.MicroKernel.Registration;
   3: using Castle.MicroKernel.SubSystems.Naming;
   4: using Castle.Windsor;
   5: using NUnit.Framework;
   6:  
   7: namespace Suteki.Blog.Tests.Spikes
   8: {
   9:     [TestFixture]
  10:     public class NamingPartsSubsystemSpike
  11:     {
  12:         [SetUp]
  13:         public void SetUp()
  14:         {
  15:         }
  16:  
  17:         [Test]
  18:         public void GetComponentsByHostname()
  19:         {
  20:             var container = new WindsorContainer();
  21:             container.Kernel.AddSubSystem(SubSystemConstants.NamingKey, new NamingPartsSubSystem());
  22:  
  23:             container.Register(
  24:                     Component.For<IThing>().ImplementedBy<Thing1>().Named("thing:colour=red,version=1"),
  25:                     Component.For<IThing>().ImplementedBy<Thing2>().Named("thing:colour=blue,version=1"),
  26:                     Component.For<IThing>().ImplementedBy<Thing3>().Named("thing:colour=red,version=2"),
  27:                     Component.For<IThing>().ImplementedBy<Thing4>().Named("thing:colour=blue,version=2")
  28:                 );
  29:  
  30:             var defaultThing = container.Resolve("thing");
  31:  
  32:             Assert.That(defaultThing, Is.Not.Null);
  33:             Assert.That(defaultThing, Is.TypeOf(typeof(Thing1)));
  34:  
  35:             var redThing = container.Resolve("thing:colour=red");
  36:  
  37:             Assert.That(redThing, Is.Not.Null);
  38:             Assert.That(redThing, Is.TypeOf(typeof(Thing1)));
  39:  
  40:             var blueThing = container.Resolve("thing:colour=blue");
  41:  
  42:             Assert.That(blueThing, Is.Not.Null);
  43:             Assert.That(blueThing, Is.TypeOf(typeof(Thing2)));
  44:  
  45:             var version2 = container.Resolve("thing:version=2");
  46:  
  47:             Assert.That(version2, Is.Not.Null);
  48:             Assert.That(version2, Is.TypeOf(typeof(Thing3)));
  49:  
  50:             var blueThingVersion2 = container.Resolve("thing:colour=blue,version=2");
  51:  
  52:             Assert.That(blueThingVersion2, Is.Not.Null);
  53:             Assert.That(blueThingVersion2, Is.TypeOf(typeof(Thing4)));
  54:         }
  55:  
  56:         private class IThing {}
  57:         private class Thing1 : IThing {}
  58:         private class Thing2 : IThing {}
  59:         private class Thing3 : IThing {}
  60:         private class Thing4 : IThing {}
  61:     }
  62: }

1 comment:

Anonymous said...

Wow. Just what i need when i need it. Im using the Email bits from Suteki in another app, and i need to set the TemplatePath based on the host.

Off to code...