Remetente de e-mail abstrato com provedores de conteúdo

Aug 17 2020

Criei um remetente de e-mail com provedores para o conteúdo do e-mail, que será alterado com base no tipo de e-mail. Preciso de ajuda para melhorá-lo.

Estes dois modelos são usados ​​para enviar

public class EmailAddress
{
    public string Name { get; set; }
    public string Address { get; set; }
}

public class EmailMessage
{
    public EmailMessage()
    {
        ToAddresses = new List<EmailAddress>();
        CcAddresses = new List<EmailAddress>();
    }

    public List<EmailAddress> ToAddresses { get; set; }
    public List<EmailAddress> CcAddresses { get; set; }
    public string Subject { get; set; }
    public string Content { get; set; }
}

O provedor de conteúdo fornece todas as informações de e-mail (assunto, corpo, Para e Cc)

public interface IEmailContentProvider
{
    EmailMessage Message { get; }
}

Em seguida, temos o remetente de e-mail abstrato IEmailSenderque possui um único método Sendque usa IEmailContentProviderparâmetros para obter as informações do e-mail

interface IEmailSender
    {
        Task Send(IEmailContentProvider provider);
    }

Eu tenho um exemplo para o provedor de conteúdoWelcomEmailProvider

public class WelcomEmailProvider : IEmailProvider
{
        public EmailMessage Message { get; }

        public WelcomEmailProvider(string address, string name)
        {
            Message = new EmailMessage
        {
            Subject = $"Welcome {name}",
            Content = $"This is welcome email provider!",
            ToAddresses = new List<EmailAddress> { new EmailAddress { Address = address, Name = name} }
        };
    }
}

A IEmailSenderimplementação:

public class EmailSender : IEmailSender
{
    private readonly SmtpOptions _options;

    public EmailSender(IOptions<SmtpOptions> options)
    {
        _options = options.Value;
    }

    public async Task Send(IEmailContentProvider provider)
    {
        var emailMessage = provider.Message;
        var message = new MimeMessage();
        message.From.Add(new MailboxAddress(_options.Sender.Name, _options.Sender.Address));
        message.To.AddRange(emailMessage.ToAddresses.Select(x => new MailboxAddress(x.Name, x.Address)));
        message.Cc.AddRange(emailMessage.CcAddresses.Select(x => new MailboxAddress(x.Name, x.Address)));

        message.Subject = emailMessage.Subject;
        message.Body = new TextPart(TextFormat.Html) { Text = emailMessage.Content };

        using var emailClient = new SmtpClient();
        await emailClient.ConnectAsync(_options.Server, _options.Port, _options.EnableSsl);
        await AuthenticatedData(emailClient);

        await emailClient.SendAsync(message);
        await emailClient.DisconnectAsync(true);

    }

    private async Task AuthenticatedData(SmtpClient smtpClient)
    {
        if (string.IsNullOrWhiteSpace(_options.Username) || string.IsNullOrWhiteSpace(_options.Password))
            return;

        emailClient.AuthenticationMechanisms.Remove("XOAUTH2");
        await emailClient.AuthenticateAsync(_options.Username, _options.Password);
    }
}

E aqui está, como usá-lo e enviar um e-mail:

class Sample
{
    private readonly IEmailSender _emailSender;
    public Samole(IEmailSender emailSender)
    {
        _emailSender = emailSender;
    }

    public async Task DoSomethingThenSendEmail()
    {
        await _emailSender.Send(new WelcomEmailProvider("[email protected]", "Someone"));
    }
}

Respostas

1 iSR5 Sep 01 2020 at 03:40
public EmailMessage()
{
    ToAddresses = new List<EmailAddress>();
    CcAddresses = new List<EmailAddress>();
}

