配置设置和 IoC

configuration settings and IoC

我使用 IoC (DI) 方法并且通常有参数,这些参数由最低层(数据库层等)从配置设置(即连接字符串、静态值等)中读取。最好的方法是什么?

  1. 直接在这个最底层读取,即:

    string sendGridApiKey = ConfigurationManager.AppSettings["SendGridApiKey"];
    

有效,但还需要将此键添加到单元测试项目的配置文件中。另外,程序集依赖于配置文件

  1. 在最高层(即 web 应用程序)读取它并作为所有层的参数抛出?它会工作,但所有中间层都会获得未使用的参数(因此,它们将取决于未使用的事物)。

当最低层的不同实现可能需要不同的参数时,也会出现问题。 IE。 SendMail1可以要求SMTP/login/password,但是SendMail2可以只要求ApiKey,但是SendMail1和SendMail2应该实现相同的接口。因此,使用方法 #2

会造成困难

选项 1 最初是一个更简单的解决方案,但很快就变得难以测试、需要引用、打破围绕从最高层到最低层流动值的模式等。

推荐的模式是#2,其中最高层将所有依赖项及其值向下发送到较低层。

即使您必须将它向下传递到所有层,您的 DI 引擎也应该在自动链接解析方面帮助您。

例如

如果你的控制器需要实例化一个业务层class,它需要实例化一个存储库class,它需要一个连接class,它需要设置值,你不不需要手动在3个地方做。

您可以在 DI 引擎中分别定义 BL class、Repository class 和 Connection class 的注册,它会负责为您实例化控制器。

它可能看起来很乏味,但通常在漫长的过程中有很大的好处 运行。 (在明确的契约定义、单元测试、无反模式、隔离问题等方面)

如果你真的担心超过 3 个地方,在工厂和聚合服务方面有多种选择。每个都有它们的 pros/cons 并且取决于您使用的 DI 引擎。让我们知道选项 2 是否绝对不能接受。

例如Autofac 允许您将大量构造函数参数包装到单个聚合服务接口中,以便 Autofac 可以为您注入。

您列出的两种方法都效果不佳 - 首先(读取服务中的配置)会阻止您提到的单元测试,其次(从顶层传递配置)需要顶层了解每个服务的所有可能实现.

我喜欢依赖于 DI 容器的配置存储和为每个接口注册的对象类型的知识的方法:

  • 在注册时传递配置——即如果容器支持注册工厂方法,那么工厂方法可以读取配置,而不是调用具体服务的特定构造函数

    // constructor: publc ConcreteServiceX(int setting1, string setting2)...
    container.RegisterFactory<IServiceX>(
        container => return new ConcreteServiceX(42, ReadSetting("X"));
    
  • 将容器中每个服务的配置注册为 class/interface

    // constructor: publc ConcreteServiceX(IConcreteServiceXSettings settings)...
    container.RegisterType<IService,ConcreteServiceX>();
    container.RegisterInstance<IConcreteServiceXSettings>(
         new ConcreteServiceXSettings(42, ReadSetting("X"));
    

这两种方法都将配置系统的知识本地化到一个地方(容器配置),并允许更轻松地对每个服务(不依赖于配置存储类型)以及更高级别的对象(不需要知道任何设置)进行单元测试服务)。


注意:示例使用类 Unity 语法,采用您选择的容器