Наследование и DI

У меня есть простой класс 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 ?


Ответы (2 шт):

Автор решения: Aziz Umarov

Вопрос: как мне получить в классе 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<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}");
            // send Email message
            
        }
    }

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

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

if (sender instanceof EmailSender)  {

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

}
→ Ссылка
Автор решения: PashaPash

Регистрируете оба типа как реализацию 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. Это явно не его ответственность, заведите для этого отдельный класс, который знает про существование нескольких реализаций, и который будет выбирать, через какие именно отправлять.

→ Ссылка