简体   繁体   English

使用简单注入器注册多个电子邮件输出服务

[英]Register Multiple Email Output Services using Simple Injector

I am using the excellent Simple Injector Ioc framework and would like to "plug in" multiple email output services ie Mandrill, MailChimp etc My question really is am I doing this correctly as it results in a Cast at my send method. 我使用的是出色的Simple Injector Ioc框架,并希望“插入”多个电子邮件输出服务,即Mandrill,MailChimp等。我的问题确实是我是否正确执行了此操作,因为这会导致发送方法转换为Cast。

So I have a simple IEmailOutputService 所以我有一个简单的IEmailOutputService

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

And a MandrillOutputService (shortened) 和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);
  }

} }

I have an EmailContext that gets the Email Output Provider for the logged in User eg "Mandrill" heres the IEmailContext 我有一个EmailContext ,它为登录的用户获取电子邮件输出提供程序,例如“ Mandrill”,这里是IEmailContext

public interface IEmailContext
{
  string GetProvider();
}

EmailOutputComposite is used to select correct Email Output Service 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);
}

and finally registrations in Simple Injector 最后在Simple Injector中注册

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

So my question is am I doing this correctly or is there a better way. 所以我的问题是我是否正确执行此操作,或者有更好的方法? I have to get the Users Email Provider (Mandrill) from Database so cant think of another way to do this but was concerned with the Cast I have to do in MandrillOutputService.Send method. 我必须从数据库中获取用户电子邮件提供者(Mandrill),因此无法想到另一种方法,但我担心必须在MandrillOutputService.Send方法中执行的Cast。

Wouldn't it be simpler to use the Strategy and Factory patterns, forgive me I'm going to change the implementation a bit: 使用策略和工厂模式会不会更简单,请原谅我将对实现进行一些更改:

For container registrations: 对于容器注册:

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

Then use a factory to resolve my email providers: 然后使用工厂来解析我的电子邮件提供商:

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

I've changed the IEmailOutputService to have one method with explicit types: 我将IEmailOutputService更改为具有一种具有显式类型的方法:

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

The email providers : 电子邮件提供商:

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

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

Usage: 用法:

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

I do not think you need IEmailContext or a EmailOutputComposite . 我认为您不需要IEmailContextEmailOutputComposite By using the EmailProviderFactory you will only create a specific provider when you need it. 通过使用EmailProviderFactory您将仅在需要时创建特定的提供程序。

I see two problems in your design: 我在您的设计中看到两个问题:

  1. You are violating the Liskov Substitution Principle in your MandrillOutputService by accepting only a subset of the accepted types of the IEmailOutputService abstraction; 通过仅接受IEmailOutputService抽象的已接受类型的子集,您在MandrillOutputService中违反了Liskov替换原则 this might cause the appliation to break at runtime when the user supplies values that are invalid for that specific implementation. 当用户提供对该特定实现无效的值时,这可能导致应用程序在运行时中断。
  2. The Identifier property on the IEmailOutputService violates the Interface Segration Principle , because it is a method that consumers don't use. IEmailOutputServiceIdentifier属性违反了接口隔离原则 ,因为它是使用者不使用的方法。 The only class that is actually interested in this property is the EmailOutputComposite . 对该属性真正感兴趣的唯一类是EmailOutputComposite Removing the Identifier from the abstraction has the advantage that it can simplify unit testing, since there is less code that a consumer can call. 从抽象中删除Identifier的优点是可以简化单元测试,因为消费者可以调用的代码更少。 It also simplifies the interface, which is always a good thing. 它还简化了界面,这始终是一件好事。

I'm unsure how to fix the LSP principle, because its unclear to me how other implementations look like. 我不确定如何解决LSP原理,因为我不清楚其他实现的外观。

With respect to the ISP violation, you can do the following to fix it: 关于ISP违规,您可以执行以下操作来解决:

  • Mark the implementations instead with an attribute that defines their Identifier . 而是使用定义其Identifier的属性来标记实现。 This allows you to remove the property from the interface, but the downside is that the Composite can only filter those services in case the actual types are injected (and not decorated, because that disallows you from retrieving those attributes). 这允许您从接口中删除该属性,但是缺点是Composite仅在注入实际类型的情况下才可以过滤这些服务(并且不进行修饰,因为这使您无法检索这些属性)。
  • You let the Composite depend on the actual concrete implementations and implement a switch - case statement inside the Composite. 您让Composite依赖于实际的具体实现,并在Composite内部实现switch - case语句。 This again allows you to remove the property from the interface, but downside is that you will have to update the composite every time a new implementation is added (which might not be that bad if you consider the Composite part of your Composition Root ). 这再次允许您从接口中删除该属性,但是缺点是,每次添加新的实现时,您都必须更新Composite(如果考虑到Composition Root的Composite部分,这可能还不错)。
  • You define a dictionary of IEmailOutputServices during the registration process, where the Identifier is the dictionary's key. 您可以在注册过程中定义IEmailOutputServices词典,其中“ Identifier是词典的键。 This removes the need to have the Identifier as part of the abstraction, but also removes the identifier from the implementation (which might actually be something good). 这消除了将Identifier作为抽象的一部分的需要,而且还消除了实现中的标识符(这实际上可能是件好事)。

Here's an example of this last example: 这是最后一个示例的示例:

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;

Where the Composite is implemented as follows: 组合的实现方式如下:

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

Whether or not this is actually an improvement is up to you. 这是否实际上是一种改善取决于您。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM