2

I am writing applications (Asp.net Web API / Worker Services) that use multiple BackgroundServices which listen to various Azure Service Bus queues and/or topics.

The documentation recommends

The Service Bus objects that interact with the service, such as ServiceBusClient, ServiceBusSender, ServiceBusReceiver, and ServiceBusProcessor, should be registered for dependency injection as singletons (or instantiated once and shared). [...] We recommend that you don't close or dispose these objects after sending or receiving each message.

Fair enough, so I added this to my startup code:

services.AddAzureClients(builder =>
{
    builder.AddServiceBusClient(myConnectionString);
});

But what should I do with the various ServiceBusReceiver instances? Without dependency injection, I would do:

ServiceBusReceiver receiverA = client.CreateReceiver("queue-a");
ServiceBusReceiver receiverB = client.CreateReceiver("queue-b");
ServiceBusReceiver receiverC = client.CreateReceiver("queue-c");

Do I have to create and register a separate singleton wrapper class for each of the ServiceBusReceiver instances? Or is there an easier way similar to named ServiceBusClients?

P.S.: I also have the analogous question for ServiceBusSender instances. In case that is somehow different in nature.

Joerg
  • 790
  • 2
  • 10
  • 23

1 Answers1

0

If you don't want to create wrappers a solution could be to register a delegate in the dependency injection.

You start by defining a delegate that returns a ServiceBusReceiver based on a name :

public delegate ServiceBusReceiver ServiceBusReceiverResolver(string name);

Then you can register an implementation of the delegate that return the receiver related to the name

ServiceBusReceiver receiverA = client.CreateReceiver("queue-a");
ServiceBusReceiver receiverB = client.CreateReceiver("queue-b");
ServiceBusReceiver receiverC = client.CreateReceiver("queue-c");

builder.Services.AddSingleton<ServiceBusReceiverResolver>(name => name switch
{
    "queue-a" => receiverA,
    "queue-b" => receiverB,
    "queue-C" => receiverC,
    _ => throw new InvalidOperationException($"Unknown receiver {name}")
});

You now can inject the delegate into your services and use it to get the receiver you need :

public class MyService
{
    private readonly ServiceBusReceiver receiver;
    public MyService(ServiceBusReceiverResolver resolver)
    {
        receiver = resolver("queue-b");
    }
}
J.Loscos
  • 2,478
  • 1
  • 10
  • 17
  • How would you address disposal in your solution when just using a delegate? Are you just relying on `ServiceBusClient.DisposeAsync()` to do the job? In your example the `ServiceBusReceiver.DisposeAsync()` is neither called explicitly nor implicitly by the dependency injection container. For the analogous sending scenario I came up with a [similar solution](https://stackoverflow.com/a/75615489/3760986) where I use a singleton wrapper class as a registry for created `ServiceBusSender` instances, mainly for the purpose of having a proper lifecycle management via `IAsyncDisposable`. – Joerg Mar 02 '23 at 12:42