Friday, May 1, 2009

Windsor - configurable component initialization

This question was raised on the Castle forums a couple of days ago:

I have a third party dependency. It uses a property class for configuration with only a default constructor.

public class Properties { 
      public Properties() {...} 

      public void Add(string name, string value) {...} 
} 

I would love to be able to invoke the .Add to setup this object. Something like this:

<components> 
  <component id="Properties" type="Example.Properties, thirdParty"> 
    <Add> 
      <name>key1</name> 
      <value>value1</value> 
    </Add> 
    <Add> 
      <name>key2</name> 
      <value>value2</value> 
    </Add> 
  </component> 
</components> 

I do realize that I could write an adapter class to interact with the Properties class and then di the adapter. But I'm wondering if I'm missing something or if there is a reason that method invocation is not supported.

Windsor has so many extensibility points that sometimes I have a hard time picking the right one. Windsor lets you change, override or customize almost every aspect of its behaviour thanks to its extensible design. To solve this one, I chose to override the default component activator. The component activator is the internal service responsible for instantiating the component object. To quote Windsor's reference manual:

The ComponentActivator takes a few steps to create the instance

  • Selects the constructor it can satisfy more parameters
  • Creates the instance using the constructor selected
  • Tries to supply dependencies to properties
  • Runs the commission phase lifecycle steps (if any was registered)

Our custom activator will call the default activator, then "deserialize" the method calls from the configuration to the appropriate MethodInfo objects, and finally call those methods on the component instance. We can use the componentActivatorType attribute to select the custom activator.

Here's a demo:

[TestFixture]
public class Tests {
    public class MyComponent {
        public int C { get; private set; }

        public void Add(int i) {
            C += i;
        }

        public void NoParameters() {
            C += 2;
        }
    }

    [Test]
    public void Init() {
        var c = new WindsorContainer(new XmlInterpreter(new StaticContentResource(@"<castle>
<components>
<component id=""mycomponent"" type=""WindsorInitConfig.Tests+MyComponent, WindsorInitConfig"" componentActivatorType=""WindsorInitConfig.InitComponentActivator, WindsorInitConfig"">
<init>
    <Add>
        <i>5</i>
    </Add>
    <Add>
        <i>3</i>
    </Add>
    <NoParameters/>
</init>
</component>
</components>
</castle>")));
        Assert.AreEqual(10, c.Resolve<MyComponent>().C);
    }
}

 

You can checkout the whole code here. Note that this is not really production-quality code: it's not properly tested and it probably won't work on generic methods and overloaded methods with the same parameter names, but it's enough for most cases. Please feel free to enhance it and send me a patch! :-)

No comments:

Post a Comment