Remitente de correo electrónico abstracto con proveedores de contenido

Aug 17 2020

Creé un remitente de correo electrónico con proveedores para el contenido del correo electrónico, que cambiará según el tipo de correo electrónico. Necesito ayuda para mejorarlo.

Estos dos modelos se utilizan 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; }
}

El proveedor de contenido proporciona toda la información del correo electrónico (asunto, cuerpo, Para y CC)

public interface IEmailContentProvider
{
    EmailMessage Message { get; }
}

Luego tenemos el remitente de correo electrónico abstracto IEmailSenderque tiene un método único Sendque usa el IEmailContentProviderparámetro para obtener la información del correo electrónico.

interface IEmailSender
    {
        Task Send(IEmailContentProvider provider);
    }

Tengo un ejemplo para el proveedor de contenido.WelcomEmailProvider

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

La IEmailSenderimplementación:

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

Y aquí está, cómo usarlo y enviar un correo electrónico:

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

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

Respuestas

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

No es necesario, puedo entender si solo inicia el ToAddress, sin embargo, iniciar las listas de esta manera podría consumir mucha memoria (¡imagínese que tiene una gran cantidad de EmailMessageinstancias! Por lo tanto, sugeriría mantenerlas como nulas y usar nullla validación para forzar iniciándolos cuando sea necesario (como en el remitente).

su diseño general es lo suficientemente bueno, sin embargo, puede hacerlo directamente:

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

EmailMessagedebe contener From(obligatorio), To(obligatorio), CC(opcional),BCC(opcional), Asunto y Cuerpo como un modelo completo del mensaje. La razón detrás de esto es que siempre se combinará como requisito para cualquier mensaje, más adelante, también sería más fácil de implementar en el lado de la base de datos. using IEnumerable<EmailAddress>se abriría para usar cualquier colección que implemente IEnumerable. Por lo tanto, no está restringido a List.

Como IEmailProviderproveedor de correo electrónico, también debe contener su propia configuración como requisito. Esto te facilitaría las cosas, tener cada mensaje de correo electrónico con su propia configuración. De esta manera, puede enviar múltiples correos electrónicos de diferentes proveedores sin codificación adicional. IOptionsse trata de forma independiente, pero en realidad, IOptionsno será útil por sí mismo, y solo lo usaría EmailProvider, por lo que si lo agregamos al contrato, siempre se implementará con la interfaz, de esta manera obligaste al proveedor a que siempre tener IOptions_ Además, IOptionsdebería renombrarse a algo así como EmailServerSettingrelacionarlo EmailProviderSettingcon la implementación principal. Ya que estos son ajustes y no opciones. UsarIOptionspara manejar la configuración opcional del correo electrónico y las funciones que se pueden administrar, como enviar como texto o html, deshabilitar/habilitar archivos adjuntos, imágenes, etc.

Además, necesita una clase de software intermedio para resumir las cosas y utiliza SmtpClient. Esto le daría la ventaja de aumentar la flexibilidad de su trabajo actual y envolverlo bajo un mismo techo, además de facilitar las cosas para reutilizar el código (como MimeMessage, TextPartetc.) en lugar de volver a implementarlo en cada nuevo remitente. También le daría la posibilidad de crear una colección para almacenar múltiples proveedores si va tan lejos. además, podrá agregar nuevos proveedores, manejarlos, manejar mensajes y mantener el alcance de su trabajo.

Así es como me imagino el uso final:

Creando un nuevo proveedor:

// 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 
    }

ahora, creando un nuevo mensaje de correo y enviándolo:

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