Search code examples
c#dependency-injectionc#-8.0servicecollection

Multiple Instances of singleton when using BuildServiceProvider


I am trying to create a depenency injection pipeline service that runs as a Background service. The idea is that I can create one to many of these services and pass in simple IOption config for some values that need to change in each step (loaded using the service key), i regiter them a keyed services so they do not overwite each other.

I also have a queue mananger that is created as a singleton that allow me to get producers/consumers base on queue config also passed in a IOptions config.

I pass in a different service key for each version of the pipeline that i want to create

A simple version of my code (cut down to just the basics is below)

public IServiceCollection AddPipelineService(this IServiceCollection services, 
IConfigurationManager configuration, string serviceKey)
{
 // Get Config for the steps based on the sevice key
 var StepOptions = Options.Create(
     configuration
     .GetSection($"{serviceKey}:{StepOptions.StepOptionsName}")
     .Get<MessageFactoryOptions>());

 // Build a list of steps and reister a service 
 services.AddKeyedSingleton<IStep, Step1>(serviceKey, (p,o) => new Step1(p.GetRequiredService<ILogger<ConfirmResponseMessageFactory>>(), stepOptions);
 services.AddKeyedSingleton<IStep, Step2>(serviceKey, (p, o) => new Step2(p.GetRequiredService<ILogger<ConfirmResponseMessageFactory>>(), stepOptions);
 services.AddKeyedSingleton<IStep, Step3>(serviceKey, (p, o) => new Step3(p.GetRequiredService<ILogger<ConfirmResponseMessageFactory>>(), stepOptions);

 // Create the service provider
 var provider = services.BuildServiceProvider(validateScopes: true);

 // Add the steps to a set collection
 IEnumerable<IStep> steps = provider.GetKeyedServices<IMessageProcessItem>(serviceKey);
 services.AddKeyedSingleton<IStepCollection, StepCollection>(serviceKey, (p, o) => new StepCollection(steps));

 // Create the queue options based on the service key
 var queueOptions = Options.Create(
     configuration
     .GetSection($"{serviceKey}:{QueueOptions.QueueOptionsName}")
     .Get<MessageFactoryOptions>());

 // NOTE 
 //  IQueueManager registered earlier as a singleton (i only want to onpen a siingle connection and reuse for creation of consumers and producers)
 //  PipelineBackgroundService is a BackgroundService

 services.AddSingleton<IHostedService, PipelineBackgroundService>(
     p => new PipelineBackgroundService(
         p.GetRequiredService<ILogger<PipelineBackgroundService>>(),
         p.GetRequiredService<IQueueManager>(),
         p.GetRequiredKeyedService<IStepCollection>(serviceKey),
         queueOptions);

 return services;
}

All this works but i end up with multiple versions of queue manager becuase i call BuildServiceProvider which i believe creates a new container. But i have read i should avoid using BuildServiceProvider

Is there a way I can do this without this side affectand with out using BuildServiceProvider.

Thanks, Nick


Solution

  • Based on @eocron comment I was able to create the disired result with ot the need to call BuildServiceProvider, I just changed the code to use the provider in the registing of the StepCollection like so:

    services.AddKeyedSingleton<IStepCollection, StepCollection>(serviceKey, 
        (p, o) => new StepCollection(p.GetKeyedServices<IMessageProcessItem>(serviceKey)));
    

    and now there is only a single instance of the QueueManager created