Search code examples
c#.netdependency-injectioninversion-of-controlcastle-windsor

How to constructor-inject a string that is only known at runtime? (Windsor Castle)


I have class that has dependency on string:

public class Person
{
    private readonly string _name;

    public Person(string name)
    {
        if (name == null) throw new ArgumentNullException("name");
        _name = name;
    }
}

This string 'name' is known only at runtime, eg. it is defined in configuration. So I have this interface that provides this string:

public interface IConfiguration
{
    string Name { get; }
}

Both types, Person and IConfiguration (with its implementation which is not important here) are registered with Windsor container.

Question: how can I tell WindsorCastle container that it should inject the Name property of IConfiguration to the constructor of Person class?

Caveat: I don't want to inject IConfiguration to Person class or use typed factories... the Person class must be simple and accept only string as parameter.


Solution

  • There are probably more ways to do this since Windsor is ridiculously flexible, but here are three off the top of my head:

    Option 1:

    If IConfiguration is a singleton or somehow can populate Name without any other assistance, you could do the following:

    container.Register(Component.For<IConfiguration>().ImplementedBy<Configuration>());
    container.Register(Component
        .For<Person>()
        .DynamicParameters((DynamicParametersDelegate)ResolvePersonName));
    
    // This should be a private method in your bootstrapper 
    void ResolvePersonName(IKernel kernel, IDictionary parameters)
    {
        parameters["name"] = kernel.Resolve<IConfiguration>().Name;
    }
    

    This method is invoked before resolving the Person, and is setting the name key/value pair to be the desired value. Windsor then uses this dictionary to override any dependencies in the constructor. I'd probably just make it a lambda to be terser:

        .DynamicParameters((k,p) => p["name"] = k.Resolve<IConfiguration>().Name));
    

    Option 2:

    If the Name value is actually defined in the applications settings file, Windsor has a built-in option for that:

    container.Register(
        Component.For<Person>()
        .DependsOn(Dependency.OnAppSettingsValue("name", "configSettingKeyName")));
    

    Option 3:

    You say you don't want to use Typed Factories, but I think this is a reasonable siutation for using one. It wouldn't complicate the Person class at all, but it would add a bit to the code creating the Person objects (people?). Here's an example:

    Define the factory interface:

    public interface IPersonFactory
    {
        Person Create(string name);
    }
    

    Then where you are creating your Person, inject the factory and use that instead:

    public class PersonUser
    {
        public Personuser(IConfiguration configuration, IPersonFactory personFactory)
        {
            Person person = personFactory.Create(configuration.Name);
        }
    }
    

    You'll have to add the facility and register the interface, but it's easy:

    container.AddFacility<TypedFactoryFacility>();
    container.Register(Component.For<IPersonFactory>().AsFactory());