MailMergeLib - A .NET Mail Client Library

原文地址:http://www.codeproject.com/KB/IP/MailMergeLib.aspx

Introduction

During the time when I worked on .NET 1.1, I started to use the mail client DotNetOpenMail by Mike Bridge which really was doing a good job. Based on this, I built a library for doing basic mail merge in a Web service.

After moving to .NET 2.0, I thought that DotNetOpenMail had become obsolete because Microsoft had introduced their System.Net.Mail library. Today I still think that this is well designed code, but it has some annoying bugs and RFC violations - which I found out one after another, and which have not come to an end yet. Still the mail merge library grew and became quite comfortable to use.

簡介

在我使用 .NET 1.1 進行工作的期間,我開始使用Mike Bridge的郵件客戶端 DotNetOpenMail真的做了很好的工作。基於這個,我在一個Web service裏建立了一個基本的郵件合併庫。

 

在進入.NET 2.0後,我認爲那個 DotNetOpenMail 已經成爲了一個陳舊的東西,因爲微軟已經引入了他們的 System.Net.Mail庫。今天我仍然認爲那(System.Net.Mail)是很好的設計代碼,但是它有一些討厭的Bug並且有違於RFC規則 接連地被我發現出來,並且它至今不能被解決。所以這個郵件合併庫漸漸改進並且變得使用相當舒適。

 

Background

With System.Net.Mail, I did not encounter any show stoppers. It will send messages.

The point is: some bugs and RFC violations will increase the spam rating of spam filters for your message, and some mail clients may show parts as garbage.

While working on System.Net.Mail, I found and fixed the following bugs:

  1. MailMessage.MailAddressCollection.Clear does not clear the corresponding headers. If you want to re-use an existing MailMessage object for different recipients, you will have to remove the recipients headers yourself (e.g. MailMessage.Headers.Remove("to")). - fixed in .NET 2.0 SP1
  2. Display names of mail addresses that are not all 7bit characters must be encoded. This works fine with every address type except for to addresses. to addresses will never be encoded, no matter which Encoding parameter is used for a new MailAddress. - fixed in .NET 2.0 SP1, except that spaces are still not encoded.
  3. MailMessage.To.ToString() returns the encoded string only after the message was sent. According to the documentation, this should be the case no matter whether the message was already sent or not.
  4. With MailMessage.Headers there is a bug where headers will have white space in an encoded text. This will lead to non-RFC 2047 compliant messages, which will increase the SPAM rating of the message.
  5. Attachments to a mail message must not have white space in the file name, which is neglected by System.Net.Mail.
  6. Setting the transfer encoding to TransferEncoding.SevenBit turns into a header text sevenbit, instead of 7bit. sevenbit is not RFC compliant and causes problems with some mail clients. - fixed in .NET 2.0 SP1
  7. Quoted-Printable encoding is not limited to a maximum of 76 characters in System.Net.Mail. RFC 2045 requires that Quoted-Printable encoding encodes lines be no more than 76 characters long. If longer lines are to be encoded with the Quoted-Printable encoding, "soft" line breaks must be used.

Although Microsoft has supplied some bug fixes, quite a few are not fixed up to .NET 3.5 SP1 (which includes .NET 2.0 SP1). So I tried to find ways to fix them on my own. All bugfixes are included in a static class Bugfixer which hopefully won't be needed in future versions of .NET. Pie in the Microsoft sky?

背景

System.Net.Mail,我沒有遇到任何的阻礙,它將發送信息。

 

要點是:對於你的信息,一些BugRFC違規將會增加垃圾郵件過濾器的垃圾郵件等級,並且一些郵件客戶端可能會顯示出垃圾的部分。

 

當在System.Net.Mail上工作的時候,我發現和修改了以下的bug

 

1.       MailMessage.MailAddressCollection.Clear方法沒有清空相應的Header。如果你希望對不同的收件人重新使用一個現有的MailMessage對象,你不得不自己移除這個收件人的Header(e.g. MailMessage.Headers.Remove("to"))-.Net 2.0 SP1裏被改正。

2.       郵件地址的顯示名稱一定沒有被完全地編碼成 7bit。這對除了 To 地址外的每個地址起了好的作用。To 地址將永遠不會被編碼,不論你對一個新的郵件地址使用了哪一種的編碼參數。- .Net 2.0SP1裏被修正,只可惜空隔仍然沒有被編碼。

3.       MailMessae.To.ToString()方法只有在信息被髮送出去後纔會返回編碼後的字符串,依照文檔,這應該成爲一個案例無論這個信息是否已經被髮送與否。

4.       關於MailMessage.Headers,那兒有一個bug,在那裏,在一個編碼後的文本里會有一段白色的空隔,這將會導致 non-RFC 2047順從信息,那將會增加了垃圾郵件等級。

5.       郵件附件的文件名裏,一定不能有空隔,但這個被System.Net.Mail忽視了。

6.       設置一個文件編碼爲TransferEncoding.SevenBit,卻變成了一個Header 文本sevenbit,而不是7bitSevenbit不是一個RFC依據並且會在一些郵件客戶端裏惹起問題。- .Net 2.0 SP1裏被修正。

7.       System.Net.Mail裏,Quoted-Printable 編碼沒有被限制於76字符的最大值。RFC2045裏需要Quoted-Printable 編碼行不超過 76字符長。如果長的行被使用Quoted-Printable 編碼來編碼,”Soft” lines被截斷。(這裏soft lines不知道怎麼譯)

 

儘管微軟已經提供了一些bug的修復,但相當多的bug.net 3.5 sp1(包括了.net 2.0 sp1)裏還是沒有被修正。所以我盡力去找一些方法來自主修正它們。所有的bug修正都被包括在一個靜態類 Bugfixer 裏,它將有望不需要今後的.net版本。微軟的天空大餅?

 

Using the Code

For sending a mail in System.Net.Mail, you'll first create a MailMessage, and then send it with SmtpClient. In MailMergeLib this is quite similar: you'll create a MailMergeMessage and then send it with MailMergeSender.

使用代碼

爲了在 System.Net.Mail裏發送一個電子郵件,你將首先創建一個MailMessage,然後使用SmtpClient來發送它。在MailMergeLib裏這相當的類似:你將創建一個MailMergeMessage,然後使用MailMergeSender發送它。

 

MailMergeMessage

Create a New Message

One big advantage of MailMergeLib comes from placeholders. {Placeholders} are the field names of a DataTable embedded in any text with curly braces.

So first create the message and adjust some settings. CultureInfo is relevant for formatting placeholders that contain dates, currency or numeric data.

創建一個新的Message

MailMergeLib的一大優勢是佔位符。{Placeholders}是一個嵌入到任意文本上的一個DataTable字段名稱。

那麼首先創建一個信息並且調整一些設置。CultureInfo 是相應的爲格式化包含日期,currency或者數字數據的佔位符。

// create the mail message

MailMergeMessage mmm = new MailMergeMessage("My subject for {Nickname}");

 

// adjust mail specific settings

mmm.CharacterEncoding = Encoding.GetEncoding("iso-8859-1");

mmm.CultureInfo = new System.Globalization.CultureInfo("en-US");

mmm.TextTransferEncoding = System.Net.Mime.TransferEncoding.SevenBit;

mmm.BinaryTransferEncoding = System.Net.Mime.TransferEncoding.Base64;

 

Formatting Capabilities

It is possible to add standard .NET formatting attributes to your placeholders. For a date that will show the day number and the month's name, you would write: {Date:"{0:dd MMMM}"}.

In case a column of DataRow will have ExtendedProperties for null and/or format, these properties will be used. Examples:

myDataTable.Columns["Date"].ExtendedProperties.Add("format", "{0:F}");

myDataTable.Columns["Date"].ExtendedProperties.Add("null", "#Display for null#");

 

Of course column types and formatting attributes must fit each other.

 

格式化容器

它可能去添加標準的.NET格式化屬性到你的佔位符。一個日期將會顯示這個日期數量和這個月的名稱,你將會寫:{Date:”{0:dd MMMM}”}

 

萬一DataRow的一列將有擴展屬性nullformat,這些屬性將會被使用。例如:

 

當然列類型和格式化屬性必須要相互適合。

 

Message Body

The message body parts can be added or changed quite easily either by using them as a parameter in the constructor, or by setting the properties:

mmm.HtmlText = new System.IO.StreamReader("HtmlBody.html").ReadToEnd();
mmm.PlainText = new System.IO.StreamReader("TextBody.html").ReadToEnd();

HtmlText and PlainText can contain placeholders. You can insert a text file by using the special syntax {IncludeFile:"file"}. IncludeFile is the column name of the DataTable, while file means to interpret the content as a file name.

信息主體

這個信息主體部分能夠被相當容易地添加,通過把它們在構造器裏當作一個參數來使用,或者設置屬性:

 

HtmlText PlainText 能夠包含佔位符。你可以通過使用特定的語法{IncludeFile:”File”}來插入一個文件文件。IncludeFile DataTable的一個列名,file意味着解釋文件名內容。

 

Attachments

You may also want to add some personalized attachments by adding placeholders to the file name. E.g.:

