Michael Kropat Michael Kropat - 3 months ago 13
C# Question

Override what component implementation is injected for nested/transitive dependencies

Say I've got two classes that are instantiated by Castle Windsor, and each has a dependency on the same interface:


  • FooRepo
    IApiClient

  • BarRepo
    IApiClient



In this case
IApiClient
is implemented by one class,
GenericApiClient
, that knows how to communicate with any API. However, I want to create different instances of
GenericApiClient
that are passed different config values (exposed via
IApiClientConfiguration
) so that
FooRepo
talks to the Foo API endpoint and the
BarRepo
talks to the Bar API endpoint:


  • FooRepo
    IApiClient (GenericApiClient)
    IApiClientConfiguration (FooClientConfiguration)

  • BarRepo
    IApiClient (GenericApiClient)
    IApiClientConfiguration (BarClientConfiguration)



Here's what I've tried so far:

container = new WindsorContainer();
container.Register(
Component.For<HomeController>().LifeStyle.Transient,
Component.For<FooRepo>()
.LifeStyle.Transient,
Component.For<BarRepo>()
//.DependsOn(Dependency.OnComponent<IApiClientConfiguration, BarClientConfiguration>()) // this does nothing cause the client config is not a direct dependency :(
.LifeStyle.Transient,
Component.For<IApiClient>()
.ImplementedBy<GenericApiClient>()
//.DependsOn(Dependency.OnComponent<IApiClientConfiguration, BarClientConfiguration>()) // this overrides for both FooRepo and BarRepo :(
.LifeStyle.Transient,
Component.For<IApiClientConfiguration>()
.ImplementedBy<FooClientConfiguration>()
.LifeStyle.Transient,
Component.For<IApiClientConfiguration>()
.ImplementedBy<BarClientConfiguration>()
.LifeStyle.Transient);


I'm having trouble figuring out how to get the
FooRepo
to get an instance of
GenericApiClient
configured with
FooClientConfiguration
, with
BarRepo
getting a
BarClientConfiguration
:


  • By default they both get
    FooClientConfiguration
    since that's what's registered first

  • I can override the config for
    IApiClient
    using
    DependsOn(...)
    , but that applies to both
    FooRepo
    and
    BarRepo

  • If I use
    DependsOn(...)
    on the
    BarRepo
    , it has no effect



(In case the above question is not clear, I have a minimum working example here)

Is there some way I can configure Castle Windsor to do what I want? Is there some way to better structure my code so I don't have this problem?

Answer
container.Register(
    Component.For<IApiClientConfiguration>()
        .ImplementedBy<FooClientConfiguration>()
        .Named("FooConfiguration")
        .LifestyleTransient(),
    Component.For<IApiClientConfiguration>()
        .ImplementedBy<BarClientConfiguration>()
        .Named("BarConfiguration")
        .LifestyleTransient(),
    Component.For<IApiClient, GenericApiClient>()
        .Named("FooClient")
        .DependsOn(Dependency.OnComponent(
            typeof(IApiClientConfiguration), "FooConfiguration")),
    Component.For<IApiClient, GenericApiClient>()
        .Named("BarClient")
        .DependsOn(Dependency.OnComponent(
            typeof(IApiClientConfiguration), "BarConfiguration")),
    Component.For<FooRepo>()
        .DependsOn(Dependency.OnComponent(typeof(IApiClient), "FooClient")),
    Component.For<BarRepo>()
        .DependsOn(Dependency.OnComponent(typeof(IApiClient), "BarClient"))
    );

It's not pretty. And there may be a way to simplify the syntax a little. Windsor usually offers a few different ways to do everything.

You're defining two different implementations of GenericApiClient, and for each you're specifying which configuration to use. Then, when registering FooRepo and BarRepo, for each of them you're specifying which named implementation of IApiClient to use.

If one or the other is the default then you can indicate it using IsDefault() and only name the other. It can also be easier to follow if you write separate installers for separate groups of implementations, or even just methods in the same installer, like

RegisterFooDependencies(IWindsorContainer container)
{
    container.Register(
        Component.For<IApiClientConfiguration>()
            .ImplementedBy<FooClientConfiguration>()
            .Named("FooConfiguration")
            .LifestyleTransient(),
        Component.For<IApiClient, GenericApiClient>()
            .Named("FooClient")
            .DependsOn(Dependency.OnComponent(
                typeof(IApiClientConfiguration), "FooConfiguration")),
        Component.For<FooRepo>()
            .DependsOn(Dependency.OnComponent(typeof(IApiClient), "FooClient"))          
    );
}