建立基於MailKit和MimeKit的.NET基礎郵件服務

       郵件服務是通常的系統都會擁有和須要的功能,可是對於.NET項目來講,郵件服務的建立和使用會較爲的麻煩。.NET對於郵件功能提供了System.Net.Mail用於建立郵件服務,該基礎服務提供郵件的基礎操做,而且使用也較爲的簡單。對於真正將該功能使用於項目的人,就會慢慢發現其中的優缺點,甚至有些時候不能忍受其中的問題。在這裏介紹一種微軟用於替代System.Net.Mail的郵件服務組件MailKit和MimeKit,官網地址:http://www.mimekit.net/。GitHub地址:https://github.com/jstedfast/MimeKit。下面就具體的介紹一下。html

  一.MailKit和MimeKit基礎概述:

       MailKit組件的支持的客戶端類型比較多,例如SMTP客戶端、POP3客戶端、IMAP4客戶端。該組件是一個跨平臺的Email組件,該組件支持.NET 4.0,.NET 4.5,Xamarin.Android,Xamarin.iOS,Windows Phone 8.1等等平臺。git

     

      MimeKit提供了一個MIME解析器,組件具有的解析特性靈活、性能高、很好的處理各類各樣的破碎的MIME格式化。MimeKit的性能實際上與GMime至關。github

      該組件在安全性的仍是比較高的,處理安全的方式較多,SASL認證、支持S / MIME v3.二、支持OpenPGP、支持DKIM簽名等等方式。Mailkit組件能夠經過CancellationToken取消對應的操做,CancellationToken傳播應取消操做的通知,一個的CancellationToken使線程,線程池工做項目之間,或取消合做任務的對象。過實例化CancellationTokenSource對象來建立取消令牌,該對象管理從其CancellationTokenSource.Token屬性檢索的取消令牌。而後,將取消令牌傳遞到應該收到取消通知的任意數量的線程,任務或操做。令牌不能用於啓動取消。安全

     MailKit組件支持異步操做,在內部編寫的有關I/O異步操做的類。服務器

  二.建立基礎郵件服務:

           介紹過MailKit和MimeKit組建的基礎信息,接下來就介紹一下如何使用兩個組件的基本功能,在這裏我將基本操做作了一個簡單的封裝,通常的項目能夠直接引用封裝好的類,你們能夠根據實際的狀況對該組件進行擴展。app

          1.郵件發送基礎服務API

    /// <summary>
    /// 郵件服務API
    /// </summary>
    public static class MailServiceApi
    {
        /// <summary>
        /// 發送郵件
        /// </summary>
        /// <param name="mailBodyEntity">郵件基礎信息</param>
        /// <param name="sendServerConfiguration">發件人基礎信息</param>
        public static SendResultEntity SendMail(MailBodyEntity mailBodyEntity,
            SendServerConfigurationEntity sendServerConfiguration)
        {
            if (sendServerConfiguration == null)
            {
                throw new ArgumentNullException();
            }

            if (sendServerConfiguration == null)
            {
                throw new ArgumentNullException();
            }

            var sendResultEntity = new SendResultEntity();

            using (var client = new SmtpClient(new ProtocolLogger(CreateMailLog())))
            {
                client.ServerCertificateValidationCallback = (s, c, h, e) => true;

                Connection(mailBodyEntity, sendServerConfiguration, client, sendResultEntity);

                if (sendResultEntity.ResultStatus == false)
                {
                    return sendResultEntity;
                }

                SmtpClientBaseMessage(client);

                Authenticate(mailBodyEntity, sendServerConfiguration, client, sendResultEntity);

                if (sendResultEntity.ResultStatus == false)
                {
                    return sendResultEntity;
                }

                Send(mailBodyEntity, sendServerConfiguration, client, sendResultEntity);

                if (sendResultEntity.ResultStatus == false)
                {
                    return sendResultEntity;
                }

                client.Disconnect(true);
            }

            return sendResultEntity;
        }


        /// <summary>
        /// 鏈接服務器
        /// </summary>
        /// <param name="mailBodyEntity">郵件內容</param>
        /// <param name="sendServerConfiguration">發送配置</param>
        /// <param name="client">客戶端對象</param>
        /// <param name="sendResultEntity">發送結果</param>
        public static void Connection(MailBodyEntity mailBodyEntity, SendServerConfigurationEntity sendServerConfiguration,
            SmtpClient client, SendResultEntity sendResultEntity)
        {
            try
            {
                client.Connect(sendServerConfiguration.SmtpHost, sendServerConfiguration.SmtpPort);
            }
            catch (SmtpCommandException ex)
            {
                sendResultEntity.ResultInformation = $"嘗試鏈接時出錯:{0}" + ex.Message;
                sendResultEntity.ResultStatus = false;
            }
            catch (SmtpProtocolException ex)
            {
                sendResultEntity.ResultInformation = $"嘗試鏈接時的協議錯誤:{0}" + ex.Message;
                sendResultEntity.ResultStatus = false;
            }
            catch (Exception ex)
            {
                sendResultEntity.ResultInformation = $"服務器鏈接錯誤:{0}" + ex.Message;
                sendResultEntity.ResultStatus = false;
            }
        }

        /// <summary>
        /// 帳戶認證
        /// </summary>
        /// <param name="mailBodyEntity">郵件內容</param>
        /// <param name="sendServerConfiguration">發送配置</param>
        /// <param name="client">客戶端對象</param>
        /// <param name="sendResultEntity">發送結果</param>
        public static void Authenticate(MailBodyEntity mailBodyEntity, SendServerConfigurationEntity sendServerConfiguration,
            SmtpClient client, SendResultEntity sendResultEntity)
        {
            try
            {
                client.Authenticate(sendServerConfiguration.SenderAccount, sendServerConfiguration.SenderPassword);
            }
            catch (AuthenticationException ex)
            {
                sendResultEntity.ResultInformation = $"無效的用戶名或密碼:{0}" + ex.Message;
                sendResultEntity.ResultStatus = false;
            }
            catch (SmtpCommandException ex)
            {
                sendResultEntity.ResultInformation = $"嘗試驗證錯誤:{0}" + ex.Message;
                sendResultEntity.ResultStatus = false;
            }
            catch (SmtpProtocolException ex)
            {
                sendResultEntity.ResultInformation = $"嘗試驗證時的協議錯誤:{0}" + ex.Message;
                sendResultEntity.ResultStatus = false;
            }
            catch (Exception ex)
            {
                sendResultEntity.ResultInformation = $"帳戶認證錯誤:{0}" + ex.Message;
                sendResultEntity.ResultStatus = false;
            }
        }

        /// <summary>
        /// 發送郵件
        /// </summary>
        /// <param name="mailBodyEntity">郵件內容</param>
        /// <param name="sendServerConfiguration">發送配置</param>
        /// <param name="client">客戶端對象</param>
        /// <param name="sendResultEntity">發送結果</param>
        public static void Send(MailBodyEntity mailBodyEntity, SendServerConfigurationEntity sendServerConfiguration,
            SmtpClient client, SendResultEntity sendResultEntity)
        {
            try
            {
                client.Send(MailMessage.AssemblyMailMessage(mailBodyEntity));
            }
            catch (SmtpCommandException ex)
            {
                switch (ex.ErrorCode)
                {
                    case SmtpErrorCode.RecipientNotAccepted:
                        sendResultEntity.ResultInformation = $"收件人未被接受:{ex.Message}";
                        break;
                    case SmtpErrorCode.SenderNotAccepted:
                        sendResultEntity.ResultInformation = $"發件人未被接受:{ex.Message}";
                        break;
                    case SmtpErrorCode.MessageNotAccepted:
                        sendResultEntity.ResultInformation = $"消息未被接受:{ex.Message}";
                        break;
                }
                sendResultEntity.ResultStatus = false;
            }
            catch (SmtpProtocolException ex)
            {
                sendResultEntity.ResultInformation = $"發送消息時的協議錯誤:{ex.Message}";
                sendResultEntity.ResultStatus = false;
            }
            catch (Exception ex)
            {
                sendResultEntity.ResultInformation = $"郵件接收失敗:{ex.Message}";
                sendResultEntity.ResultStatus = false;
            }
        }

        /// <summary>
        /// 獲取SMTP基礎信息
        /// </summary>
        /// <param name="client">客戶端對象</param>
        /// <returns></returns>
        public static MailServerInformation SmtpClientBaseMessage(SmtpClient client)
        {
            var mailServerInformation = new MailServerInformation
            {
                Authentication = client.Capabilities.HasFlag(SmtpCapabilities.Authentication),
                BinaryMime = client.Capabilities.HasFlag(SmtpCapabilities.BinaryMime),
                Dsn = client.Capabilities.HasFlag(SmtpCapabilities.Dsn),
                EightBitMime = client.Capabilities.HasFlag(SmtpCapabilities.EightBitMime),
                Size = client.MaxSize
            };

            return mailServerInformation;
        }

        /// <summary>
        /// 建立郵件日誌文件
        /// </summary>
        /// <returns></returns>
        public static string CreateMailLog()
        {
            var logPath = AppDomain.CurrentDomain.BaseDirectory + "/DocumentLog/" +
                Guid.NewGuid() + ".txt";

            if (File.Exists(logPath)) return logPath;
            var fs = File.Create(logPath);
            fs.Close();
            return logPath;
        }
    }

       2.組裝郵件消息:

    /// <summary>
    /// 郵件信息
    /// </summary>
    public static class MailMessage
    {
        /// <summary>
        /// 組裝郵件文本/附件郵件信息
        /// </summary>
        /// <param name="mailBodyEntity">郵件消息實體</param>
        /// <returns></returns>
        public static MimeMessage AssemblyMailMessage(MailBodyEntity mailBodyEntity)
        {
            if (mailBodyEntity == null)
            {
                throw new ArgumentNullException(nameof(mailBodyEntity));
            }

            var message = new MimeMessage();

            //設置郵件基本信息
            SetMailBaseMessage(message, mailBodyEntity);

            var multipart = new Multipart("mixed");

            //插入文本消息
            if (string.IsNullOrEmpty(mailBodyEntity.MailTextBody) == false)
            {
                var alternative = new MultipartAlternative
                {
                    AssemblyMailTextMessage(mailBodyEntity.MailTextBody, mailBodyEntity.MailBodyType)
                 };

                multipart.Add(alternative);
            }

            //插入附件
            if (mailBodyEntity.MailFilePath != null && File.Exists(mailBodyEntity.MailFilePath) == false)
            {
                var mimePart = AssemblyMailAttachmentMessage(mailBodyEntity.MailFileType, mailBodyEntity.MailFileSubType,
                     mailBodyEntity.MailFilePath);

                multipart.Add(mimePart);
            }

            //組合郵件內容
            message.Body = multipart;

            return message;
        }

        /// <summary>
        /// 設置郵件基礎信息
        /// </summary>
        /// <param name="minMessag"></param>
        /// <param name="mailBodyEntity"></param>
        /// <returns></returns>
        public static MimeMessage SetMailBaseMessage(MimeMessage minMessag, MailBodyEntity mailBodyEntity)
        {
            if (minMessag == null)
            {
                throw new ArgumentNullException();
            }

            if (mailBodyEntity == null)
            {
                throw new ArgumentNullException();
            }

            //插入發件人
            minMessag.From.Add(new MailboxAddress(mailBodyEntity.Sender, mailBodyEntity.SenderAddress));

            //插入收件人
            foreach (var recipients in mailBodyEntity.Recipients)
            {
                minMessag.To.Add(new MailboxAddress(recipients));
            }

            //插入抄送人
            foreach (var cC in mailBodyEntity.Cc)
            {
                minMessag.Cc.Add(new MailboxAddress(cC));
            }

            //插入主題
            minMessag.Subject = mailBodyEntity.Subject;

            return minMessag;
        }

        /// <summary>
        /// 組裝郵件文本信息
        /// </summary>
        /// <param name="mailBody">郵件文本內容</param>
        /// <param name="textPartType">郵件文本類型(plain,html,rtf,xml)</param>
        /// <returns></returns>
        public static TextPart AssemblyMailTextMessage(string mailBody, string textPartType)
        {
            if (string.IsNullOrEmpty(mailBody))
            {
                throw new ArgumentNullException();
            }

            if (string.IsNullOrEmpty(textPartType))
            {
                throw new ArgumentNullException();
            }

            var textBody = new TextPart(textPartType)
            {
                Text = mailBody
            };

            return textBody;
        }

        /// <summary>
        /// 組裝郵件附件信息
        /// </summary>
        /// <param name="fileAttachmentType">附件類型(image,application)</param>
        /// <param name="fileAttachmentSubType">附件子類型 </param>
        /// <param name="fileAttachmentPath">附件路徑</param>
        /// <returns></returns>
        public static MimePart AssemblyMailAttachmentMessage(string fileAttachmentType, string fileAttachmentSubType, string fileAttachmentPath)
        {
            if (string.IsNullOrEmpty(fileAttachmentSubType))
            {
                throw new ArgumentNullException();
            }

            if (string.IsNullOrEmpty(fileAttachmentType))
            {
                throw new ArgumentNullException();
            }

            if (string.IsNullOrEmpty(fileAttachmentPath))
            {
                throw new ArgumentNullException();
            }

            var attachment = new MimePart(fileAttachmentType, fileAttachmentSubType)
            {
                Content = new MimeContent(File.OpenRead(fileAttachmentPath)),
                ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),
                ContentTransferEncoding = ContentEncoding.Base64,
                FileName = Path.GetFileName(fileAttachmentPath)
            };

            return attachment;
        }

    }

       3.郵件基礎服務實體:

    /// <summary>
    /// 郵件內容實體
    /// </summary>
    public class MailBodyEntity
    {
        /// <summary>
        /// 郵件文本內容
        /// </summary>
        public string MailTextBody { get; set; }

        /// <summary>
        /// 郵件內容類型
        /// </summary>
        public string MailBodyType { get; set; }

        /// <summary>
        /// 郵件附件文件類型
        /// </summary>
        public string MailFileType { get; set; }

        /// <summary>
        /// 郵件附件文件子類型
        /// </summary>
        public string MailFileSubType { get; set; }

        /// <summary>
        /// 郵件附件文件路徑
        /// </summary>
        public string MailFilePath { get; set; }

        /// <summary>
        /// 收件人
        /// </summary>
        public List<string> Recipients { get; set; }

        /// <summary>
        /// 抄送
        /// </summary>
        public List<string> Cc { get; set; }

        /// <summary>
        /// 發件人
        /// </summary>
        public string Sender { get; set; }

        /// <summary>
        /// 發件人地址
        /// </summary>
        public string SenderAddress { get; set; }

        /// <summary>
        /// 郵件主題
        /// </summary>
        public string Subject { get; set; }

        /// <summary>
        /// 郵件內容
        /// </summary>
        public string Body { get; set; }
    }

    /// <summary>
    /// 郵件服務器基礎信息
    /// </summary>
    public class MailServerInformation
    {
        /// <summary>
        /// SMTP服務器支持SASL機制類型
        /// </summary>
        public bool Authentication { get; set; }

        /// <summary>
        /// SMTP服務器對消息的大小
        /// </summary>
        public uint Size { get; set; }

        /// <summary>
        /// SMTP服務器支持傳遞狀態通知
        /// </summary>
        public bool Dsn { get; set; }

        /// <summary>
        /// SMTP服務器支持Content-Transfer-Encoding
        /// </summary>
        public bool EightBitMime { get; set; }

        /// <summary>
        /// SMTP服務器支持Content-Transfer-Encoding
        /// </summary>
        public bool BinaryMime { get; set; }

        /// <summary>
        /// SMTP服務器在消息頭中支持UTF-8
        /// </summary>
        public string UTF8 { get; set; }
    }

    /// <summary>
    /// 郵件發送結果
    /// </summary>
    public class SendResultEntity
    {
        /// <summary>
        /// 結果信息
        /// </summary>
        public string ResultInformation { get; set; } = "發送成功!";

        /// <summary>
        /// 結果狀態
        /// </summary>
        public bool ResultStatus { get; set; } = true;
    }

    /// <summary>
    /// 郵件發送服務器配置
    /// </summary>
    public class SendServerConfigurationEntity
    {
        /// <summary>
        /// 郵箱SMTP服務器地址
        /// </summary>
        public string SmtpHost { get; set; }

        /// <summary>
        /// 郵箱SMTP服務器端口
        /// </summary>
        public int SmtpPort { get; set; }

        /// <summary>
        /// 是否啓用IsSsl
        /// </summary>
        public bool IsSsl { get; set; }

        /// <summary>
        /// 郵件編碼
        /// </summary>
        public string MailEncoding { get; set; }

        /// <summary>
        /// 發件人帳號
        /// </summary>
        public string SenderAccount { get; set; }

        /// <summary>
        /// 發件人密碼
        /// </summary>
        public string SenderPassword { get; set; }

    }

     上面提供了藉助MailKit組建建立發送郵件服務,分別是建立郵件服務器鏈接,組裝郵件基礎信息,郵件基礎實體。發送郵件的基礎服務比較的多,下面介紹一下郵件的接收。異步

    /// <summary>
    /// 跟投郵件服務API
    /// </summary>
    public static class ReceiveEmailServiceApi
    {
        /// <summary>
        /// 設置發件人信息
        /// </summary>
        /// <returns></returns>
        public static SendServerConfigurationEntity SetSendMessage()
        {
            var sendServerConfiguration = new SendServerConfigurationEntity
            {
                SmtpHost = ConfigurationManager.AppSettings["SmtpServer"],
                SmtpPort = int.Parse(ConfigurationManager.AppSettings["SmtpPort"]),
                IsSsl = Convert.ToBoolean(ConfigurationManager.AppSettings["IsSsl"]),
                MailEncoding = ConfigurationManager.AppSettings["MailEncoding"],
                SenderAccount = ConfigurationManager.AppSettings["SenderAccount"],
                SenderPassword = ConfigurationManager.AppSettings["SenderPassword"]
            };
            return sendServerConfiguration;
        }


        /// <summary>
        /// 接收郵件
        /// </summary>
        public static void ReceiveEmail()
        {
            var sendServerConfiguration = SetSendMessage();

            if (sendServerConfiguration == null)
            {
                throw new ArgumentNullException();
            }

            using (var client = new ImapClient(new ProtocolLogger(CreateMailLog())))
            {
                client.Connect(sendServerConfiguration.SmtpHost, sendServerConfiguration.SmtpPort,
                    SecureSocketOptions.SslOnConnect);
                client.Authenticate(sendServerConfiguration.SenderAccount, sendServerConfiguration.SenderPassword);
                client.Inbox.Open(FolderAccess.ReadOnly);
                var uids = client.Inbox.Search(SearchQuery.All);
                foreach (var uid in uids)
                {
                    var message = client.Inbox.GetMessage(uid);
                    message.WriteTo($"{uid}.eml");
                }

                client.Disconnect(true);
            }
        }

        /// <summary>
        /// 下載郵件內容
        /// </summary>
        public static void DownloadBodyParts()
        {
            var sendServerConfiguration = SetSendMessage();

            using (var client = new ImapClient())
            {
                client.Connect(sendServerConfiguration.SmtpHost, sendServerConfiguration.SmtpPort,
                    SecureSocketOptions.SslOnConnect);
                client.Authenticate(sendServerConfiguration.SenderAccount, sendServerConfiguration.SenderPassword);
                client.Inbox.Open(FolderAccess.ReadOnly);

                // 搜索Subject標題包含「MimeKit」或「MailKit」的郵件
                var query = SearchQuery.SubjectContains("MimeKit").Or(SearchQuery.SubjectContains("MailKit"));
                var uids = client.Inbox.Search(query);

                // 獲取搜索結果的摘要信息(咱們須要UID和BODYSTRUCTURE每條消息,以便咱們能夠提取文本正文和附件)
                var items = client.Inbox.Fetch(uids, MessageSummaryItems.UniqueId | MessageSummaryItems.BodyStructure);

                foreach (var item in items)
                {
                    // 肯定一個目錄來保存內容
                    var directory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "/MailBody", item.UniqueId.ToString());

                    Directory.CreateDirectory(directory);

                    // IMessageSummary.TextBody是一個便利的屬性,能夠爲咱們找到「文本/純文本」的正文部分
                    var bodyPart = item.TextBody;

                    // 下載'text / plain'正文部分
                    var body = (TextPart) client.Inbox.GetBodyPart(item.UniqueId, bodyPart);

                    // TextPart.Text是一個便利的屬性,它解碼內容並將結果轉換爲咱們的字符串
                    var text = body.Text;

                    File.WriteAllText(Path.Combine(directory, "body.txt"), text);

                    // 如今遍歷全部附件並將其保存到磁盤
                    foreach (var attachment in item.Attachments)
                    {
                        // 像咱們對內容所作的那樣下載附件
                        var entity = client.Inbox.GetBodyPart(item.UniqueId, attachment);

                        // 附件能夠是message / rfc822部件或常規MIME部件
                        var messagePart = entity as MessagePart;
                        if (messagePart != null)
                        {
                            var rfc822 = messagePart;

                            var path = Path.Combine(directory, attachment.PartSpecifier + ".eml");

                            rfc822.Message.WriteTo(path);
                        }
                        else
                        {
                            var part = (MimePart) entity;

                            // 注意:這多是空的,但大多數會指定一個文件名
                            var fileName = part.FileName;

                            var path = Path.Combine(directory, fileName);

                            // decode and save the content to a file
                            using (var stream = File.Create(path))
                                part.Content.DecodeTo(stream);
                        }
                    }
                }

                client.Disconnect(true);
            }
        }

        /// <summary>
        /// 建立郵件日誌文件
        /// </summary>
        /// <returns></returns>
        public static string CreateMailLog()
        {
            var logPath = AppDomain.CurrentDomain.BaseDirectory + "/DocumentLog/" +
                DateTime.Now.ToUniversalTime().ToString(CultureInfo.InvariantCulture) + ".txt";

            if (File.Exists(logPath)) return logPath;
            var fs = File.Create(logPath);
            fs.Close();
            return logPath;

        }
    }

     上面只是簡單的介紹了郵件的接收,若是須要更加深刻的瞭解功能,能夠進一步對組件源碼進行解析,該組件的文檔爲較爲的豐富。性能

  三.組件使用感悟:

            MailKit和MimeKit組件在項目的使用中較爲的便捷,基本包含了全部的基礎郵件服務操做。組件提供的SmtpClient類提供的功能很豐富,例如鏈接郵件服務器,郵件帳戶認證,組裝郵件消息,獲取郵件服務器配置信息等等方法的提供,可讓咱們在項目中快速的獲取郵件服務的全部信息。ui

           使用過郵件功能的項目 都會有困擾,客戶端與郵件服務器的鏈接是否成功,以及郵件是否發送成功狀態沒有辦法很快的獲取,只能根據郵件服務器返回的一場狀態進行判斷。可是MailKit提供對應的方法和異常類,對郵件服務器返回的異常信息進行解析,客戶端能夠根據這些異常類獲取郵件狀態。編碼

           MailKit組件的提供了ProtocolLogger類,該類用於記錄SMTP操做基礎信息,該類做用爲記錄郵件服務日誌。在郵件發送完畢後,須要及時的關閉鏈接,調用Disconnect(true)方法。

相關文章
相關標籤/搜索