Search code examples
c#.netcastle-windsorioc-container

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:

  • FooRepoIApiClient
  • BarRepoIApiClient

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:

  • FooRepoIApiClient (GenericApiClient)IApiClientConfiguration (FooClientConfiguration)
  • BarRepoIApiClient (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?


Solution

  • 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"))          
        );
    }