Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Register multiple singletons with same interface but different constructor parameters values

I got stuck and need some advice or pointer to a solution.

A web API with ASP.NET Core 3.1

Startup.cs

services.AddSingleton<ITopicClient>(s => new TopicClient({connectionstring},{topic}));

TopicRepository.cs

 public class TopicRepository : ITopicRepository
 {
        private readonly ITopicClient _topicClient1;
       private readonly ITopicClient _topicClient2;

        public TopicRepository (ITopicClient topicClient1, ITopicClient topicClient2)
        {         
            _topicClient1 = topicClient1;
            _topicClient2 = topicClient2;
        }
        public async Task<Response> SendToTopicAsync(string message, string topic)
        {
           if( topic == "topic1")
           await _topicClient1.send(message);
           else if (topic == "topic2")
           await _topicClient2.send(message);
        }
}

TopicClient.cs in a shared library

        public TopicClient(string serviceBusConnectionString, string topicName)
        {
           _topicClient = new TopicClient(_serviceBusConnectionString,topicName);
        }

I need to send message to different topics. I would like to register services with different topic names in startup.cs. I want to reuse topicClient connection.

services.AddSingleton(s => new TopicClient({connectionstring},{topic1}));

services.AddSingleton(s => new TopicClient({connectionstring},{topic2}));

How can I achieve this by registering singleton instances of same type using same interface ?

Thank you in advance!

like image 590
roczstar Avatar asked Sep 06 '25 05:09

roczstar


2 Answers

You could use a client resolver that holds your registered clients with a wrapper around the client. First create a wrapper around your client with a name or enum for how to resolve it. As I'm not a fan of magic strings I decided to go with an enum in the example.

// Wrapper for your TopicClients
public interface ICustomTopicClient
{
    public ITopicClient TopicClient { get; }
    public TopicName TopicName { get; }
}

// Implement the ICustomTopicClient interface
public class CustomTopicClient : ICustomTopicClient
{
    public ITopicClient TopicClient { get; }
    public TopicName TopicName { get; }
    
    public CustomTopicClient(ITopicClient topicClient, TopicName topicName)
    {
        TopicClient = topicClient;
        TopicName = topicName;
    }
}

// Enum for how to resolve the requested TopicClient
public enum TopicName
{
    Topic1 = 0,
    Topic2 = 1
}

// Register all ICustomTopicClients in your container
services.AddSingleton<ICustomTopicClient>(s => new CustomTopicClient(new TopicClient({connectionstring},{topic}), TopicName.Topic1));
services.AddSingleton<ICustomTopicClient>(s => new CustomTopicClient(new TopicClient({connectionstring},{topic2}), TopicName.Topic2));

Then you create a resolver that holds all custom clients. You inject the collection of clients from the container and create a dictionary with a public method to resolve the clients.

public interface IMessageClientResolver
{
    ITopicClient ResolveClient(TopicName name);
}

public class MessageClientResolver : IMessageClientResolver
{
    private readonly Dictionary<TopicName, ITopicClient> topicClients;

    public MessageClientResolver(IEnumerable<ICustomTopicClient> clients)
    {
         topicClients = clients.ToDictionary(k => k.TopicName, v => v.TopicClient);
    }

    public ITopicClient ResolveClient(TopicName name)
    {
        topicClients.TryGetValue(name, out var client);
        if (client is null)
            throw new ArgumentException(nameof(client));

        return client;
    }
}

Register the resolver to the container.

services.AddSingleton<IMessageClientResolver, MessageClientResolver>();

And then use it like this:

public class Foo
{
    private readonly ITopicClient topicClient;
    private readonly ITopicClient topicClient2;

    public Foo(IMessageClientResolver clientResolver)
    {
        topicClient = clientResolver.ResolveClient(TopicName.Topic1);
        topicClient2 = clientResolver.ResolveClient(TopicName.Topic2);
    }
}

You can use the same pattern and extend the resolver with IQueueClients. And add a resolve method to return the IQueueClient by a QueueName enum.

like image 50
Ronnehag Avatar answered Sep 07 '25 19:09

Ronnehag


You can already register multiple instances as the same interface, so when you do:

services.AddSingleton<ITopicClient>(_ => new TopicClient("topic1"));
services.AddSingleton<ITopicClient>(_ => new TopicClient("topic2"));

you already added two instances to the container.

It is just when you resolve interface ITopicClient, you always get the last added instance. For example, if you resolve:

// instance = topic2
var instance = container.GetService<ITopicClient>();

If you need all instances, you should resolve / inject IEnumerable<ITopicClient>.

class TopicRepository
{
    public TopicRepository(IEnumerable<ITopicClient> clients)
    {
        // clients contains topic1 and topic2
    }
}
like image 33
weichch Avatar answered Sep 07 '25 21:09

weichch



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!