Expéditeur d'e-mail abstrait avec fournisseurs de contenu

Aug 17 2020

J'ai créé un expéditeur d'e-mail avec des fournisseurs pour le contenu de l'e-mail, qui sera modifié en fonction du type d'e-mail. J'ai besoin d'aide pour l'améliorer.

Ces deux modèles sont utilisés pour envoyer

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

Le fournisseur de contenu fournit toutes les informations de l'e-mail (sujet, corps, À et Cc)

public interface IEmailContentProvider
{
    EmailMessage Message { get; }
}

Ensuite, nous avons l'expéditeur d'e-mail abstrait IEmailSenderqui a une seule méthode Sendqui utilise un IEmailContentProviderparamètre pour obtenir les informations d'e-mail

interface IEmailSender
    {
        Task Send(IEmailContentProvider provider);
    }

J'ai un exemple pour le fournisseur de contenuWelcomEmailProvider

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 IEmailSendermise en œuvre :

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

Et voici, comment l'utiliser et envoyer un email :

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

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

Réponses

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

Pas nécessaire, je peux comprendre si vous venez de lancer le ToAddress, cependant, lancer les listes comme celle-ci peut consommer beaucoup de mémoire (imaginez que vous avez une grande quantité d' EmailMessageinstances! Donc, je suggérerais de les garder comme nulles et d'utiliser nullla validation pour forcer les initier quand c'est nécessaire (comme dans l'expéditeur).

votre conception globale est assez bonne, cependant, vous pouvez le faire directement :

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

EmailMessagedoit contenir From (obligatoire), To (obligatoire), CC (facultatif), BCC (facultatif), Objet et Corps comme modèle complet du message. La raison derrière cela est qu'il sera toujours associé comme exigence à tout message, plus tard, il serait également plus facile à implémenter du côté de la base de données. using IEnumerable<EmailAddress>s'ouvrirait pour utiliser n'importe quelle collection qui implémente IEnumerable. Donc, ce n'est pas limité à List.

Pour IEmailProviderEn tant que fournisseur de messagerie, il doit également contenir ses propres paramètres comme exigence. Cela faciliterait les choses pour vous, d'avoir chaque message électronique avec ses propres paramètres. De cette façon, vous pouvez envoyer plusieurs e-mails de différents fournisseurs sans codage supplémentaire. IOptionsest traité indépendamment, mais en réalité, IOptionsne sera pas utile en soi, et il ne serait utilisé que par EmailProvider, donc si nous l'ajoutons au contrat, il sera toujours implémenté avec l'interface, de cette façon vous avez forcé le fournisseur à toujours avoir IOptions. En outre, IOptionsdevrait être renommé en quelque chose comme EmailServerSettingou EmailProviderSettingpour le relier à l'implémentation principale. Comme ce sont des paramètres et non des options. UtilisationIOptionspour gérer les paramètres facultatifs de messagerie et les fonctionnalités qui peuvent être gérées comme l'envoi de texte ou html, désactiver/activer les pièces jointes, les images, etc.

En outre, vous avez besoin d'une classe intermédiaire pour conclure et utilise SmtpClient. Cela vous donnerait l'avantage d'augmenter la flexibilité de votre travail actuel et de les regrouper sous un même toit tout en facilitant la réutilisation du code (tel que MimeMessage, TextPart..etc.) au lieu de le réimplémenter sur chaque nouvel expéditeur. Cela vous donnerait également la possibilité de créer une collection pour stocker plusieurs fournisseurs si vous allez aussi loin. De plus, vous pourrez ajouter de nouveaux fournisseurs, les gérer, gérer les messages et conserver la portée de votre travail.

Voici comment j'imagine l'utilisation finale :

Création d'un nouveau fournisseur :

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

maintenant, créer un nouveau message électronique et l'envoyer :

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