You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为自定义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);
    }
}

该方案存在开放泛型单例会重复实例化的问题,现提出两个问题:

  1. 能否提供思路完善步骤4的实现,尤其是开放泛型注册的迁移?
  2. 该方案是否不合理,应选择其他应用配置模式?

问题解答

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 09:10:44