SimpleInjector-依赖于另一个注册对象的值的注册对象
本文关键字:对象 注册 依赖于 另一个 SimpleInjector- | 更新日期: 2023-09-27 18:00:30
使用SimpleInjector,我试图注册一个依赖于从另一个注册实体检索到的值的实体。例如:
Settings
-读取指示应用程序需要的SomeOtherService
类型的设置值。
SomeOtherService
-依赖于Settings
中的一个值进行实例化(因此进行注册)。
一些DI容器允许在解析另一个对象之后注册一个对象。所以你可以做一些类似下面的伪代码:
container.Register<ISettings, Settings>();
var settings = container.Resolve<ISettings>();
System.Type theTypeWeWantToRegister = Type.GetType(settings.GetTheISomeOtherServiceType());
container.Register(ISomeOtherService, theTypeWeWantToRegister);
SimpleInjector不允许在解析后进行注册。SimpleInjector中是否存在允许相同体系结构的某种机制?
获得此要求的一个简单方法是注册所有可能需要的可用类型,并进行配置以确保容器在运行时返回正确的类型。。。用英语解释不那么容易,让我示范一下。
您可以有一个接口的多个实现,但在运行时您需要其中一个,并且您需要的一个由文本文件中的设置(string
)控制。以下是测试类。
public interface IOneOfMany { }
public class OneOfMany1 : IOneOfMany { }
public class OneOfMany2 : IOneOfMany { }
public class GoodSettings : ISettings
{
public string IWantThisOnePlease
{
get { return "OneOfMany2"; }
}
}
所以,让我们继续注册它们:
private Container ContainerFactory()
{
var container = new Container();
container.Register<ISettings, GoodSettings>();
container.RegisterAll<IOneOfMany>(this.GetAllOfThem(container));
container.Register<IOneOfMany>(() => this.GetTheOneIWant(container));
return container;
}
private IEnumerable<Type> GetAllOfThem(Container container)
{
var types = OpenGenericBatchRegistrationExtensions
.GetTypesToRegister(
container,
typeof(IOneOfMany),
AccessibilityOption.AllTypes,
typeof(IOneOfMany).Assembly);
return types;
}
魔术发生在对GetTheOneIWant
的调用中——这是一个delegate
,直到Container
配置完成后才会被调用——以下是delegate
:的逻辑
private IOneOfMany GetTheOneIWant(Container container)
{
var settings = container.GetInstance<ISettings>();
var result = container
.GetAllInstances<IOneOfMany>()
.SingleOrDefault(i => i.GetType().Name == settings.IWantThisOnePlease);
return result;
}
一个简单的测试将确认它按预期工作:
[Test]
public void Container_RegisterAll_ReturnsTheOneSpecifiedByTheSettings()
{
var container = this.ContainerFactory();
var result = container.GetInstance<IOneOfMany>();
Assert.That(result, Is.Not.Null);
}
如前所述,Simple Injector不允许混合注册和解析实例。当从容器中解析第一个类型时,容器将被锁定以进行进一步的更改。之后调用其中一个注册方法时,容器将抛出异常。选择这种设计是为了迫使用户严格地将两个阶段分开,并防止各种可能很容易出现的令人讨厌的并发问题。然而,这种锁定也允许性能优化,使Simple Injector成为该领域中速度最快的。
然而,这意味着你有时需要对注册进行一些不同的思考。然而,在大多数情况下,解决方案相当简单。
例如,在您的示例中,只需让ISomeOtherService
实现具有类型为ISettings
的构造函数参数即可解决问题。这将允许设置实例在解析时注入该类型:
container.Register<ISettings, Settings>();
container.Register<ISomeOtherService, SomeOtherService>();
// Example
public class SomeOtherService : ISomeOtherService {
public SomeOtherService(ISettings settings) { ... }
}
另一种解决方案是注册代理人:
container.Register<ISettings, Settings>();
container.Register<ISomeOtherService>(() => new SomeOtherService(
container.GetInstance<ISettings>().Value));
请注意,这里仍然调用container.GetInstance<ISettings>()
,但它嵌入在已注册的Func<ISomeOtherService>
委托中。这将保持注册和解析分离。
另一种选择是首先防止具有大型应用程序Settings
类。我过去曾经历过,这些类往往会经常更改,并可能使代码复杂化,因为许多类都依赖于该类/抽象,但每个类都使用不同的属性。这表明违反了接口隔离原则。
相反,您也可以将配置值直接注入到需要它的类中:
var conString = ConfigurationManager.ConnectionStrings["Billing"].ConnectionString;
container.Register<IConnectionFactory>(() => new SqlConnectionFactory(conString));
在我构建的最后几个应用程序中,我仍然有一些Settings类,但这个类是我的Composition Root内部的,它本身并没有注入,只是它在注入的地方保存的配置值。它看起来是这样的:
string connString = ConfigurationManager.ConnectionStrings["App"].ConnectionString;
var settings = new AppConfigurationSettings(
scopedLifestyle: new WcfOperationLifestyle(),
connectionString: connString,
sidToRoleMapping: CreateSidToRoleMapping(),
projectDirectories: ConfigurationManager.AppSettings.GetOrThrow("ProjectDirs"),
applicationAssemblies:
BuildManager.GetReferencedAssemblies().OfType<Assembly>().ToArray());
var container = new Container();
var connectionFactory = new ConnectionFactory(settings.ConnectionString);
container.RegisterSingle<IConnectionFactory>(connectionFactory);
container.RegisterSingle<ITimeProvider, SystemClockTimeProvider>();
container.Register<IUserContext>(
() => new WcfUserContext(settings.SidToRoleMapping), settings.ScopedLifestyle);
更新
关于您的更新,如果我理解正确,您希望允许注册的类型根据配置值进行更改。一个简单的方法如下:
var settings = new Settings();
container.RegisterSingle<ISettings>(settings);
Type theTypeWeWantToRegister = Type.GetType(settings.GetTheISomeOtherServiceType());
container.Register(typeof(ISomeOtherService), theTypeWeWantToRegister);
但是,请仍然考虑根本不注册Settings
文件。
但也要注意,需要将类型名称放在配置文件中这样大的灵活性是非常罕见的。通常,您唯一需要的是当您有一个动态插件模型时,在该模型中,可以将插件程序集添加到应用程序中,而无需更改应用程序。
然而,在大多数情况下,您有一组固定的实现,这些实现在编译时就已经知道了。例如,在您的验收和暂存环境中使用的假IMailSender
和在生产中使用的真SmptMailSender
。由于这两个实现都包含在编译过程中,因此允许指定完整的完全限定类型名称,只会提供比您需要的更多的选项,这意味着会出现更多的错误。
然而,在这种情况下,您只需要一个布尔开关。类似的东西
<add key="IsProduction" value="true" />
在你的代码中,你可以这样做:
container.Register(typeof(IMailSender),
settings.IsProduction ? typeof(SmtpMailSender) : typeof(FakeMailSender));
这允许此配置具有编译时支持(当名称更改时,配置仍然有效),并使配置文件保持简单。