Mittente e-mail astratto con fornitori di contenuti

Aug 17 2020

Ho creato un mittente e-mail con provider per il contenuto dell'e-mail, che verrà modificato in base al tipo di e-mail. Ho bisogno di un aiuto per migliorarlo.

Questi due modelli sono usati per inviare

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

Il fornitore di contenuti fornisce tutte le informazioni sull'e-mail (oggetto, corpo, A e Cc)

public interface IEmailContentProvider
{
    EmailMessage Message { get; }
}

Quindi abbiamo il mittente dell'e-mail astratto IEmailSenderche ha un unico metodo Sendche utilizza IEmailContentProvideril parametro per ottenere le informazioni sull'e-mail

interface IEmailSender
    {
        Task Send(IEmailContentProvider provider);
    }

Ho un esempio per il fornitore di contenutiWelcomEmailProvider

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

L' IEmailSenderimplementazione:

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

Ed ecco come usarlo e inviare un'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"));
    }
}

Risposte

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

Non necessario, posso capire se avvii semplicemente il ToAddress, tuttavia, l'avvio degli elenchi in questo modo potrebbe consumare molta memoria (immagina di avere una grande quantità di EmailMessageistanze! Quindi, suggerirei di mantenerli come null e utilizzare nullla convalida per forzare avviandoli quando è necessario (come nel mittente).

il tuo design complessivo è abbastanza buono, tuttavia, potresti farlo direttamente:

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

EmailMessagedovrebbe contenere From (obbligatorio), To (obbligatorio), CC (opzionale), BCC (opzionale), Oggetto e Corpo come modello completo del messaggio. Il motivo è che sarà sempre accoppiato come requisito per qualsiasi messaggio, in seguito sarebbe anche più facile da implementare sul lato database. using IEnumerable<EmailAddress>si aprirebbe per utilizzare qualsiasi raccolta che implementa IEnumerable. Quindi, non è limitato a List.

Per IEmailProviderCome provider di posta elettronica dovrebbe anche contenere le proprie impostazioni come requisito. Ciò semplificherebbe le cose per te, avere ogni messaggio di posta elettronica con le proprie impostazioni. In questo modo, puoi inviare più e-mail da diversi provider senza codifica aggiuntiva. IOptionsè trattato in modo indipendente, ma in realtà IOptionsnon sarà utile da solo e verrebbe utilizzato solo da EmailProvider, quindi se lo aggiungiamo al contratto, sarà sempre implementato con l'interfaccia, in questo modo hai costretto il provider a sempre avere IOptions. Inoltre, IOptionsdovrebbe essere rinominato in qualcosa di simile EmailServerSettingo EmailProviderSettingper metterlo in relazione con l'implementazione principale. Poiché si tratta di impostazioni e non di opzioni. UsoIOptionsper gestire le impostazioni e le funzionalità opzionali dell'e-mail che possono essere gestite come l'invio come testo o html, disabilitare/abilitare allegati, immagini ..etc.

Inoltre, hai bisogno di una classe middleware per concludere le cose e usa SmtpClient. Ciò ti darebbe il vantaggio di aumentare la flessibilità del tuo lavoro attuale e avvolgerli sotto lo stesso tetto insieme a facilitare le cose per il riutilizzo del codice (come MimeMessage, TextPart..etc.) invece di reimplementarlo su ogni nuovo mittente. Ti darebbe anche la possibilità di creare una raccolta per archiviare più fornitori se stai andando così lontano. inoltre, sarai in grado di aggiungere nuovi fornitori, gestirli, gestire i messaggi e mantenere l'ambito del tuo lavoro.

Ecco come immagino l'utilizzo finale:

Creazione di un nuovo fornitore:

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

ora, creando un nuovo messaggio di posta e inviandolo:

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