Não é necessário, posso entender se você apenas iniciar o ToAddress, no entanto, iniciar as listas assim pode consumir muita memória (imagine que você tenha uma grande quantidade de EmailMessageinstâncias! Portanto, sugiro mantê-las como nulas e usar nulla validação para forçar iniciando-os quando é necessário (como no remetente).

seu design geral é bom o suficiente, no entanto, você pode fazer isso diretamente:

public class EmailMessage 
{
    public EmailAddress From { get; set; }
    public IEnumerable<EmailAddress> To { get; set; }
    public IEnumerable<EmailAddress> Cc { get; set; }
    public IEnumerable<EmailAddress> Bcc { get; set; }
    public string Subject { get; set; }
    public string Body { get; set; }
}

public interface IEmailProvider
{
    IEmailServerSetting ServerSettings { get; set; }
}

EmailMessagedeve conter From(obrigatório), To(obrigatório), CC(opcional),BCC(opcional), Subject e Body como um modelo completo da mensagem. A razão por trás disso é que sempre será emparelhado como requisito para qualquer mensagem, mais tarde, também seria mais fácil de implementar no lado do banco de dados. using IEnumerable<EmailAddress>abriria para usar qualquer coleção que implemente IEnumerable. Portanto, não é restrito a List.

Para IEmailProviderComo um provedor de e-mail também deve conter suas próprias configurações como requisito. Isso tornaria as coisas mais fáceis para você, para ter cada mensagem de e-mail com suas próprias configurações. Dessa forma, você pode enviar vários e-mails de diferentes provedores sem codificação extra. IOptionsé tratado de forma independente, mas na realidade, IOptionsnão será útil por si só, e seria usado apenas por EmailProvider, então se adicionarmos ao contrato, sempre será implementado com a interface, dessa forma você forçou o provedor a sempre tem IOptions. Além disso, IOptionsdeve ser renomeado para algo parecido EmailServerSettingou EmailProviderSettingrelacionado à implementação principal. Como estas são configurações e não opções. UsarIOptionspara lidar com as configurações e recursos opcionais de e-mail que podem ser gerenciados, como enviar como texto ou html, desativar/ativar anexos, imagens, etc.

Além disso, você precisa de uma classe de middleware para agrupar as coisas e usa SmtpClient. Isso lhe daria a vantagem de aumentar a flexibilidade do seu trabalho atual e colocá-los sob o mesmo teto, além de facilitar a reutilização de código (como MimeMessage, TextPart..etc.) em vez de reimplementá-lo em cada novo remetente. Também lhe daria a capacidade de criar uma coleção para armazenar vários provedores se você for tão longe. além disso, você poderá adicionar novos provedores, manipulá-los, manipular mensagens e manter o escopo do seu trabalho.

Aqui está como eu imagino o uso final:

Criando um novo provedor:

// creating a new email provider 
    public class SomeEmailProvider : IEmailProvider
    {
        // only set the settings internally but it's exposed to be readonly
        public EmailServerSetting ServerSettings { get; private set; }
        
        public SomeEmailProvider()
        {
            //set up the server settings
            ServerSettings = new EmailServerSetting 
            {
                ServerType = EmailServerType.POP, 
                Server = "pop.mail.com",
                Port = 995, 
                Encryption = EmailServerEncryption.SSLOrTLS, 
                EnableSsl = true
            };      
        }
       // some other related code 
    }

agora, criando uma nova mensagem de e-mail e enviando-a:

// can be single object or collection 
var messages = new EmailMessage
{
    From = new EmailAddress("Test", "[email protected]"), 
    To = new EmailAddress [] {
         new EmailAddress("Test1", "[email protected]"), 
         new EmailAddress("Test2", "[email protected]")
        }, 
    Subject = "Testing Subject", 
    Body = "Normal Text Body"           
};

using(var client = new EmailClient(new SomeEmailProvider()))
{
    client.Options = new EmailClientOptions 
    {
        // would send it as plain text 
        EnableHtml = false 
    };
    
    client.Messages.Add(messages); 
    
    client.Send();  
}