simpleinjector / SimpleInjector

An easy, flexible, and fast Dependency Injection library that promotes best practice to steer developers towards the pit of success.
https://simpleinjector.org
MIT License
1.21k stars 154 forks source link

How to handle mixed constructor parameters with multiple instances? ex. (DependentSvc svc, string p1, string p2) #978

Closed amguilmet closed 1 year ago

amguilmet commented 1 year ago

I'm looking for a way to handle mixed constructors with mixed dependencies and primitive types. I see in the docs and example for one way to handle this under the section "Resolving classes that have primitive types in their constructor" by wrapping the primitive types in a separate type i.e. "FilePathSettings". My problem is that this only works when the wrapper is resolvable to a single instance, So if I register a transient type that uses this wrapper, I'm limited to only a single instance of the wrapper type when the transient type is resolved; at least that's how I'm interpreting this. I could be totally wrong here.

For example, if I register a file reader service that has some auto-wired dependency how can I pass it specific wrapper instances-

public SystemFileReader(ISomeDependency svc, IFileReaderSettings settings) : IFileReader { ... }
public InMemFileReader(ISomeDependency svc, IFileReaderSettings settings) : IFileReader { ... }

container.Register<ISomeDependency, SomeDependency>();
container.Register<IFileReader, SystemFileReader>(Lifestyle.Transient);
//container.Register<IFileReader, InMemFileReader>(Lifestyle.Transient); //Used in testing only

container.RegisterInstance(new FilePathSettings("C:\first.txt"));
container.RegisterInstance(new FilePathSettings("C:\second.txt")); //How to do this?

var inst1 = container.GetInstance<IFileReader>(); //I want to create the instance using first.txt
var inst2 = container.GetInstance<IFileReader>(); // I want to create this instance using second.txt

Is there some sort of way to pass a specific instance of the FilePathSettings wrapper type to GetInstance<IFileReader>()?

dotnetjunkie commented 1 year ago

It's unclear to me what it is you are trying to achieve. In your example you are requesting IFileReader twice from the container, yet you expect to get something different in return the second time? What would cause the injected value to change the second time? This is information that seems to be missing from the question.

Might it be that C:\first.txt and C:\second.txt are runtime-data values? If so, please read this, because composing application components using runtime data leads to lots of complications. This might be the reason you are in this confusing.

But if this isn't runtime data, please elaborate, and describe what it is you are trying to achieve? This might require you to take a step back and describe the feature you are building.

amguilmet commented 1 year ago

I needed the resolved instances to be able to mix both constructor injection along with primitives. In the documentation, it is suggested to wrap the primitives in a type that can be injected like this:

// New class that wraps the connection string.
public class SqlUserRepositorySettings
{
    public SqlUserRepositorySettings(string connectionString) =>
        this.ConnectionString = connectionString;

    public string ConnectionString { get; }
}

public class SqlUserRepository : IUserRepository
{
    private readonly IUserContext userContext;
    private readonly SqlUserRepositorySettings settings;

    // Depend on the new wrapper type instead
    public UserController(
        IUserContext userContext, SqlUserRepositorySettings settings)
    {
        this.userContext = userContext;
        this.settings = settings;
    }
}

container.Register<IUserRepository, SqlUserRepository>();
container.RegisterInstance(new SqlUserRepositorySettings("some constr"));

AFAIK, only one instance SqlUserRepositorySettings can be registered at a time with a particular connection string so it wouldn't be possible to pass different instances that have different ConnectionString values to different registrations of classes that required it to be injected like SqlUserRepository does.

I've since solved this with factory classes and passing the required primitives to the factory's properties and just Create()'ing to get my configured instance.