Абстрагированный отправитель электронной почты с поставщиками контента
Я создал отправителя электронной почты с поставщиками содержимого электронной почты, который будет изменяться в зависимости от типа электронной почты. Мне нужна помощь, чтобы улучшить его.
Эти две модели используются для отправки
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; }
}
Поставщик контента предоставляет всю информацию об электронной почте (тема, текст, Кому и Копия)
public interface IEmailContentProvider
{
EmailMessage Message { get; }
}
Затем у нас есть абстрактный отправитель электронной почты IEmailSender
, у которого есть единственный метод, Send
который использует IEmailContentProvider
параметр для получения информации электронной почты.
interface IEmailSender
{
Task Send(IEmailContentProvider provider);
}
У меня есть пример для контент-провайдера 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} }
};
}
}
IEmailSender
Реализация:
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);
}
}
А вот как им воспользоваться и отправить электронное письмо:
class Sample
{
private readonly IEmailSender _emailSender;
public Samole(IEmailSender emailSender)
{
_emailSender = emailSender;
}
public async Task DoSomethingThenSendEmail()
{
await _emailSender.Send(new WelcomEmailProvider("[email protected]", "Someone"));
}
}
Ответы
public EmailMessage()
{
ToAddresses = new List<EmailAddress>();
CcAddresses = new List<EmailAddress>();
}
Нет необходимости, я могу понять, если вы просто инициируете ToAddress
, однако создание таких списков может потребовать много памяти (представьте, что у вас большое количество EmailMessage
экземпляров! Итак, я бы предложил оставить их как null и использовать null
проверку для принудительного инициируя их, когда это необходимо (например, в отправителе).
ваш общий дизайн достаточно хорош, однако вы можете сделать это напрямую:
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; }
}
EmailMessage
должен содержать From (обязательно), To (обязательно), CC (необязательно), BCC (необязательно), Subject и Body в качестве полной модели сообщения. Причина этого в том, что они всегда будут объединены вместе, как требование к любому сообщению, позже это также будет проще реализовать на стороне базы данных. using IEnumerable<EmailAddress>
откроет для использования любую коллекцию, которая реализует IEnumerable
. Итак, это не ограничивается List
.
Для В IEmailProvider
качестве поставщика услуг электронной почты должен также содержать свои собственные настройки как требование. Это упростит вам задачу, если у каждого сообщения электронной почты будут собственные настройки. Таким образом, вы можете отправлять несколько писем от разных поставщиков без лишнего кодирования. IOptions
обрабатывается независимо, но на самом деле IOptions
не будет полезен сам по себе, и он будет использоваться только кем-либо EmailProvider
, поэтому, если мы добавим его в контракт, он всегда будет реализован с помощью интерфейса, таким образом вы заставили поставщика всегда есть IOptions
. Кроме того, его IOptions
следует переименовать во что-то вроде EmailServerSetting
или EmailProviderSetting
связать с основной реализацией. Поскольку это настройки, а не параметры. Используется IOptions
для обработки дополнительных настроек и функций электронной почты, которыми можно управлять, например, отправлять в виде текста или HTML, отключать / включать вложения, изображения и т. Д.
Кроме того, вам нужен средний класс, чтобы подвести итог и использовать SmtpClient
. Это даст вам преимущество в увеличении гибкости вашей текущей работы и объединении их под одной крышей, а также упрощении повторного использования кода (например MimeMessage
, ... и TextPart
т. Д.) Вместо повторной реализации его для каждого нового отправителя. Это также даст вам возможность создать коллекцию для хранения нескольких поставщиков, если вы зайдете так далеко. Кроме того, вы сможете добавлять новых поставщиков, обрабатывать их, обрабатывать сообщения и сохранять объем своей работы.
Вот как я представляю себе окончательное использование:
Создание нового провайдера:
// 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
}
теперь, создав новое почтовое сообщение и отправив его:
// 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();
}