最近pm臨時提出了多種郵件驗證操作的需求,因為一時間也沒有找到好的郵件收發組件,也抱著研究ABP的心態,就花了幾小時時間探究了一下ABP中關於Email的處理和操作。其實郵件操作大多大同小異,這次只是希望介紹一下ABP中實現功能的代碼結構而已,以下是具體過程
演示的ABP代碼版本為0.9.0.0,不過後面版本對於這部分的修改較少,所以完全不影響之後版本的移植使用
2. 實現過程
ABP的Mail操作放在了Abp.Net.Mail和Abp.Net.Mail.Smtp中,第一步先讓我們直接看看這個文件夾下類及接口的代碼圖(未經允許不可使用)
1. 代碼圖(重)
根據代碼圖可以發現ABP對於Mail處理主要由三部分組成
- 第一部分是通過繼承SettingProvider的EmailSettingProvider來對Mail相關參數進行設置(其中EmailSettingNames定義相關字符串)
- 第二部分是以IEmailSenderConfiguration接口為基派生出的對SettingProvider設置的郵件參數進行讀取和傳輸的相關操作類
- 第三部分是以IEmailSender接口為基派生出的Mail發送操作相關類
至於Smtp開頭的文件,則是以Smtp形式進行郵件發送的一種實現文件而已,後文也將直接使用該種方式進行處理
2.具體實現
在具體的實現上,我發現ABP本身的Mail相關類已經十分完整,只是在郵件參數的配置上需要采取自定義的實現,所以我直接抽取了ABP的源碼來進行演示
2.1 定義AppSettingNames及AppSettingProvider
AppSettingNames中定義相關的唯一字符串,大家可以認為是Key,而AppSettingProvider中則是將Key對應的郵件參數賦值,供之後的Configuration讀取
郵件功能推薦放在Core模塊中,完成相關的provider後在CoreModule加入
Configuration.Settings.Providers.Add<AppSettingProvider>();
即可生效public static class AppSettings { /// <summary> /// SMTP related email settings. /// </summary> public static class Smtp { /// <summary> /// Abp.Net.Mail.DefaultFromAddress /// </summary> public const string DefaultAddress = "Trucking.Net.Mail.DefaultFromAddress"; /// <summary> /// Abp.Net.Mail.DefaultFromDisplayName /// </summary> public const string DefaultDisplayName = "Trucking.Net.Mail.DefaultFromDisplayName"; /// <summary> /// Abp.Net.Mail.Smtp.Host /// </summary> public const string Host = "Trucking.Net.Mail.Smtp.Host"; /// <summary> /// Abp.Net.Mail.Smtp.Port /// </summary> public const string Port = "Trucking.Net.Mail.Smtp.Port"; /// <summary> /// Abp.Net.Mail.Smtp.UserName /// </summary> public const string UserName = "Trucking.Net.Mail.Smtp.UserName"; /// <summary> /// Abp.Net.Mail.Smtp.Password /// </summary> public const string Password = "Trucking.Net.Mail.Smtp.Password"; /// <summary> /// Abp.Net.Mail.Smtp.Domain /// </summary> public const string Domain = "Trucking.Net.Mail.Smtp.Domain"; /// <summary> /// Abp.Net.Mail.Smtp.EnableSsl /// </summary> public const string EnableSsl = "Trucking.Net.Mail.Smtp.EnableSsl"; /// <summary> /// Abp.Net.Mail.Smtp.UseDefaultCredentials /// </summary> public const string UseDefaultCredentials = "Trucking.Net.Mail.Smtp.UseDefaultCredentials"; } } public class AppSettingProvider : SettingProvider { public override IEnumerable<SettingDefinition> GetSettingDefinitions(SettingDefinitionProviderContext context) { return new[] { new SettingDefinition(AppSettings.Smtp.Host, "smtp.gmail.com", L("SmtpHost"), scopes: SettingScopes.Application | SettingScopes.Tenant), new SettingDefinition(AppSettings.Smtp.Port, "587", L("SmtpPort"), scopes: SettingScopes.Application | SettingScopes.Tenant), new SettingDefinition(AppSettings.Smtp.UserName, "[email protected]", L("Username"), scopes: SettingScopes.Application | SettingScopes.Tenant), new SettingDefinition(AppSettings.Smtp.Password, "mypassword", L("Password"), scopes: SettingScopes.Application | SettingScopes.Tenant), new SettingDefinition(AppSettings.Smtp.Domain, "", L("DomainName"), scopes: SettingScopes.Application | SettingScopes.Tenant), new SettingDefinition(AppSettings.Smtp.EnableSsl, "true", L("UseSSL"), scopes: SettingScopes.Application | SettingScopes.Tenant), new SettingDefinition(AppSettings.Smtp.UseDefaultCredentials, "false", L("UseDefaultCredentials"), scopes: SettingScopes.Application | SettingScopes.Tenant), new SettingDefinition(AppSettings.Smtp.DefaultAddress, "[email protected]", L("DefaultEmailAddress"), scopes: SettingScopes.Application | SettingScopes.Tenant), new SettingDefinition(AppSettings.Smtp.DefaultDisplayName, "CompanyName", L("DefaultDisplayName"), scopes: SettingScopes.Application | SettingScopes.Tenant) }; } private static LocalizableString L(string name) { return new LocalizableString(name, AbpConsts.LocalizationSourceName); } }2.2 EmailSenderConfiguration配置
這個類的作用如上面提到的那樣,主要是讀取自定義的AppSettingProvider中設置的郵件參數值
IUserEmailSenderConfiguration接口略
public class UserEmailSenderConfiguration : TruckingServiceBase, IUserEmailSenderConfiguration, ITransientDependency { /// <summary> /// Gets a setting value by checking. Throws <see cref="AbpException"/> if it's null or empty. /// </summary> /// <param name="name">Name of the setting</param> /// <returns>Value of the setting</returns> protected string GetNotEmptySettingValue(string name) { var value = SettingManager.GetSettingValue(name); if (value.IsNullOrEmpty()) { throw new AbpException(String.Format("Setting value for '{0}' is null or empty!", name)); } return value; } /// <summary> /// SMTP Host name/IP. /// </summary> public string Host { get { return GetNotEmptySettingValue(AppSettings.Smtp.Host); } } /// <summary> /// SMTP Port. /// </summary> public int Port { get { return SettingManager.GetSettingValue<int>(AppSettings.Smtp.Port); } } /// <summary> /// User name to login to SMTP server. /// </summary> public string UserName { get { return GetNotEmptySettingValue(AppSettings.Smtp.UserName); } } /// <summary> /// Password to login to SMTP server. /// </summary> public string Password { get { return GetNotEmptySettingValue(AppSettings.Smtp.Password); } } /// <summary> /// Domain name to login to SMTP server. /// </summary> public string Domain { get { return SettingManager.GetSettingValue(AppSettings.Smtp.Domain); } } /// <summary> /// Is SSL enabled? /// </summary> public bool EnableSsl { get { return SettingManager.GetSettingValue<bool>(AppSettings.Smtp.EnableSsl); } } /// <summary> /// Use default credentials? /// </summary> public bool UseDefaultCredentials { get { return SettingManager.GetSettingValue<bool>(AppSettings.Smtp.UseDefaultCredentials); } } public string DefaultAddress { get { return GetNotEmptySettingValue(AppSettings.Smtp.DefaultAddress); } } public string DefaultDisplayName { get { return SettingManager.GetSettingValue(AppSettings.Smtp.DefaultDisplayName); } } }2.3 SmtpEmailSender實現(Smtp實現郵件發送)
UserSmtpEmailSender類才是真正的對Mail操作類,它通過注入IUserEmailSenderConfiguration接口,讀取相關的Mail參數,如Host,UserName,Password等,然後再調用.NET的Mail發送郵件。
IUserSmtpEmailSender接口略
public class UserSmtpEmailSender : IUserSmtpEmailSender, ITransientDependency { private readonly IUserEmailSenderConfiguration _configuration; public UserSmtpEmailSender(IUserEmailSenderConfiguration configuration) { _configuration = configuration; } public async Task SendAsync(string to, string subject, string body, bool isBodyHtml = true) { await SendAsync(_configuration.DefaultAddress, to, subject, body, isBodyHtml); } public void Send(string to, string subject, string body, bool isBodyHtml = true) { Send(_configuration.DefaultAddress, to, subject, body, isBodyHtml); } public async Task SendAsync(string from, string to, string subject, string body, bool isBodyHtml = true) { await SendAsync(new MailMessage(from, to, subject, body) {IsBodyHtml = isBodyHtml}); } public void Send(string from, string to, string subject, string body, bool isBodyHtml = true) { Send(new MailMessage(from, to, subject, body) {IsBodyHtml = isBodyHtml}); } public async Task SendAsync(MailMessage mail, bool normalize = true) { if (normalize) NormalizeMail(mail); await SendEmailAsync(mail); } public void Send(MailMessage mail, bool normalize = true) { if (normalize) NormalizeMail(mail); SendEmail(mail); } public SmtpClient BuildClient() { var host = _configuration.Host; var port = _configuration.Port; var smtpClient = new SmtpClient(host, port); try { if (_configuration.EnableSsl) smtpClient.EnableSsl = true; if (_configuration.UseDefaultCredentials) { smtpClient.UseDefaultCredentials = true; } else { smtpClient.UseDefaultCredentials = false; var userName = _configuration.UserName; if (!userName.IsNullOrEmpty()) { var password = _configuration.Password; var domain = _configuration.Domain; smtpClient.Credentials = !domain.IsNullOrEmpty() ? new NetworkCredential(userName, password, domain) : new NetworkCredential(userName, password); } } return smtpClient; } catch { smtpClient.Dispose(); throw; } } /// <summary> /// Normalizes given email. /// Fills <see cref="MailMessage.From" /> if it's not filled before. /// Sets encodings to UTF8 if they are not set before. /// </summary> /// <param name="mail">Mail to be normalized</param> protected virtual void NormalizeMail(MailMessage mail) { if ((mail.From == null) || mail.From.Address.IsNullOrEmpty()) mail.From = new MailAddress( _configuration.DefaultAddress, _configuration.DefaultDisplayName, Encoding.UTF8 ); if (mail.HeadersEncoding == null) mail.HeadersEncoding = Encoding.UTF8; if (mail.SubjectEncoding == null) mail.SubjectEncoding = Encoding.UTF8; if (mail.BodyEncoding == null) mail.BodyEncoding = Encoding.UTF8; } protected async Task SendEmailAsync(MailMessage mail) { using (var smtpClient = BuildClient()) { await smtpClient.SendMailAsync(mail); } } protected void SendEmail(MailMessage mail) { using (var smtpClient = BuildClient()) { smtpClient.Send(mail); } } }
之後我們只需要再調用該EmailSender的SendAsync,填入對應的參數,親測有效。如果之後要更換郵件組件,則只需要實現對應的UserLibraryEmailSerder即可。
至此,我們便將ABP中單獨的一個郵件功能抽離了出來並做了相關解釋,其實只要花點功夫,自己手動剝離代碼圖也可以理解了。至於一個簡單的郵件功能為什麼在ABP中要實現得如此復雜,每個程序員有每個程序員的答案,還是繼續學習吧