mmm.FileAttachments.Add
    (new FileAttachment("testmail_{Nickname}.pdf", "sample.pdf", "application/pdf"));

And that's the way to add string attachments:

mmm.StringAttachments.Add(new StringAttachment
    ("Some programmatically created content", "file.txt", "text/plain"));

 

附件

 

你也可能希望通過文件名稱佔位符來添加一些個性化的附件。

 

並且那是添加字符串附件的方法:

 

Mail Addresses

For sending a mail, we need supply at least one recipient's address and the sender's address. Again, using placeholders makes it possible to create personalized e-mails.

// add to and from addresses
mmm.MailMergeAddresses.Add
    (new MailMergeAddress(MailAddressType.To, "<{Email}>", "{Nickname}"));
mmm.MailMergeAddresses.Add
    (new MailMergeAddress(MailAddressType.From, "<[email protected]>", "From Name"));

 

郵件地址

 

爲了發送一封電子郵件,我們至少需要提供一個收件人地址和一個發件地址。再次,利用佔位符使它能夠創建個性化的電子郵件。

 

Miscellaneous

If your data comes from a DataTable, it may well happen that you have recipients with empty e-mail fields. That's why you may want to set:

mmm.IgnoreEmptyRecipientAddr = true

This will then not throw an exception with empty addresses.

Want to change MailMergeLib's identification? Set...

mmm.Xmailer = "MailMergeLib 2.0"; 

... to anything you like.

雜項

 

如果你的數據是從一個DataTable來的,你的收件人可能會是空的電子郵件字段。那你可能希望設置:

 

這將不會因爲空地址而拋出一個異常。

 

希望改變MailMergeLib的標識?設置

 

任何你想要的。

 

MailMergeSender

In the beginning, MailMergeSender is much like System.Net.Mail.SmtpClient: Create instance of the class and provide some SMTP related settings.

SMTP Settings

Setup the mail sender:

MailMergeSender mailSender = new MailMergeSender();
mailSender.MessageOutput = MessageOutput.SmtpServer;

Set up SMTP server login details:

mailSender.SmtpHost = "smtp.server.com";
mailSender.SmtpPort = 25;
mailSender.SetSmtpAuthentification("username", "password");
mailSender.LocalHostName = "my.localhostname.com"; // used in SMTP Hello command

 

MailMergeSender

首先,MailMergeSender非常像System.Net.Mail.SmtpClient:創建一個類的實例和提供一些SMTP的相關設置。

 

SMTP設置

 

設置Mail sender:

 

設置SMTP服務器登錄信息:

 

EventHandlers

Now here come some nice features that SmtpClient does not have. Add custom event handlers for OnBeforeSend, OnAfterSend, OnSendFailure, OnMergeBegin, OnMergeComplete, and OnMergeProgress:

mailSender.OnAfterSend += new EventHandler<MailSenderAfterSendEventArgs>(
         delegate(object obj, MailSenderAfterSendEventArgs args)
         {
                // do something useful here, like updating a progress bar
         });

 

EventHandlers

 

現在這裏來了一些SmtpClient所沒有的很好的特色。爲OnBeforeSend, OnAfterSend, OnSendFailure, OnMergeBegin, OnMergeComplete, OnMergeProgress添加自定義的Event handlers

 

Sending a Message

For the send job, there are two alternatives:

  1. Start to send messages as an asynchronous operation, which will not block the calling thread:
mailSender.SendAsync(mmm, myDataTable);
  1. Start to send messages as a synchronous operation. In this case, you will loop through the rows of your DataTable, supply the row as variables to the MailMergeMessage and then call Send for each row.
foreach (DataRow dr in myDataTable.Rows)
{
   mmm.Variables = dr;
   mailSender.Send(mmm);
} 

 

發送一個信息

 

對於一個發送任務,這裏有兩種選擇:

1.       當作一個異步操作來發送一個信息,這將不會被調用的線程所阻礙。

mailSender.SendAsync(mmm, myDataTable);

2.       使用一個同步操作來發送一個信息。既然這樣,你將循環你的DataTable的行,提供行的變量給MailMergeMessage,並每行調用Send方法。

 

Cancelling a Send Operation

Asynchronous send operations can be cancelled at any time:

// cancel the pending mail merge immediately
mailSender.SendCancel();

 

取消一個發送操作

 

異步發送操作能夠被隨時取消:

// cancel the pending mail merge immediately
mailSender.SendCancel();

 

Influencing Error Handling

Timeout in milliseconds:

mailSender.Timeout = 100000;

Maximum number of failures until sending a message will finally fail:

