Resolve different instances based on configuration

220 Views Asked by At

I'm trying to use Ninject as my IoC framework for a Windows Service. I have the following classes and interfaces:

Assembly Core:

public class Orchestrator : IOrchestrator
{
    ...

    public Orchestrator(ITerminal terminal)
    {
        ...
    }

    ...
}

Assembly Vx520:

public class Vx520 : ITerminal
{
    ...

    public Vx520(string comPort, int bauds, int dataBits, Parity parity, StopBits stopBits)
    {
        ...
    }

    ...
}

Assembly Vx580:

public class Vx580 : ITerminal
{
    ...

    public Vx580(string ip, int port)
    {
        ...
    }

    ...
}

My idea is to use a ConfigurationSection to let the user configure which and how many terminals he wants. For example, in the following configuration, I should get 3 Orcestrator instances, one with a Vx520 terminal, and the other two with Vx580, each with it's custom configuration:

<Terminals>
  <add Type="Vx520" ComPort="COM3" Bauds="9600" DataBits="8" Parity="None" StopBits="One" />
  <add Type="Vx580" Ip="192.168.0.50" Port="33999"/>
  <add Type="Vx580" Ip="192.168.0.51" Port="33999"/>
</Terminals>

I have the configuration part working, with an IEnumerable<TerminalConfiguration> as the final output. Is there a way to dynamically resolve the dependency for the Orchestrator class using this list of configurations? If Ninject is uncapable of doing this, is there other IoC framework that you can recommend me?

Thank you in advance

1

There are 1 best solutions below

1
On BEST ANSWER

Ninject (or most or even all DI containers for that matter) cannot be used to control how many objects to instantiate based on some arbitrary "external" information. What you can do is having multiple bindings, like so:

IBindingRoot.Bind<IOrchestrator>().To<Orchestrator>();
IBindingRoot.Bind<IOrchestrator>().To<Orchestrator>();
IBindingRoot.Bind<IOrchestrator>().To<Orchestrator>();

and then inject them (ctor argument of type IEnumerable<IOrchestrator> or retrieve them by IResolutionRoot.GetAll<IOrchestrator>(). That will result in exactly 3 instances. You can also have conditional (contextual) bindings where you put a condition on a binding, for example:

IBindingRoot.Bind<ITerminal>().To<Vx520>()
    .When(x => Config.Terminal.Type == "VX520");
IBindingRoot.Bind<ITerminal>().To<Vx580>()
    .When(x => Config.Terminal.Type == "VX580");

and when you inject ITerminal / do IResolutionRoot.Get<ITerminal>() you will get a Vx520 or Vx580 (or a missing binding exception!) depending on the value of Config.Terminal.Type.

Now, of course, you could parse the config and create the bindings accordingly:

foreach(ITerminalConfig config in Terminals)
{
    IBindingRoot.Bind<IOrchestrator>().To<Orchestrator>()
        .WithParameter(new ConstructorArgument("config", config, shouldInherit: true));
}

So you would have 3 bindings for IOrchestrator, each with a ConstructorArgument which holds it's config. Since the ConstructorArgument has shouldInherit set to true, the types down the IOrchestrator tree can it injected and can also have conditions injected to it. For example, you could do something along the lines of:

IBindingRoot.Bind<ITerminal>().To<Vx580>()
    .When(ctx =>
    {
        ConstructorArgument configConstructorArgument = 
            ctx.Parameters
               .OfType<ConstructorArgument>()
               .Single(x => x.Name == "config");

        var config = (ITerminalConfig)configConstructorArgument.GetValue(null, null);
        return config.Type == "Vx520";
    });

and also have the ITerminalConfig injected into Vx520 (that's how you would get the IP & Port / comport & baudrate,.. settings).


But to be honest, instead of creating multiple Bind<IOrchestrator>().To<Orchestrator>() bindings, wouldn't it be better to have the creation done explicitly? Per some service initializer / application startup step which uses an IOrchestratorFactory to create the instances?