Search code examples
c#dependency-injectionsimple-injector

Simple Injector constructor parameter


I'm using Simple Injector as DI Container in a project.

The problem is that I have a SqliteStorage-class, which needs the path to the db. There are multiple dbs, so I need a way to inject the path to the SqliteStorage-class at creation.

My code looks as follows (simplified without interfaces):

public class SqliteStorageOptions
{
    public string Path {get; set;}
}

public class SqliteStorage
{
    private readonly string _path;

    public SqliteStorage(SqliteStorageOptions options)
    {
        _path = options.Path;
    }
}

public class Db1
{
    private readonly SqliteStorage _sqlite;

    public Db1(SqliteStorage sqlite)
    {
        _sqlite = sqlite;
    }
}

public class Db2
{
    private readonly SqliteStorage _sqlite;

    public Db1(SqliteStorage sqlite)
    {
        _sqlite = sqlite;
    }
}


// without di
var db1 = new Db1(new SqliteStorage(new SqliteStorageOptions { Path = "db1.db" });
var db2 = new Db2(new SqliteStorage(new SqliteStorageOptions { Path = "db2.db" });

Possible Solutions:

  1. Include SqliteStorageOptions as parameter at every method in SqliteStorage.
  2. Provide a init-method in SqliteStorage
  3. Create a SqliteStorageFactory with a public SqliteStorage Create(SqliteStorageOptions options)-method.

So are there any built-in solution to my problem in simple-injector or can someone provide another (better) solution?

Thanks

Edit 1: I added some code. Db1 and Db2 both connect to sqlite-dbs (different dbs, different schema), so I wanted to extract all the sqlite-stuff to its own class SqliteStorage. So, the SqliteStorage needs to know the db path.


Solution

  • Which solution is best depends a bit on whether you require Auto-Wiring (automatic constructor injection) or not. Using conditional registrations (using RegisterConditional) is a good pick, but you have be aware that it is limited to determining the injection based on only its direct parent. This means that you can't make SqliteStorageOptions conditional based on its parent parent (either Db1 or Db2).

    If the Db1 and Db2 classes solely depend on a SqliteStorage and don't require any other dependencies, Auto-Wiring is not a real issue and your registrations can be as simple as the following:

    container.Register<Db1>(
        () => new Db1(new SqliteStorage(new SqliteStorageOptions { Path = "db1.db" }));
    container.Register<Db2>(
        () => new Db2(new SqliteStorage(new SqliteStorageOptions { Path = "db2.db" });
    

    In case Auto-Wiring is required inside Db1 and Db2, RegisterConditional gives a good alternative, because it enables Auto-Wiring:

    container.Register<Db1>();
    container.Register<Db2>();
    
    container.RegisterConditional<SqliteStorage>(
        Lifestyle.CreateRegistration(
            () => new SqliteStorage(new SqliteStorageOptions { Path = "db1.db" }),
            container),
        c => c.Consumer.ImplementationType == typeof(Db1));
    
    container.RegisterConditional<SqliteStorage>(
        Lifestyle.CreateRegistration(
            () => new SqliteStorage(new SqliteStorageOptions { Path = "db2.db" }),
            container),
        c => c.Consumer.ImplementationType == typeof(Db2)); 
    

    In this code snippet, both Db1 and Db2 are registered 'normally', while the SqliteStorage registrations are conditionally injected based on thei consumer.

    This registration is more complex, because RegisterConditonal need to be supplied with a Registration instance: there is no RegisterConditional overload that directly accepts a Func<T> factory delegate.