【问题标题】:Dependency Injection in Net Core with configurable service implementations具有可配置服务实现的 Net Core 中的依赖注入
【发布时间】:2020-11-19 17:16:28
【问题描述】:

我有多个相同服务的实现:

public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService { } 

我希望能够在我的 appsettings.json 中定义 IService 的实现,以便可以在部署时替换服务。像这样的:

{
   "Service": 
   {
       "class":"NameSpaceA",
       "settting":"value",
       "setting2":"value"
   }
{ 

请注意,ServiceA 和 ServiceB 的构造函数可能不同,我希望 DI 框架负责解决依赖关系。

【问题讨论】:

    标签: .net .net-core dependency-injection inversion-of-control ioc-container


    【解决方案1】:

    一个非常简洁的解决方案是编写一个扩展方法,它使用带有命名选项的选项模式。这允许您拥有零个、一个或多个服务实现,其中每个实现都有自己的配置。

    类似这样的:

    public static class DependencyInjectionExtensions
    {
            public static IServiceCollection AddConfiguredServices<TService, TConfig>(this IServiceCollection serviceCollection, string configSectionName, IConfiguration config) where TConfig : class
            {
                var configSections = config.GetSection(configSectionName).GetChildren();
                foreach (var configSection in configSections)
                {
                    string name = configSection["className"] ?? throw new ConfigurationErrorsException($"className is not specified for {typeof(TService).Name} at path \"{configSection.Path}\"");
                    serviceCollection.Configure<TConfig>(name, configSection);
                    Type serviceType = GetType(name);
                    serviceCollection.AddSingleton(typeof(TService), serviceType);
                }
    
                return serviceCollection;
            }
    }
    

    注意:GetType 可以是简单的 Type.GetType 调用,也可以是搜索加载程序集的更复杂的方法。

    这允许你有这样的配置:

    "Services": [
        {
          "className": "NamespaceA.ServiceA",
          "setting1": "...",
          "setting2": "..."
          }
        },
        {
          "className": "NamespaceB.ServiceB",
          "setting1": "...",
          "setting3": "..."
          }
        }
    ]
    

    我们只需要一个 Options 类:

    public class ServiceSetttings
    {
        public const string Section = "Services";
        public string Setting1 { get; set; }
        public string Setting2 { get; set; }
        public string Setting3 { get; set; }
    }
    

    注册成为一个单一的班轮:

    serviceCollection.AddConfiguredServices<IService, ServiceSetttings>(ServiceSetttings.Section, config);
    

    为了完整起见,服务构造函数如下所示:

    public class ServiceA: IService
    {
        private readonly ILogger<ServiceA> logger;
        private readonly SomeOtherDependency dependency;
        private readonly string someConfig;
    
        public ServiceA(SomeOtherDependency dependency, IOptionsMonitor<ServiceSetttings> settings, ILogger<ServiceA> logger)
        {
            ServiceSetttings config = settings.Get(this.GetType().FullName) ?? throw new ConfigurationErrorsException($"Configuration not found for {this.GetType().FullName}");
                
            someConfig = config.Setting1;
            this.dependency= dependency;
            this.logger = logger;
        }
        //Service Code
    }
    

    【讨论】:

      【解决方案2】:

      Startup 类的ConfigureServices 方法中,您应该从配置中读取完全限定的类型名称,将其转换为类型,并将其添加到IServiceCollection。例如:

      IConfigurationSection settings = this.Configuration.GetSection("Service");
      string typeName = settings.GetValue<string>("class");
      Type type = Type.GetType(typeName, throwOnError: true);
      services.AddTransient(typeof(IService), type);
      

      这指示 DI 容器使用后期绑定类型作为 IService 抽象的实现。 DI Container 将使用 Auto-Wiring 来构造类型,这意味着它将在运行时分析其构造函数。 ServiceAServiceB 可以有不同的构造函数签名,但容器仍然可以解析它们。

      【讨论】:

      • 这几乎就是我的选择。但是,我需要具有各自配置的多个实现,因此解决方案有点复杂。有关详细信息,请参阅我自己的答案。
      猜你喜欢
      • 2022-12-20
      • 1970-01-01
      • 1970-01-01
      • 2020-02-05
      • 1970-01-01
      • 2020-06-10
      • 2019-05-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多