配置设置和 IoC

本文关键字:IoC 设置 配置 | 更新日期: 2023-09-27 18:35:28

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

  1. 直接在此最低层读取,即:

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

它可以工作,但还需要将此密钥添加到单元测试项目的配置文件中。此外,程序集取决于配置文件

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

当最低层的不同实现可能需要不同的参数时,也存在一个问题。 即 SendMail1 可以要求 SMTP/login/password,但 SendMail2 只能要求 ApiKey,但 SendMail1 和 SendMail2 应该实现相同的接口。因此,使用方法#2会产生困难

配置设置和 IoC

您概述的两种方法都不起作用 - 首先(读取服务中的配置)阻止您提到的单元测试,其次(从顶层传递配置)需要了解顶层每个服务的所有可能实现。

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

  • 在注册时传递配置 - 即,如果容器支持注册工厂方法,则此类工厂方法可以读取配置并调用具体服务的特定构造函数

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

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

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


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

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

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

即使你必须在所有层中传递它,你的DI引擎应该在自动链接分辨率方面帮助你。

例如

如果您的控制器需要实例化业务层类(需要实例化存储库类),需要连接类(需要设置值),则无需在 3 个位置手动执行此操作。

您可以在 DI 引擎中分别定义 BL 类、存储库类和连接类的注册,它将负责为您实例化控制器。

它可能看起来很乏味,但从长远来看通常有很大的好处。(在明确的合约定义、单元测试、无反模式、孤立关注点等方面)

如果您真的担心通过它 3 个地方,那么在工厂和聚合服务方面有多种选择。每个都有其优点/缺点,并取决于您使用的 DI 引擎。如果选项2是绝对不可接受的,请告诉我们。

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