天天看點

跨平台的.NET郵件協定MailKit元件解析

   發起的.NET Core開源組織号召,進展的速度是我自己也沒有想到的,很多園友都積極參與(雖然有些人誠心砸場子,要是以我以前的寶脾氣,這會應該被我打住院了吧,不過幸好是少數,做一件事總有人說好,也有人說是用武漢話說“鬧眼子”),.NET社群不是沒有樂于共享知識的人,隻是沒有一個完整和良好的生态環境,總之希望國内的.NET發展越來越強大。我在這裡想到一句話“我們希望自己可以做巨浪,但我們也甘願做巨浪來襲前的小浪”。

   上面扯淡完畢(我這人幹正事前,都要将一些扯淡的話,這個習慣改不掉了...)

   項目中為了及時的通信,有直接發資料到頁面,也有利用短信通知,也有我門今天介紹的郵件元件。我們今天的主要任務就是講解一下有一個.NET的免費開源的郵件元件MailKit。本文将一如既往的結合執行個體群組件底層代碼講解一下相關元件的知識。(項目招人的時候,我都會問一下.NET的底層原理,有一個大神問我這樣有什麼意義嗎?我們也寫不出.NET底層那樣的優秀處理方式,為何取了解這些,其實我個人覺得,問底層的原理,隻是向為了跟好的處理一些程式出現的問題,以及對程式編碼的時候,選擇最合适的方式提升性能,任何一種方式都有優勢和劣勢,.NET的類庫代碼也是如此,如果我們知道.NET的底層實作,我們在項目的需求實作時,可以根據.NET底層實作,選擇合适的方式,以求性能最優)。

一.Mailkit元件概述

   項目中使用Email的操作機會比較多,一般稍微大一點的項目,都會使用到郵件操作這一個操作。對于.NET郵件操作的元件和方式比較多,今天我們就介紹一款郵件操作的元件MailKit,這個郵件元件是一個開源免費的,我們現在就來了解一下這一個元件的特點。MimeKit旨在通過盡可能接近地遵循MIME規範來解決這個問題,同時還為程式員提供了一個非常容易使用的進階API。

   元件的支援的用戶端類型比較多,例如SMTP用戶端、POP3用戶端、IMAP用戶端。該元件是一個跨平台的Email元件,該元件支援.NET 4.0,.NET 4.5,Xamarin.Android,Xamarin.iOS,Windows Phone 8.1等等平台。該元件提供了一個MIME解析器,元件具備的解析特性靈活、性能高、很好的處理各種各樣的破碎的MIME格式化。MimeKit的性能實際上與GMime相當。

   該元件在安全性的還是比較高的,處理安全的方式較多,SASL認證、支援S / MIME v3.2、支援OpenPGP、支援DKIM簽名等等方式。Mailkit元件可以通過CancellationToken取消對應的操作,CancellationToken傳播應取消操作的通知,一個的CancellationToken使線程,線程池工作項目之間,或取消合作任務的對象。過執行個體化CancellationTokenSource對象來建立取消令牌,該對象管理從其CancellationTokenSource.Token屬性檢索的取消令牌。然後,将取消令牌傳遞到應該收到取消通知的任意數量的線程,任務或操作。令牌不能用于啟動取消。

  MailKit元件支援異步操作,在内部編寫的有關I/O異步操作的類。

二.MailKit執行個體:

    上面介紹了MailKit元件的背景和特點,這裡就介紹一下Email元件的簡單應用。

  1.建立郵件方式:

public void SentEmail(string path)
        {
            var message = new MimeMessage();
            //擷取From标頭中的位址清單,添加指定的位址
            message.From.Add(new MailboxAddress("Joey", "[email protected]"));
            //擷取To頭中的位址清單,添加指定的位址
            message.To.Add(new MailboxAddress("Alice", "[email protected]"));
            //擷取或設定消息的主題
            message.Subject = "How you doin?";
            // 建立我們的消息文本,就像以前一樣(除了不設定為message.Body)
            var body = new TextPart("plain")
            {
                Text = @"Hey Alice-- Joey"
            };
            // 為位于路徑的檔案建立圖像附件
            var attachment = new MimePart("image", "gif")
            {
                ContentObject = new ContentObject(File.OpenRead(path), ContentEncoding.Default),
                ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),
                ContentTransferEncoding = ContentEncoding.Base64,
                FileName = Path.GetFileName(path)
            };
            // 現在建立multipart / mixed容器來儲存消息文本和圖像附件
            var multipart = new Multipart("mixed")
            {
                body, attachment
            };
            // 現在将multipart / mixed設定為消息正文 
            message.Body = multipart;
        }      

   調用該元件發送郵件和為郵件添加附件是比較簡單的,第一步是執行個體化MimeMessage對象,對于該對象的解析将在下面進行,得到MimeMessage對象後,指定郵件的位址和主題等等相關資訊。第二步執行個體化TextPart對象,為對象設定文本資訊。若需要問郵件建立檔案的附件,可以使用MimePart對象,包含内容(如消息正文文本或)的葉節點MIME部分一個附件。第四步為建立的郵件主體和文本以及附件資訊後,可以建立Multipart對象,建立郵件容器,用來裝載文本資訊和附件。最後調用MimeMessage.body屬性擷取或設定消息的正文。

    2.郵件資訊的解析:

