简单注入器构造函数参数
Simple Injector constructor parameter
我在项目中使用 Simple Injector 作为 DI 容器。
问题是我有一个 SqliteStorage
-class,它需要数据库的路径。有多个数据库,所以我需要一种方法来在创建时注入 SqliteStorage
-class 的路径。
我的代码如下所示(没有接口的简化):
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" });
可能的解决方案:
- 在
SqliteStorage
中的每个方法中包含 SqliteStorageOptions
作为参数。
- 在
SqliteStorage
中提供init
方法
- 使用
public SqliteStorage Create(SqliteStorageOptions options)
方法创建 SqliteStorageFactory
。
那么在 simple-injector 中是否有解决我的问题的内置解决方案,或者有人可以提供另一个(更好的)解决方案吗?
谢谢
编辑 1:
我添加了一些代码。 Db1
和 Db2
都连接到 sqlite-dbs(不同的数据库,不同的模式),所以我想将所有的 sqlite-stuff 提取到它自己的 class SqliteStorage
。因此,SqliteStorage
需要知道数据库路径。
每个数据库连接可以有 2 个单例。让我们考虑一个例子,首先我们需要为您的 StorageService 创建一个接口:
public interface IStorage
{
void UsePath();
}
现在让我们创建此存储服务的几个实现:
public class RedisStorage: IStorage
{
private readonly string _path;
public RedisStorage(string path)
{
_path = path;
}
public void UsePath()
{
Console.WriteLine($"Here's path: {_path}");
}
}
public class SqlStorage: IStorage
{
private readonly string _path;
public SqlStorage(string path)
{
_path = path;
}
public void UsePath()
{
Console.WriteLine($"Here's path: {_path}");
}
}
用于区分 IStorage 实现的枚举:
public class StorageSource
{
public enum StorageTypes
{
Redis=1,
Sql=2
}
}
完成后,让我们为存储源创建一个包装器:
public interface IStorageWrapper
{
void DoStuff();
}
现在是一个棘手的部分,实例化一个存储包装服务装饰器:
public class StorageServiceWrapper: IStorageWrapper
{
private readonly Func<string, IStorage> _storage;
public StorageServiceWrapper(Func<string, IStorage> storage)
{
_storage = storage;
}
public void UsePath()
{
_storage(StorageSource.StorageTypes.Redis.ToString()).DoStuff();
//uncomment for sql
//_storage(StorageSource.StorageTypes.Sql.ToString()).DoStuff();
}
}
为此,您需要在 Startup.cs 中注册您的 类,如下所示:
services.AddScoped<IStorageWrapper, StorageServiceWrapper>();
services.AddSingleton<RedisStorage>();
services.AddSingleton<SqlStorage>();
services.AddTransient<Func<string, IStorage>>(serviceProvider => key =>
{
switch (key)
{
case "Redis":
return serviceProvider.GetService<RedisStorage>();
default:
return serviceProvider.GetService<SqlStorage>();
}
});
这不会像调用 _storage.DoStuff();
那样漂亮,但我相信会帮助您解决问题。如果您仍然想把它放在手边,请考虑管理您的设置文件并使用您需要的 conn 字符串注入适当的 IOptions<> 实例并注册工厂方法。
哪种解决方案最好取决于您是否需要自动装配(自动构造函数注入)。使用条件注册(使用 RegisterConditional
)是一个不错的选择,但您已经意识到它仅限于仅根据其直接父项来确定注入。这意味着您不能根据其父父项(Db1
或 Db2
)使 SqliteStorageOptions
成为条件。
如果 Db1
和 Db2
类 仅依赖于 SqliteStorage
而不需要任何其他依赖项,自动装配不是真正的问题并且您的注册可以像下面这样简单:
container.Register<Db1>(
() => new Db1(new SqliteStorage(new SqliteStorageOptions { Path = "db1.db" }));
container.Register<Db2>(
() => new Db2(new SqliteStorage(new SqliteStorageOptions { Path = "db2.db" });
如果 Db1
和 Db2
中需要自动装配,RegisterConditional
提供了一个很好的选择,因为它启用了自动装配:
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));
在此代码段中,Db1
和 Db2
都已注册 'normally',而 SqliteStorage
注册是根据消费者有条件地注入的。
这个注册比较复杂,因为 RegisterConditonal
需要提供一个 Registration
实例:没有直接接受 Func<T>
工厂委托的 RegisterConditional
重载.
我在项目中使用 Simple Injector 作为 DI 容器。
问题是我有一个 SqliteStorage
-class,它需要数据库的路径。有多个数据库,所以我需要一种方法来在创建时注入 SqliteStorage
-class 的路径。
我的代码如下所示(没有接口的简化):
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" });
可能的解决方案:
- 在
SqliteStorage
中的每个方法中包含SqliteStorageOptions
作为参数。 - 在
SqliteStorage
中提供 - 使用
public SqliteStorage Create(SqliteStorageOptions options)
方法创建SqliteStorageFactory
。
init
方法
那么在 simple-injector 中是否有解决我的问题的内置解决方案,或者有人可以提供另一个(更好的)解决方案吗?
谢谢
编辑 1:
我添加了一些代码。 Db1
和 Db2
都连接到 sqlite-dbs(不同的数据库,不同的模式),所以我想将所有的 sqlite-stuff 提取到它自己的 class SqliteStorage
。因此,SqliteStorage
需要知道数据库路径。
每个数据库连接可以有 2 个单例。让我们考虑一个例子,首先我们需要为您的 StorageService 创建一个接口:
public interface IStorage
{
void UsePath();
}
现在让我们创建此存储服务的几个实现:
public class RedisStorage: IStorage
{
private readonly string _path;
public RedisStorage(string path)
{
_path = path;
}
public void UsePath()
{
Console.WriteLine($"Here's path: {_path}");
}
}
public class SqlStorage: IStorage
{
private readonly string _path;
public SqlStorage(string path)
{
_path = path;
}
public void UsePath()
{
Console.WriteLine($"Here's path: {_path}");
}
}
用于区分 IStorage 实现的枚举:
public class StorageSource
{
public enum StorageTypes
{
Redis=1,
Sql=2
}
}
完成后,让我们为存储源创建一个包装器:
public interface IStorageWrapper
{
void DoStuff();
}
现在是一个棘手的部分,实例化一个存储包装服务装饰器:
public class StorageServiceWrapper: IStorageWrapper
{
private readonly Func<string, IStorage> _storage;
public StorageServiceWrapper(Func<string, IStorage> storage)
{
_storage = storage;
}
public void UsePath()
{
_storage(StorageSource.StorageTypes.Redis.ToString()).DoStuff();
//uncomment for sql
//_storage(StorageSource.StorageTypes.Sql.ToString()).DoStuff();
}
}
为此,您需要在 Startup.cs 中注册您的 类,如下所示:
services.AddScoped<IStorageWrapper, StorageServiceWrapper>();
services.AddSingleton<RedisStorage>();
services.AddSingleton<SqlStorage>();
services.AddTransient<Func<string, IStorage>>(serviceProvider => key =>
{
switch (key)
{
case "Redis":
return serviceProvider.GetService<RedisStorage>();
default:
return serviceProvider.GetService<SqlStorage>();
}
});
这不会像调用 _storage.DoStuff();
那样漂亮,但我相信会帮助您解决问题。如果您仍然想把它放在手边,请考虑管理您的设置文件并使用您需要的 conn 字符串注入适当的 IOptions<> 实例并注册工厂方法。
哪种解决方案最好取决于您是否需要自动装配(自动构造函数注入)。使用条件注册(使用 RegisterConditional
)是一个不错的选择,但您已经意识到它仅限于仅根据其直接父项来确定注入。这意味着您不能根据其父父项(Db1
或 Db2
)使 SqliteStorageOptions
成为条件。
如果 Db1
和 Db2
类 仅依赖于 SqliteStorage
而不需要任何其他依赖项,自动装配不是真正的问题并且您的注册可以像下面这样简单:
container.Register<Db1>( () => new Db1(new SqliteStorage(new SqliteStorageOptions { Path = "db1.db" })); container.Register<Db2>( () => new Db2(new SqliteStorage(new SqliteStorageOptions { Path = "db2.db" });
如果 Db1
和 Db2
中需要自动装配,RegisterConditional
提供了一个很好的选择,因为它启用了自动装配:
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));
在此代码段中,Db1
和 Db2
都已注册 'normally',而 SqliteStorage
注册是根据消费者有条件地注入的。
这个注册比较复杂,因为 RegisterConditonal
需要提供一个 Registration
实例:没有直接接受 Func<T>
工厂委托的 RegisterConditional
重载.