Home > Software design >  How to get the correct instance from IServiceProvider
How to get the correct instance from IServiceProvider

Time:10-05

I'm trying to handle a number of subscriptions in and Azure.Messaging.SericeBus.

The Docs suggest that I should register my ServiceBusClient, ServiceBusSender and ServiceBusProcessor for DI.

For the latter, it means I need an instance for each subscription, so I have something like this...

services.AddSingleton(provider =>
{
    var options = provider.GetService<IOptions<WorkerOptions>>()
                          .Value;

    var client = provider.GetService<ServiceBusClient>();

    var subscription = // code to determine the subscription to use

    return client.CreateProcessor(options.Topic, subscription);
}

Now I need to instantiate the processors and that's where I come unstuck. In this example I'm using an IServiceProvider but I think I'm going to have the same problem just using DI and constructor injection.

var processor = MyServiceProvider.GetService<ServiceBusProcessor>()!;

How do I get a specific ServiceBusProcessor?

I thought I should be able to "name" each instance but that doesn't appear to be possible.

What am I missing?

CodePudding user response:

With .NET Core DI, you need to use separate types to discern between the injection targets. One way to do this is to create a dedicated class for each subscription, e.g.

public abstract class ProcessorProvider
{
  private readonly ServiceBusProcessor _proc;

  public ProcessorProvider(ServiceBusProcessor proc)
  {
    _proc = proc;
  }

  public virtual ServiceBusProcessor Processor { get => _proc; }
}
    
public class ProcessorProviderA : ProcessorProvider
{
    public ProcessorProviderA(ServiceBusProcessor proc): base(proc) {}
}
    
public class ProcessorProviderB : ProcessorProvider
{
    public ProcessorProviderB(ServiceBusProcessor proc): base(proc) {}
}

In your classes, you do not inject the processor directly, but rely on the different classes that provide the processor, e.g.

public class ClassThatReliesOnSubscriptionA
{
  private readonly ServiceBusProcessor _proc;

  public ClassThatReliesOnSubscriptionA(ProcessorProviderA procProv)
  {
    _proc = _procProv.Processor;
  }
}

// Same for subscription B

This way, you can add a registration for IProcessorProviderForSubscriptionA and IProcessorProviderForSubscriptionB like this:

services.AddSingleton<ProcessorProviderA>(provider =>
{
    var options = provider.GetService<IOptions<WorkerOptions>>()
                          .Value;
    var client = provider.GetService<ServiceBusClient>();
    var subscription = // Access subscription A
    var proc = client.CreateProcessor(options.Topic, subscription);
    return new ProcessorProviderA(proc);
}

services.AddSingleton<ProcessorProviderB>(provider =>
{
    var options = provider.GetService<IOptions<WorkerOptions>>()
                          .Value;
    var client = provider.GetService<ServiceBusClient>();
    var subscription = // Access subscription B
    var proc = client.CreateProcessor(options.Topic, subscription);
    return new ProcessorProviderB(proc);
}

This way the inversion of control container can discern between the types that are required by the classes (ClassThatReliesOnSubscriptionA in this sample). Please note that above code is a sample that can give you an outline on how to solve the problem. You can optimize the code further, e.g. by moving common steps into ProcessorProvider. In order to improve "mockability" in unit tests, you could also use marker interfaces instead of the classes.

  • Related