使用Simple Injector注册多个电子邮件输出服务

时间:2017-03-24 17:39:20

标签: c# dependency-injection simple-injector

我正在使用优秀的Simple Injector Ioc框架,并希望"插入"多个电子邮件输出服务,即Mandrill,MailChimp等 我的问题是我正确地这样做,因为它导致了我的发送方法的演员。

所以我有一个简单的IEmailOutputService

public interface IEmailOutputService
{
   string Identifier { get; }
   bool Send(object message, object contents);
}

MandrillOutputService(缩短)

public class MandrillOutputService : IEmailOutputService
{         
   public MandrillOutputService()
   {
     //DI stuff here
   }

   public string Identifier => "Mandrill"; 
   public bool Send(EmailMessage message, IEnumerable<TemplateContent> templateContents)
   {
     if (message == null)
       return false;

     //send code here      
     return true;
   }

  bool IEmailOutputService.Send(object message, object contents)
  {
    //TODO this doesnt look right!!
    var m = message as EmailMessage;
    var c = contents as IEnumerable<TemplateContent>;
    //forwards method onto bespoke Mandrill Send method above
    return Send(m, c);
  }

}

我有EmailContext获取登录用户的电子邮件输出提供程序,例如&#34; Mandrill&#34;继承了IEmailContext

public interface IEmailContext
{
  string GetProvider();
}

EmailOutputComposite用于选择正确的电子邮件输出服务

public class EmailOutputComposite : IEmailOutputService
{
  private readonly IEmailContext _emailContext;
  private readonly IEnumerable<IEmailOutputService> _emailOutputServices;
  public string Identifier => "EmailOutputComposite";

  public EmailOutputComposite(
    IEmailContext emailContext, IEnumerable<IEmailOutputService> emailOutputServices)
  {
    this._emailContext = emailContext;
    this._emailOutputServices = emailOutputServices;
  }

  bool IEmailOutputService.Send(object message, object contents) =>
    this._emailOutputServices
      .FirstOrDefault(x => x.Identifier.ToLower() == this._emailContext.GetProvider())
      .Send(message, contents);
}

最后在Simple Injector注册

container.RegisterCollection(typeof(IEmailOutputService), new[]
{
    typeof(MandrillOutputService)
    //other emailOutputServices to go here
});
container.Register(typeof(IEmailOutputService), typeof(EmailOutputComposite),
    Lifestyle.Singleton);

所以我的问题是我正确地做到了这一点还是有更好的方法。我必须从数据库中获取用户电子邮件提供程序(Mandrill),因此无法想到另一种方法,但是我关注的是我必须在MandrillOutputService.Send方法中执行的操作。

2 个答案:

答案 0 :(得分:3)

使用策略和工厂模式不是更简单,原谅我,我将稍微改变实现:

对于容器注册:

 container.Register<EmailProviderFactory>(Lifestyle.Scoped);
 container.Register<MandrillOutputService>(Lifestyle.Scoped);
 container.Register<OtherOutputService>(Lifestyle.Scoped);

然后使用工厂解析我的电子邮件提供商:

public class EmailProviderFactory
{
    private readonly Container container;

    public EmailProviderFactory(Container container)
    {
        this.container = container;
    }

    public IEmailOutputService Create(string provider)
    {
        switch (provider)
        {
            case "Mandrill": // should be in a constants class
                return container.GetInstance<MandrillOutputService>();

            case "Other":  // should be in a constants class
                 return container.GetInstance<OtherOutputService>();

            default: throw new ArgumentOutOfRangeException("provider");
        }
    }
}

我已经将IEmailOutputService更改为具有显式类型的一个方法:

public interface IEmailOutputService
{ 
    bool Send(EmailMessage message, IEnumerable<TemplateContent> contents);
}

电子邮件提供商:

public class MandrillOutputService : IEmailOutputService
{
    public bool Send(EmailMessage message, IEnumerable<TemplateContent> templateContents)
    {
        // ...
    }
}

public class OtherOutputService : IEmailOutputService
{
    public bool Send(EmailMessage message, IEnumerable<TemplateContent> templateContents)
    {
        // ...
    }
}

<强>用法:

foreach(var userEmailProvider in UserEmailProviders) {
     // I'm assuming the factory is injected
     var emailService = _emailProviderFactory.Create(userEmailProvider.Name);

     emailService.Send(new EmailMessage(), new List<TemplateContent>());
}

我认为您不需要IEmailContextEmailOutputComposite。通过使用EmailProviderFactory,您只需在需要时创建特定的提供程序。

答案 1 :(得分:1)

我在设计中看到两个问题:

  1. 您通过仅接受MandrillOutputService抽象的可接受类型的子集,违反了IEmailOutputService中的Liskov Substitution Principle;当用户提供对该特定实现无效的值时,这可能导致应用程序在运行时中断。
  2. Identifier上的IEmailOutputService属性违反Interface Segration Principle,因为这是消费者不使用的方法。实际上对此属性感兴趣的唯一类是EmailOutputComposite。从抽象中删除Identifier的优点是它可以简化单元测试,因为消费者可以调用的代码更少。它还简化了界面,这总是一件好事。
  3. 我不确定如何修复LSP原则,因为我不清楚其他实现的外观。

    关于ISP违规,您可以执行以下操作来修复它:

    • 使用定义其Identifier的属性来标记实现。这允许您从界面中删除属性,但缺点是Composite只能在注入实际类型时过滤这些服务(并且不进行修饰,因为这样就不允许您检索这些属性)。
    • 您让Composite依赖于实际的具体实现,并在Composite内部实现switch - case语句。这再次允许您从界面中删除属性,但缺点是每次添加新实现时都必须更新组合(如果考虑Composition Root的复合部分,这可能不会那么糟糕)。
    • 您在注册过程中定义了一个IEmailOutputServices字典,其中Identifier是字典的密钥。这消除了将Identifier作为抽象的一部分的需要,但也从实现中删除了标识符(实际上可能是好的)。

    这是最后一个例子的一个例子:

    container.RegisterSingleton<IEmailOutputService, EmailOutputComposite>();
    container.RegisterSingleton(new Dictionary<string, Func<IEmailOutputService>>()
    {
        "Mandrill", CreateEmailServiceProducer<MandrillOutputService>(container),
        "other", CreateEmailServiceProducer<Other>(container),
        //  ..
    });
    
    privte static Func<IEmailOutputService> CreateEmailServiceProducer<T>(Container c)
        where T : IEmailOutputService =>
        Lifestyle.Transient.CreateProducer<IEmailOutputService, T>(c).GetInstance;
    

    复合材料的实施方式如下:

    public class EmailOutputComposite : IEmailOutputService
    {
        private readonly IEmailContext _emailContext;
        private readonly Dictionary<string, Func<IEmailOutputService>> _emailOutputServices;
    
        public EmailOutputComposite(
            IEmailContext emailContext, 
            Dictionary<string, Func<IEmailOutputService>> emailOutputServices)
        {
            _emailContext = emailContext;
            _emailOutputServices = emailOutputServices;
        }
    
        public bool Send(object m, object c) => Service.Send(m, c);
        IEmailOutputService Service => _emailOutputServices[_emailContext.GetProvider()]();
    }
    

    这是否真的取决于你。