如何使用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; }); 

在这种情况下,您可能缺少IDatabaseFactoryIDatabaseManager抽象,您应该执行以下操作:

 container.RegisterSingle(new SqlDatabaseFactory(connectionString));