一个非常简洁的解决方案是编写一个扩展方法,它使用带有命名选项的选项模式。这允许您拥有零个、一个或多个服务实现,其中每个实现都有自己的配置。
类似这样的:
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
}