Building intermediate IServiceProvider for use in custom IConfigurationProvider

226 Views Asked by At

Problem Statement: I have a custom IConfigurationProvider that requires a complex service to function properly. This complex service would, naturally be registered to the applications IServiceProvider. I want to use IServiceCollection/IServiceProvider facilities in conjunction with IConfigurationProvider to avoid manual new of this complex service, and to re-use registration code that would otherwise be written in the normal part of the DI container building portion of the app.

I've found plenty of documentation describing the troubles of needing an IServiceProvider in an IConfigurationProvider. This is the closest thing that felt ok to me, and is the inspiration for this post.

Here's my approach at a high level

  1. Build the configuration up enough to construct the intermediate IServiceProvider
  2. Build the intermediate IServiceProvider
  3. Build the rest of the configuration via custom IConfigurationProvider's that require special services, retrieved via intermediateServiceProvider.GetRequiredService<T>();
  4. Transfer the registrations and, specifically, singleton objects, from the intermediate IServiceCollection/IServiceProvider to the final IServiceCollection/IServiceProvider. This will help avoid re-registering things in step #5 and will help avoid second instances of singletons in the final IServiceProvider.
  5. Register the final set of services to complete the final IServiceProvider, using configuration that was injected in step #4.

#1,#2,#3,#5 are simple enough. #4 is where I'm hitting roadblocks. My first attempt at #4 was the following

    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);
        }
    }

As documented here, registering open-generic types with a factory function is not supported.

After stumbling upon this limitation, my latest approach looks like:

    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);
        }
    }

Obviously, my current implementation is not perfect as it allows for multiple singleton instances if that singleton is registered as an open-generic. But, with an understood limitation of bootstrap registration being non open-generic types, I can "successfully" create an intermediate IServiceProvider for use within IConfigurationProvider's and transfer it to the final IServiceProvider.

  1. Can anyone provide inspiration that can lead to a complete implementation for #4, specifically around transferring open-generic registrations?
  2. Is the assumption that this approach is reasonable total nonsense and I should opt for a different pattern to configure my application?
1

There are 1 best solutions below

1
On

If you use the same configuration provider for intermediate and final service provider and you need the same services in the final service provider as within the intermediate one, why don't you put your whole setup logic of the intermediate provider into a method that gets the target builder as parameter? Then you can first call it to setup your intermediate one and later another time to setup your final provider.

In that case you don't need any kind of reflection and you can use all available extension helper class or own logic to setup the configuration provider.