如何使用dependency injection从多个源获取配置?
我正在使用Simple Injector,但也许我需要的更多是一个概念性的答案。
这是交易,假设我有一个界面与我的应用程序设置:
public interface IApplicationSettings { bool EnableLogging { get; } bool CopyLocal { get; } string ServerName { get; } }
然后,通常会有一个实现IApplicationSettings的类,从指定的源获取每个字段,例如:
public class AppConfigSettings : IApplicationSettings { private bool? enableLogging; public bool EnableLogging { get { if (enableLogging == null) { enableLogging = Convert.ToBoolean(ConfigurationManager.AppSettings["EnableLogging"]; } return enableLogging; } } ... }
然而! 假设我想从app.config获取EnableLogging
,从数据库获取CopyLocal
,从另一个获取当前计算机名称的实现获取ServerName
。 我希望能够混合匹配我的应用程序配置,而无需创建9个实现,每个组合一个。
我假设我不能传递任何参数,因为接口由注入器(容器)解析。
我最初想到了这个:
public interface IApplicationSettings where TEnableLogging : IGetValue where TCopyLocal : IGetValue where TServerName : IGetValue { TEnableLogging EnableLog{get;} TCopyLocal CopyLocal{get;} TServerName ServerName{get;} } public class ApplicationSettings { private bool? enableLogging; public bool EnableLogging { get { if (enableLogging == null) { enableLogging = Container.GetInstance().Value } return enableLogging; } } }
但是,有了这个我有一个主要问题:我如何知道如何创建TEnableLogging
的实例(这是一个IGetValue
)? 哦,假设IGetValue
是一个具有Value属性的接口,它将由具体类实现。 但具体类可能需要一些细节(比如app.config中键的名称)或不需要(我可能只想返回总是如此)。
我对dependency injection相对较新,所以也许我的想法是错误的。 有没有人对如何做到这一点有任何想法?
(您可以使用另一个DI库回答,我不介意。我想我只需要抓住它的概念。)
你肯定在这里走错路。
几年前,我构建了一个包含界面的应用程序,就像你的IApplicationSettings
。 我相信我将其命名为IApplicationConfiguration
,但它也包含所有应用程序的配置值。
虽然它帮助我首先使我的应用程序可测试,但经过一段时间后,设计开始受到阻碍。 很多实现都依赖于该接口,但它不断变化,并且随之而来的是实现和测试版本。
就像你我实施了一些延迟加载,但这有一个可怕的下降。 当其中一个配置值丢失时,我只发现它是在第一次调用该值时执行的。 这导致配置难以validation 。
我花了几次重构来重构问题的核心。 大接口是一个问题。 我的IApplicationConfiguration
类违反了接口隔离原则 ,结果是可维护性差。
最后我发现这个界面完全没用。 除了违反ISP之外,这些配置值描述了实现细节,而不是进行应用程序范围的抽象,直接为每个实现提供所需的配置值要好得多。
执行此操作时,最简单的方法是将该配置值作为属性值传递。 使用Simple Injector,您可以使用RegisterInitializer
方法:
var enableLogging = Convert.ToBoolean(ConfigurationManager.AppSettings["EnableLogging"]); container.RegisterInitializer(logger => { logger.EnableLogging = enableLogging; });
执行此操作时, enableLogging
值仅从配置文件中读取一次,并在应用程序启动期间完成。 这使得它很快并且在缺少值时在应用程序启动时失败。
如果由于某种原因你需要延迟读取(例如从数据库),你可以使用Lazy
:
Lazy copyLocal = new Lazy (() => container.GetInstance().RunQuery(CopyLocalQuery)); container.RegisterInitializer(copier => { copier.CopyLocal = copyLocal.Value; });
您也可以使用构造函数参数,而不是使用属性传递值,但这有点难以实现。 看看这篇文章的一些想法。
如果您发现自己为许多注册注册了相同的配置值,则可能缺少抽象。 看看这个:
container.RegisterInitializer(rep => { rep.ConnectionString = connectionString; }); container.RegisterInitializer(rep => { rep.ConnectionString = connectionString; }); container.RegisterInitializer(rep => { rep.ConnectionString = connectionString; }); container.RegisterInitializer(rep => { rep.ConnectionString = connectionString; });
在这种情况下,您可能缺少IDatabaseFactory
或IDatabaseManager
抽象,您应该执行以下操作:
container.RegisterSingle(new SqlDatabaseFactory(connectionString));