mailSender.MaxFailures = 3;

Retry delay time between failures:

mailSender.RetryDelayTime = 3000;

Delay time between each message:

mailSender.DelayBetweenMessages = 1000;

 

影響的錯誤處理

 

毫秒級的超時設置:

mailSender.Timeout = 100000;

發送失敗的最大失敗次數:

mailSender.MaxFailures = 3;

失敗重試的間隔時間:

mailSender.RetryDelayTime = 3000;

每次發送信息的間隔時間:

mailSender.DelayBetweenMessages = 1000;

Conclusion

By fixing the bugs in System.Net.Mail, I learned a lot about its design, but also at least as much about System.Reflection. Although all the bugs mentioned were reported to Microsoft a very long time ago, they didn't remove them. Life could be so easy, I'd even pay them for using my code... Anyway, MailMergeLib works well (again, after .NET 2.0 SP1 broke it).

結論

通過在System.Net.Mail裏修正這些bug,我學到了很多關於它的設計,和同樣多的System.Reflection的設計。儘管所有的bug在很久以前已經報告給了微軟,但微軟還沒有移除它們。生命如此短暫,我卻把它們付給了我的代碼無論如何,MailMergeLib運行得非常好(再一次,在.NET 2.0 SP1 超過它後)。

 

Special Thanks To

  • .NET Reflector for helping to dive deeply into System.Net.Mail and to find out ways to fix its bugs
  • Mike Bridge for his QPEncoder in DotNetOpenMail
  • People working on Mono 1.2.3.1 for their AttachmentBase.MimeTypes
  • All authors of inspiring articles on The Code Project that I have learned from

特別感謝

.NET Reflector,因爲有助於深入鑽研System.Net.Mail和找出修正bug的方法。

Mike Bridge ,因爲他在DotNetOpenMail裏的QPEncoder

Mono 1.2.3項目工作者,因爲他們的AttachmentBase.MimeTypes

CodeProject網站上的支持者。

History

  • 2007-07-10: Initial release
  • 2007-07-11: Minor doc and code update
  • 2007-10-08: SSL support added (thanks to WPKF), GetEncodedMailAddress fixed
  • 2007-12-28: Re-designed bug fixes in BugFixer class that .NET 2.0 SP1 / .NET 3.5 broke:
    • CorrectSubjectEncodedWordRFC2047compliant
    • AddToAddressWithCorrectEncoding
    • ClearMessageHeaders
  • 2008-01-01: Improvement of the Tools class
    • CalcMailSize(MailMessage msg) now gives accurate size instead of an estimation
    • Added GetMailAsMimeString (MailMessage msg) to retrieve the message content (same as in an EML file)
  • 2008-01-12: Minor improvements
    • Tools.WrapLine(string input, int length)will now allow empty lines (thanks to woetertie)
    • QPEncoder from DotNetOpenMail might return a string 1 byte longer than RFC2027-compliant, fixed
  • 2008-05-22: Minor improvement
    • Fixed potential problem with zip files
  • 2008-08-30: Minor improvements
    • Verified: Bugs still exist in .NET 3.5 SP1, and bugfixing of MailMergeLib works with it
    • Updated documentation

歷史記錄

  • 2007-07-10: 最初的發佈版本
  • 2007-07-11: 小文檔和代碼更新
  • 2007-10-08: 添加了SSL支持(感謝 WPKF), GetEncodedMailAddress 修正
  • 2007-12-28: BugFixer 類裏重新設計了bug 修正 that .NET 2.0 SP1 / .NET 3.5 broke:
    • CorrectSubjectEncodedWordRFC2047compliant
    • AddToAddressWithCorrectEncoding
    • ClearMessageHeaders
  • 2008-01-01: 改進了 Tools
    • CalcMailSize(MailMessage msg) 現在給了精確的大小來代替估計。
    • Added GetMailAsMimeString (MailMessage msg) to retrieve the message content (same as in an EML file)
  • 2008-01-12:  小改進
    • Tools.WrapLine(string input, int length)現在允許空行(感謝 woetertie)
    • QPEncoder from DotNetOpenMail might return a string 1 byte longer than RFC2027-compliant, fixed
  • 2008-05-22: 小改進
    • 修改了Zip文件的潛在問題。
  • 2008-08-30: 小改進
    • 覈實: Bugs 仍然存在於 .NET 3.5 SP1, 並且MailMergeLib bugfixing 仍然對bug起作用。
    • 文檔更新。

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

許可

這個文章,連同任何的相關資源和文件,在Code Project Open License(CPOL)下得到許可。

About the Author

Norbert Bietsch

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章