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

1

6

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.

Geoffrey answered 16/12, 2014 at 14:53 Comment(0)
C
6

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());
Cock answered 16/12, 2014 at 16:20 Comment(4)
Thanks for the thorough answer. The option 1 suits me the best so I tried that. However, when I started using DynamicParameters my WCF (registered to Windsor container with WCF facility) service stopped working. Not sure if this is some bug? The WCF service class is instantiated fine but service endpoint is not created by Windsor. When I remove DynamicParameters it works again... Am I missing something? Seems like two unrelated things are interfering with each other.Geoffrey
Not sure honestly. If you can't figure it out, feel free to create another question!Cock
@PatrickQuirk any chance of getting an update for that link as it's not working anymore?Ignacioignacius
@TheBeardedLlama Fixed!Cock

© 2022 - 2024 — McMap. All rights reserved.