1

У меня есть простой класс Sender

public class Sender : ISender
    {
        private readonly FinMonitoringOptions options;
        private readonly ILogger logger;
    public Sender(IOptionsSnapshot<FinMonitoringOptions> _options, ILoggerFactory loggerFactory)
    {
        options = _options?.Value ?? throw new ArgumentNullException("_options");
        logger = loggerFactory.CreateLogger<Sender>() ?? throw new ArgumentNullException("loggerFactory");
    }

    public async Task SendNotify(AlertCorp alert)
    {
        logger.LogInformation($"Sender.SendNotify IncidentId : {alert.IncidentId}");

    }


}

теперь я хочу создать 2 класса-наследника EmailSender и ESBSender от этого же интерфейса ISender

public interface ISender
    {
        public Task SendNotify(AlertCorp alert);
    }

я использую DI от Microsoft и развязываю зависимости в Startup

.AddTransient<ISender, Sender>()

Вопрос: как мне получить в классе Sender потомков EmailSender и EsbSender через DI и вызвать их методы SendNotify ?

Hel
  • 21
  • 1

2 Answers2

4

Регистрируете оба типа как реализацию ISender:

services.AddTransient<ISender, EsbSender>();
services.AddTransient<ISender, EmailSender>();

В классе, который отправляет нотификации, добавляете зависимость от IEnumerable<ISender> senders.

public HomeController(ILogger<HomeController> logger, IEnumerable<ISender> senders)
{
    _logger = logger;
    _senders = senders;
}

В качестве значения senders от DI придет набор экземпляров всех зарегистрированных ISender.

Только не пытайтесь запихнуть управление отправкой через несколько ISender в базовый тип Sender. Это явно не его ответственность, заведите для этого отдельный класс, который знает про существование нескольких реализаций, и который будет выбирать, через какие именно отправлять.

  • очередность в IEnumerable соответствует порядку вызовов AddTransient? Или порядок не гарантируется? – Grundy Jan 12 '21 at 12:36
  • @Grundy порядок гарантируется: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0#service-registration-methods –  Jan 12 '21 at 13:26
0

Вопрос: как мне получить в классе Sender потомков EmailSender и EsbSender через DI и вызвать их методы SendNotify ? - обычно это так не делается. Интерфейс вызывает свой метод. Который реализован в классе.

Согласно SOLID - а конкретно D - dependency inversion principle

Формулировка:

  • Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
  • Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

В вашем случае

public class EmailSender : ISender
    {
        private readonly FinMonitoringOptions options;
        private readonly ILogger logger;
    public Sender(IOptionsSnapshot&lt;FinMonitoringOptions&gt; _options, ILoggerFactory loggerFactory)
    {
        options = _options?.Value ?? throw new ArgumentNullException(&quot;_options&quot;);
        logger = loggerFactory.CreateLogger&lt;Sender&gt;() ?? throw new ArgumentNullException(&quot;loggerFactory&quot;);
    }

    public async Task SendNotify(AlertCorp alert)
    {
        logger.LogInformation($&quot;Sender.SendNotify IncidentId : {alert.IncidentId}&quot;);
        // send Email message

    }
}

имея на руках только ISender. Вы должны вызвать метод sender.SendNotify(...) и не думать кто там на конце провода, который класс будет отсылать сообщение. В этом и есть основная идея интерфейсов.

А так можно в коде можно проверить что указатели совместимы и преобразовать

if (sender instanceof EmailSender)  {

EmailSender emailSender = (EmailSender)sender; ... // либо вариант. Кому как удобно // EmailSender emailSender = sender AS EmailSender; ... emailSender."call EmailSender method"()

}

Aziz Umarov
  • 22,567
  • 2
  • 10
  • 33
  • Не понятно, как получить в классе Sender объект EmailSender? Где прописать зависимости, что б Di создавал? Если создать отдельный интерфейс IEmailSender, то можно запросить через services.AddTransient, но поскольку в этом случае интерфейс IEmailSender идентичен ISender, как обойтись одним интерфейсрм? – Hel Jan 12 '21 at 07:35
  • Обновил ответ для ясности – Aziz Umarov Jan 12 '21 at 07:36
  • "Как обойтись одним интерфейсом": это разные интерфейсы, одним не обойтись. И поэтому вопрос не имеет отношения к DI, он только из-за использования DI проявился явно - а до этого вы делали неправильно и не замечали проблемы. – A K Jan 12 '21 at 07:52
  • Смысл в том, что сам Sender должен вызвать метод EmailSender.SendNotify и ESBSender.SendNotify. Но для этого класс Sender должен как то получить экземпляры этих объектов? Как? Можно, конечно, просто использовать шаблон цепочка обязанностей или еще что то, можно в самом классе Sender создать эти экземпляры, но это плохо, т.к. нужно использовать DI? А как указать одному интерфейсу несколько реализаций в Startup, если при множественном указании сопоставится последний? – Hel Jan 12 '21 at 07:54
  • 1
    @Hel, как указать одному интерфейсу несколько реализаций в Startup - никак – Grundy Jan 12 '21 at 07:55