Castle Windsor new feature – dynamic parameters from registration site

UPDATE:

I renamed the method from WithParameters to DynamicParameters to avoid confusion and be consistent with Parameters method which is used for static parameters.

I just committed small new feature to Castle Windsor, that I think can nicely clean up your code. It remedies the following problem:

Problem

What if you have a component that relies on a dynamically provided primitive value? Things like current request’s Uri, or DateTime.Now?

public class UsesCurrentTime

{

   UsesCurrentTime(DateTime currentTime)

   {

      Console.WriteLine("Current time is {0}”, currentTime);

   }

}

Solution – the old way

Until now you had two choices:

  • wrap the dependency in a service, like ICurrentUriProvider, or IClock and take dependency on that interface
    public class UsesCurrentTime

    {

       UsesCurrentTime(IClock currentTime)

       {

          Console.WriteLine("Current time is {0}", currentTime.Now);

       }

    }

  • in the code using that service, reference the container and pull the service yourself, instead of having it injected.
    public class Uses_UsesCurrentTime

    {

       private IKernel container;

     

       public void DoSomethingWithCurrentTime()

       {

          var service = container.Resolve<UsesCurrentTime>(new {currentTime = DateTime.Now});

          // do something with the service here

       }

    }

Neither of these approaches is ideal. The new feature I mentioned is here to save the day. It basically lets you take the best of both approaches, without paying their price. You get to pass the parameters without additional layer of indirection, and without referencing container and pulling the service manually.

Solution – the new way

To use it, we need to use a new method on the fluent registration API: DynamicParameters, like this.

kernel.Register(

    Component.For<UsesCurrentTime>()

        .LifeStyle.Transient

        .DynamicParameters((kernel, parameters) => parameters["currentTime"] = DateTime.Now));

the method takes a delegate that will be invoked when the component is being resolved, but before any actual resolution, parameter matching, appropriate constructor searching etc happens. It gives you access to two things: the container itself, and the dictionary of parameters, that were passed from the call site. You can then inspect the parameters, add new, remove them, etc.

What’s more interesting – you can use it not only for primitives. You can also dynamically override components.

kernel.Register(

    Component.For<ICustomer>()

        .ImplementedBy<CustomerImpl>()

        .Named("defaultCustomer"),

    Component.For<ICustomer>().ImplementedBy<CustomerImpl2>()

        .Named("otherCustomer")

        .Parameters(

            Parameter.ForKey("name").Eq("foo"), // static parameters, resolved at registration time

            Parameter.ForKey("address").Eq("bar st 13"),

            Parameter.ForKey("age").Eq("5")),

    Component.For<CommonImplWithDependancy>()

        .LifeStyle.Transient

        .DynamicParameters((k, d) => // dynamic parameters

        {

            var randomNumber = 2;

            if (randomNumber == 2)

            {

                d["customer"] = k.Resolve<ICustomer>("otherCustomer");

            }

        }));

 

var component = kernel.Resolve<CommonImplWithDependancy>();

Assert.IsInstanceOf<CustomerImpl2>(component.Customer);

This test will pass.

Technorati Tags:
  • Jan

    looks über cool!
    I wonder though how you would get to the current request`s uri…

  • @Jan

    HttpContext.Current.Request.Url;

    Am I missing something here?

  • Brad Mead

    Krzysztof,

    I am looking for the Windsor equivalent for StructureMap’s EnrichWith(..) convention. It was touched upon here:

    http://tunatoksoz.com/post/Implementing-EnrichWith(of-StructureMap)-with-Castle.aspx

    I am wondering. It looks like the delegate injection/interception here can be used to the same effect. Can you verify this. In other words, am I missing anything that would prevent a similar usage?

    Thanks,
    Brad

  • Brad,

    OnCreate is the equivalent of EnrichWith. It’s not core part of Windsor and does not requite any facility.

    DynamicParamteres serve different purpose.

  • Brad Mead

    Does the OnCreate interception allow injection of constructor arguments or only properties? The documentation shows only support Property assignment:

    kernel.Register(Component.For<MyComp>().OnCreate((kernel,item)=>item.Name="tuna"));

    Is there a way to set constructor arguments?

  • Brad,

    OnCreate executes after object is created.

    If you want to affect its constructor arguments, you should use DynamicParameters or UsingFactoryMethod