var message = MimeMessage.Load(stream);      

   郵件的資訊我們需要進行對應的解析,這裡我們使用MimeMessage的Load方法,該方法從指定的流加載MimeKit.MimeMessage。另一個加載資料的方式,可以使用MimeParser類,這裡就不再解析了。

    3.郵件的接收:

public static void HandleMimeEntity(MimeEntity entity)
        {
            //MimeEntity轉化為Multipart實體
            var multipart = entity as Multipart;
            if (multipart != null)
            {
                for (int i = 0; i < multipart.Count; i++)
                    HandleMimeEntity(multipart[i]);
                return;
            }
            var rfc822 = entity as MessagePart;

            if (rfc822 != null)
            {
                var message = rfc822.Message;
                HandleMimeEntity(message.Body);
                return;
            }
            var part = (MimePart)entity;
        }      

   以上是對接收到的消息的一個周遊,采用遞歸周遊MIME結構。MIME是内容的樹結構,很像一個檔案系統。MIME确實定義了一組通用規則,用于郵件用戶端如何解釋MIME部分的樹結構。的 内容處置頭是為了給接收用戶端提供提示以哪些部分是為了顯示作為消息體的一部分,并且意在被解釋為附件。另外兩種方式這離就不做介紹了。

三.MailKit核心對象解析

   上面介紹了Email的基本操作就不做過多的介紹,在使用該元件時,較為的簡單。這裡就來看看該元件的類型結構和一些核心對象。類庫結構有如下圖:

跨平台的.NET郵件協定MailKit元件解析

    1.MimeMessage.Load():

public static MimeMessage Load (ParserOptions options, Stream stream, bool persistent, 
                                CancellationToken cancellationToken = default (CancellationToken))
        {
            if (options == null)
                throw new ArgumentNullException (nameof (options));

            if (stream == null)
                throw new ArgumentNullException (nameof (stream));

            var parser = new MimeParser (options, stream, MimeFormat.Entity, persistent);

            return parser.ParseMessage (cancellationToken);
        }      

     該方法從指定的流加載MimeMessage,具有6個方法重載。該方法傳回一個MimeMessage對象,有源碼可以看出,在該方法内部建立了一個MimeParser對象,MimeParser包含内容(例如郵件正文文本或附件)的葉節點MIME部分。調用ParseMessage方法,解析來自流的消息。

   2.TextPart.Text:

public string Text {
            get {
                if (ContentObject == null)
                    return string.Empty;
                var charset = ContentType.Parameters["charset"];
                using (var memory = new MemoryStream ()) {
                    ContentObject.DecodeTo (memory);
                    var content = memory.ToArray ();
                    Encoding encoding = null;
                    if (charset != null) {
                        try {
                            encoding = CharsetUtils.GetEncoding (charset);
                        } catch (NotSupportedException) {
                        }
                    }
                    if (encoding == null) {
                        try {
                            return CharsetUtils.UTF8.GetString (content, 0, (int) memory.Length);
                        } catch (DecoderFallbackException) {
                            encoding = CharsetUtils.Latin1;
                        }
                    }
                    return encoding.GetString (content, 0, (int) memory.Length);
                }
            }
            set {
                SetText (Encoding.UTF8, value);
            }
        }      

    該屬性擷取解碼的文本内容。該屬性是一個可讀可寫的屬性。ContentType.Parameters["charset"]用于擷取charset參數的值。該方法用來将參數的值設定為資料流并設定對應的編碼。看到這裡的異常處理結構,就想簡單的談幾句,.NET的異常比較的薄弱,很多時候在寫.NET的異常時就更加的簡單,以上是對異常知識捕獲,有些地方并沒有做處理,有些地方是對異常的地方進行恢複。

   3.MimeEntity.WriteTo():

public virtual void WriteTo (FormatOptions options, Stream stream, bool contentOnly, 
                            CancellationToken cancellationToken = default (CancellationToken))
        {
            if (options == null)
                throw new ArgumentNullException (nameof (options));

            if (stream == null)
                throw new ArgumentNullException (nameof (stream));

            if (!contentOnly)
                Headers.WriteTo (options, stream, cancellationToken);
        }      

    該方法将MimeEntity寫入到指定的資料流中,該方法接受參數options格式選項。stream輸出資料流,contentOnly判斷是否可寫。該方法定義為虛方法,在繼承此方法後,可以在子類種對該方法進行重寫。

四.總結

   本人覺得在項目開發中,如果引入了第三方元件,我們盡量引入元件的源碼,這樣我們對整個元件的結構有一個認識,元件的實作方式我們也可以進行細緻了解,尤其是我們在進行調試的事後更加有用,我們可以逐一的進行斷點調試。以上是對該元件的一個簡單介紹,有興趣的可以去深入的了解和學習。

愛知求真,靜心鑽研,虛心學習,務實創新,細緻平和。