Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simple Injector - Register Service with one custom Parameter

I try to register a communication service with a custom parameter "server". But i want automatic resolve the ILogger dependency and not set this dependency twice.

Whats the best way to do this?

SimpleInjector Register

var diContainer = new Container();
diContainer.Register<ILogger, DefaultLogger>();
//Good
diContainer.Register<ICommunicationService>(
    () => new CommunicationService("server1"), 
    Lifestyle.Singleton);
//Bad
diContainer.Register<ICommunicationService>(
    () => new CommunicationService(new DefaultLogger(), "server1"),
    Lifestyle.Singleton);

CommunicationService Class

public class CommunicationService : ICommunicationService
{
    public CommunicationService(ILogger logger, string server)
    {
    }
}

Update 2017-11-27

@Steven I want run two instances of CommunicationService with different configuration. In my example server1 and server2.

var container = new Container();
container.Register<ILogger, DefaultLogger>();
container.RegisterSingleton(new CommunicationServiceConfig { Server = "server1" });
container.RegisterSingleton(new CommunicationServiceConfig { Server = "server2" });

diContainer.Register<ICommunicationService, CommunicationService>(Lifestyle.Singleton);
diContainer.Register<ICommunicationService, CommunicationService>(Lifestyle.Singleton);
like image 983
live2 Avatar asked Nov 24 '17 15:11

live2


2 Answers

Injection of primitive types is always an area of trouble for DI Containers, since primitive types such as string int and bool cause ambiguity.

The simplest solution therefore is to wrap this string configuration value into its own configuration class, for instance:

public class CommunicationServiceConfig
{
    public string Server { get; set; }
}

And let CommunicationService depend on on CommunicationServiceConfig rather than string:

public class CommunicationService : ICommunicationService
{
    public CommunicationService(ILogger logger, CommunicationServiceConfig config)
    {
    }
}

With this change, the Composition Root becomes:

var container = new Container();
container.Register<ILogger, DefaultLogger>();
container.RegisterSingleton(new CommunicationServiceConfig { Server = "server1" });

diContainer.Register<ICommunicationService, CommunicationService>(Lifestyle.Singleton);
like image 98
Steven Avatar answered Nov 02 '22 10:11

Steven


Not ideal, but it seems you can capture the container itself to resolve dependencies which have already been registered, in the actual bootstrapping phase itself, which can then be passed to the factory function registration, i.e.:

    diContainer.Register<ICommunicationService>(
        () => new CommunicationService(
         diContainer.GetInstance<ILogger>(), "server1"), Lifestyle.Singleton);

Thus allowing a blend of hard coded values, e.g. for Config, Connection strings etc, plus the other resolved dependencies, although this will result in a lot of maintenance pain if constructor signatures are changed.

One thing to note is that SimpleInjector freezes registration once the first resolution has been made, so for example, the following isn't possible:

var sharedLogger = diContainer.GetInstance<ILogger>();

diContainer.Register<ICommunicationService>(
        () => new CommunicationService(sharedLogger, "server1"), Lifestyle.Singleton);
// Other registrations with `sharedLogger`

System.InvalidOperationException : The container can't be changed after the first call to GetInstance

i.e. you'll need to ensure your factory methods stay lazy.

like image 42
StuartLC Avatar answered Nov 02 '22 12:11

StuartLC