Зарегистрируйте несколько синглетонов с одним и тем же интерфейсом, но с разными значениями параметров конструктора.

Я застрял и мне нужен совет или указатель на решение.

Веб-API с 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 в общей библиотеке

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

Мне нужно отправить сообщение в разные темы. Я хотел бы зарегистрировать службы с разными именами тем в startup.cs. Я хочу повторно использовать подключение TopicClient.

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

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

Как я могу добиться этого, зарегистрировав одноэлементные экземпляры одного типа с использованием одного и того же интерфейса?

Заранее спасибо!


person roczstar    schedule 19.04.2020    source источник
comment
Вам не нужна служба для каждой темы, вам нужна какая-то фабрика соединений, которая отвечает за управление вашим единственным соединением, затем вы вводите ее в свои службы и вызываете ее, чтобы вернуть соединение для вас.   -  person JohanP    schedule 20.04.2020
comment
Или создайте тип для каждой темы и зарегистрируйте Topic123TopicClient для каждой из них. Подобно строкам подключения к базе данных, ваше приложение, вероятно, использует небольшой фиксированный набор тем.   -  person David Browne - Microsoft    schedule 20.04.2020
comment
Есть ли другой способ, который я мог бы реализовать, потому что в настоящее время мне нужны экземпляры TopicClient только для 5 тем, но в будущем они могут вырасти?   -  person roczstar    schedule 20.04.2020


Ответы (2)


Вы уже можете зарегистрировать несколько экземпляров как один и тот же интерфейс, поэтому, когда вы делаете:

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

вы уже добавили в контейнер два экземпляра.

Просто когда вы разрешаете интерфейс ITopicClient, вы всегда получаете последний добавленный экземпляр. Например, если вы решите:

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

Если вам нужны все экземпляры, вы должны разрешить/внедрить IEnumerable<ITopicClient>.

class TopicRepository
{
    public TopicRepository(IEnumerable<ITopicClient> clients)
    {
        // clients contains topic1 and topic2
    }
}
person weichch    schedule 20.04.2020

Вы можете использовать распознаватель клиентов, который содержит ваших зарегистрированных клиентов с оболочкой вокруг клиента. Сначала создайте оболочку вокруг вашего клиента с именем или перечислением, как его разрешить. Поскольку я не фанат волшебных строк, я решил использовать в этом примере перечисление.

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

Затем вы создаете преобразователь, который содержит всех пользовательских клиентов. Вы вводите коллекцию клиентов из контейнера и создаете словарь с общедоступным методом для разрешения клиентов.

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

Зарегистрируйте преобразователь в контейнере.

services.AddSingleton<IMessageClientResolver, MessageClientResolver>();

А затем используйте его следующим образом:

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

Вы можете использовать тот же шаблон и расширить преобразователь с помощью IQueueClients. И добавьте метод разрешения для возврата IQueueClient по перечислению QueueName.

person Ronnehag    schedule 23.04.2021