为自定义IConfigurationProvider构建中间IServiceProvider
IConfigurationProvider与DI容器整合的问题 我有一个自定义IConfigurationProvider,它依赖已注册到应用IServiceProvider的复杂服务才能运行。我希望结合IServiceCollection/IServiceProvider机制和IConfigurationProvider,避免手动实例化该复杂服务,同时复用DI容器构建阶段的注册代码。
整体方案
- 构建足够配置以创建中间
IServiceProvider - 构建中间
IServiceProvider - 通过需要特殊服务的自定义
IConfigurationProvider构建剩余配置,服务从中间服务提供者获取 - 将中间
IServiceCollection/IServiceProvider的注册信息(尤其是单例对象)迁移到最终服务集合/提供者,避免重复注册和单例多实例 - 使用步骤4注入的配置注册最终服务,完成最终
IServiceProvider
步骤1、2、3、5均易实现,但步骤4遇到阻碍。最初尝试的代码如下:
foreach (var sd in intermediateServiceCollection) { if (sd.Lifetime == ServiceLifetime.Singleton) { // Externally owned if (sd.ImplementationInstance != null) { finalServiceCollection.AddSingleton(sd.ServiceType, sd.ImplementationInstance); } // Provide a factory function to delegate to intermediate service provider else { finalServiceCollection.AddSingleton(sd.ServiceType, s => intermediateServiceProvider.GetRequiredService(sd.ServiceType)); } } // Transient/scoped service descriptors can be forwarded along without issue else { finalServiceCollection.Add(sd); } }
但根据文档,开放泛型类型不支持工厂函数注册,因此调整后的方案如下:
foreach (var sd in intermediateServiceCollection) { if (sd.Lifetime == ServiceLifetime.Singleton) { // Externally owned if (sd.ImplementationInstance != null) { finalServiceCollection.AddSingleton(sd.ServiceType, sd.ImplementationInstance); } // Provide a factory function to delegate to intermediate service provider else if (!sd.ServiceType.IsGenericType) { finalServiceCollection.AddSingleton(sd.ServiceType, s => intermediateServiceProvider.GetRequiredService(sd.ServiceType)); } else { // Simply adding the service descriptor to the final service collection // opens the door for singleton instances to be created again // // In reality, this may be configurable to raise an exception to signal // to our developers they need to avoid registering open-generics in the // bootstrapping portion of the app. But, this may serve it's purpose // if you can live with multiple instances of a singleton. finalServiceCollection.Add(sd); } } // Transient/scoped service descriptors can be forwarded along without issue else { finalServiceCollection.Add(sd); } }
该方案存在开放泛型单例会重复实例化的问题,现提出两个问题:
- 能否提供思路完善步骤4的实现,尤其是开放泛型注册的迁移?
- 该方案是否不合理,应选择其他应用配置模式?
问题解答
1. 完善开放泛型单例迁移的思路
思路一:提前解析开放泛型实例
在中间IServiceProvider中,提前解析所有已注册开放泛型单例的具体实现实例,将这些实例作为已有单例直接注册到最终IServiceCollection,避免重复创建:
// 遍历中间服务提供者中已解析的单例(针对开放泛型的具体实例) foreach (var instance in intermediateServiceProvider.GetServices<object>()) { var serviceType = instance.GetType(); // 检查该实例对应的服务是否是中间集合中的开放泛型单例 var descriptor = intermediateServiceCollection.FirstOrDefault(sd => sd.Lifetime == ServiceLifetime.Singleton && sd.ServiceType.IsGenericTypeDefinition && serviceType.IsAssignableTo(sd.ServiceType.MakeGenericType(serviceType.GetGenericArguments()))); if (descriptor != null) { finalServiceCollection.AddSingleton(serviceType, instance); // 同时注册服务定义类型(如 IGenericService<T>) finalServiceCollection.AddSingleton(descriptor.ServiceType.MakeGenericType(serviceType.GetGenericArguments()), instance); } }
思路二:自定义服务提供者桥接
实现一个桥接IServiceProvider,当最终服务提供者需要解析服务时,优先从中间服务提供者获取单例,仅当中间提供者无对应服务时,才从最终容器创建。这种方式无需迁移服务描述符,直接通过桥接复用单例:
public class BridgedServiceProvider : IServiceProvider { private readonly IServiceProvider _intermediateProvider; private readonly IServiceProvider _finalProvider; private readonly IReadOnlyList<ServiceDescriptor> _intermediateServices; public BridgedServiceProvider(IServiceProvider intermediateProvider, IServiceProvider finalProvider, IReadOnlyList<ServiceDescriptor> intermediateServices) { _intermediateProvider = intermediateProvider; _finalProvider = finalProvider; _intermediateServices = intermediateServices; } public object GetService(Type serviceType) { // 优先从中间提供者获取单例服务 if (IsSingletonInIntermediate(serviceType)) { var service = _intermediateProvider.GetService(serviceType); if (service != null) return service; } return _finalProvider.GetService(serviceType); } private bool IsSingletonInIntermediate(Type serviceType) { return _intermediateServices.Any(sd => sd.ServiceType == serviceType && sd.Lifetime == ServiceLifetime.Singleton); } }
使用时,先构建最终IServiceProvider,再用桥接提供者包装,后续应用直接使用桥接提供者即可。
2. 方案合理性与替代模式
你的分阶段构建配置与DI容器的模式,在配置依赖复杂DI服务的场景下是合理的,但确实存在复杂度较高的问题。如果场景允许,推荐以下替代模式:
模式一:将配置逻辑转为服务依赖
把原本由IConfigurationProvider提供的配置值,改为由一个业务服务类(如IConfigResolver)提供,该服务直接依赖所需的复杂服务,在运行时计算配置值。这种方式完全规避了配置依赖DI的反向依赖问题,更符合常规DI设计。
模式二:简化中间DI容器
仅在中间容器中注册自定义IConfigurationProvider必需的最小服务集合,避免注册开放泛型单例。中间容器仅负责支撑配置构建,后续最终容器重新注册所有业务服务(除了配置相关的单例),减少迁移环节的复杂度。
模式三:使用配置后处理回调
在ASP.NET Core中,可利用PostConfigure或Configure回调,在DI容器构建完成后再初始化需要复杂服务的配置部分。不过这种方式仅适用于配置的后处理,无法替代自定义IConfigurationProvider的场景。
内容的提问来源于stack exchange,提问作者Clay Brooks

