Search code examples
c#.netdependency-injectionioc-containersimple-injector

Inject two specific interface implementations into constructor with Simple Injector


I have and interface IConnector. And have some implementations, say SomeConnector. And my use case looks like:

public class Worker : IWorker
{
    public Worker(IConnector dataConnector, IConnector transactionConnector) {}
}

public class SomeConnector : IConnector
{
    public SomeConnector(IConnectorContext connectorContext) {}
}

In the Worker constructor I need to have two instances of IConnector. And not just two instances, but two specific instances, created with their own contexts. How can I make this registration?

Update1

Adding ConnectorContext implementation

public class SomeConnectorContext : IConnectorContext
{
    public List<string> Types { get; }
    public int DataTimeoutSeconds { get; }
    public string Key { get; }
    public string ConnectorName { get; }

    public SomeConnectorContext(
        List<string> types, int dataTimeoutSeconds, string key, string connectorName)
    {
        Types = types;
        DataTimeoutSeconds = dataTimeoutSeconds;
        Key = key;
        ConnectorName = connectorName;
    }
}

Update2

In fact I need some kind of conditional registration based on config. For example something like:

switch (workerType)
{
    // the following code is obviously incorrect, because I
    // don't know how make registrations properly in this case.
    case "worker1":
        //create context for dataConnector based on config.
        Container.Register<IConnectorContext>(new SomeConnectorContext(...));
        //this is dataConnector. and should use dataConnector context.
        Container.Register<IConnector, SomeConnector>();

        //create context for transactionConnector based on config.
        Container.Register<IConnectorContext>(new SomeConnectorContext(...));
        //this is transactionConnector. and should use transactionConnector context.
        Container.Register<IConnector, SomeConnector>();

        Container.Register<IWorker, Worker1>();
        break;

    //in the following case Worker2 needs only one Connector
    case "worker2":
        //create context for allPurposeConnector based on config.
        Container.Register<IConnectorContext>(new SomeConnectorContext(...));
        //this is allPurposeConnector. and should use allPurposeConnector context.
        Container.Register<IConnector, SomeConnector>();

        Container.Register<IWorker, Worker2>();
        break;
}

Update3 Adding workerType assignment example.

workerType is a configuration value. For example it can be set like this: workerType = Properties.Settings.Default.WorkerType;


Solution

  • I see 2 options.

    You can hand-wire your dependencies by using a lambda. This means you only register IWorker and build it and its dependencies manually. This disables Auto-Wiring, but results in quite straightforward code, as shown in this example:

    switch (workerType)
    {
        case "worker1":
            var context1 = new SomeConnectorContext(...);
            var context2 = new SomeConnectorContext(...);
    
            Container.Register<IWorker>(() => Worker1(
                new SomeConnector(context1),
                new SomeConnector(context2)));
            break;
    
        case "worker2":
            var context1 = new SomeConnectorContext(...);
    
            Container.Register<IWorker>(() => Worker1(
                new SomeConnector(context1)));
            break;
    }
    

    You second option is to register SomeConnector as conditional registration. This allows you to make both SomeConnector registrations, and link them to their corresponding constructor parameters. This allows IWorker to be Auto-Wired, but does result in a more complex registration, as shown below:

    switch (workerType)
    {
        case "worker1":
            var context1 = new SomeConnectorContext(...);
            Container.RegisterConditional<SomeConnector>(
                Lifestyle.Transient.CreateRegistration(
                () => new SomeConnector(context1), container),
                c => c.Consumer.Target.Name == "dataConnector");
    
            var context2 = new SomeConnectorContext(...);
            Container.RegisterConditional<SomeConnector>(
                Lifestyle.Transient.CreateRegistration(
                () => new SomeConnector(context2), container),
                c => c.Consumer.Target.Name == "transactionConnector");
    
            Container.Register<IWorker, Worker1>();
            break;
    
        case "worker2":
            Container.Register<IConnectorContext>(new SomeConnectorContext(...));
            Container.Register<IConnector, SomeConnector>();
            Container.Register<IWorker, Worker2>();
            break;
    }