.net 邮件批量发送功能源码

#define debug
 
using System;
using System.Text;
using System.Linq;
using System.IO;
using System.Net;
using System.Net.Mail;
using System.Net.Mime;
using System.Threading;
using System.ComponentModel;
using System.Diagnostics;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Collections.ObjectModel;
 
 
namespace Mail_Test.Mail
{
     /// <summary>
     /// 非线程安全类
     /// 使用注意事项:
     /// 1、该类无需(也不能)在外部包裹多线程,因为内部有提供“异步发送”方法,内、外都使用多线程会导致线程池对可用资源的误判,从而创建过多阻塞线程。
     /// 2、MailHelper类的 m_autoDisposeSmtp 属性的使用,具体见此字段注释。
     /// 3、启用 UTF-8 字符编码
     /// </summary>
     public class MailHelper
     {
 
         #region 构造函数
 
         /// <summary>
         /// 构建 MailHelper 实例
         /// </summary>
         /// <param name="isAsync">是否启用异步邮件发送,默认为同步发送</param>
         public MailHelper( bool isAsync = false )
         {
             m_IsAsync = isAsync;
         }
 
         /// <summary>
         /// 构建 MailHelper 实例
         /// </summary>
         /// <param name="mSmtpClient">SmtpClient实例</param>
         /// <param name="autoReleaseSmtp">是否自动释放SmtpClient实例</param>
         /// <param name="isAsync">是否启用异步邮件发送</param>
         public MailHelper(SmtpClient mSmtpClient, bool autoReleaseSmtp, bool isAsync = false )
         {
             this .SetSmtpClient(mSmtpClient, autoReleaseSmtp);
             m_IsAsync = isAsync;
         }
 
         #endregion
 
         #region  计划邮件数量 和 已执行完成邮件数量
 
         // 记录和获取在大批量执行异步短信发送时已经处理了多少条记录
         // 1、根据此值手动或自动释放 SmtpClient .实际上没有需要根据此值进行手动释放,因为完全可以用自动释放替换此逻辑
         // 2、根据此值可以自己设置进度
         private long m_CompletedSendCount = 0;
         public long CompletedSendCount
         {
             get { return Interlocked.Read( ref m_CompletedSendCount); }
             private set { Interlocked.Exchange( ref m_CompletedSendCount, value); }
         }
 
         // 计划邮件数量
         private long m_PrepareSendCount = 0;
         public long PrepareSendCount
         {
             get { return Interlocked.Read( ref m_PrepareSendCount); }
             private set { Interlocked.Exchange( ref m_PrepareSendCount, value); }
         }
 
         #endregion
 
         #region 异步 发送邮件相关参数
 
         // 是否启用异步发送邮件
         private bool m_IsAsync = false ;
 
         // 案例:因为异步发送邮件在SmtpClient处必须加锁保证一封一封的发送。
         // 这样阻塞了主线程。所以换用队列的方式以无阻塞的方式进行异步发送大批量邮件
 
         // 发送任务可能很长,所以使用 Thread 而不是用ThreadPool。(避免长时间暂居线程池线程),并且SmtpClient只支持一次一封邮件发送
         private Thread m_SendMailThread = null ;
 
         private AutoResetEvent m_AutoResetEvent = null ;
         private AutoResetEvent AutoResetEvent
         {
             get
             {
                 if (m_AutoResetEvent == null )
                     m_AutoResetEvent = new AutoResetEvent( true );
                 return m_AutoResetEvent;
             }
         }
 
         // 待发送队列缓存数量。单独开个计数是为了提高获取此计数的效率
         private int m_messageQueueCount = 0;
         // 因为 MessageQueue 可能在 m_SendMailThread 线程中进行出队操作,所以使用并发队列ConcurrentQueue.
         // 队列中的数据只能通过取消异步发送进行清空,或则就会每一元素都执行发送邮件
         private ConcurrentQueue<MailUserState> m_MessageQueue = null ;
         private ConcurrentQueue<MailUserState> MessageQueue
         {
             get
             {
                 if (m_MessageQueue == null )
                     m_MessageQueue = new ConcurrentQueue<MailUserState>();
                 return m_MessageQueue;
             }
         }
 
         /// <summary>
         /// 在执行异步发送时传递的对象,用于传递给异步发生完成时调用的方法 OnSendCompleted 。
         /// </summary>
         public object AsycUserState { get ; set ; }
 
         #endregion
 
         #region 内部字段、属性
 
         private SmtpClient m_SmtpClient = null ;
 
         /// <summary>
         /// 默认为false。设置在 MailHelper 类内部,发送完邮件后是否自动释放 SmtpClient 实例
         /// Smtp不管是在 MailHelper 内部还是在外部都必须进行主动释放,
         /// 因为:SmtpClient 没有提供 Finalize() 终结器,所以GC不会进行回收,只能使用完后主动进行释放,否则会发生内存泄露问题。
         ///
         /// 何时将 autoReleaseSmtp 设置为false,就是SmtpClient需要重复使用的情况,即需要使用“相同MailHelper”向“相同Smtp服务器”发送大批量的邮件时。
         /// </summary>
         private bool m_autoDisposeSmtp = false ;
 
         /// <summary>
         /// 设置此电子邮件的收件人的地址集合。
         /// </summary>
         Dictionary< string , string > m_DicTo = null ;
         Dictionary< string , string > DicTo
         {
             get
             {
                 if (m_DicTo == null )
                     m_DicTo = new Dictionary< string , string >();
                 return m_DicTo;
             }
         }
         /// <summary>
         /// 设置此电子邮件的抄送 (CC) 收件人的地址集合。
         /// </summary>
         Dictionary< string , string > m_DicCC = null ;
         Dictionary< string , string > DicCC
         {
             get
             {
                 if (m_DicCC == null )
                     m_DicCC = new Dictionary< string , string >();
                 return m_DicCC;
             }
         }
         /// <summary>
         /// 设置此电子邮件的密件抄送 (BCC) 收件人的地址集合。
         /// </summary>
         Dictionary< string , string > m_DicBcc = null ;
         Dictionary< string , string > DicBcc
         {
             get
             {
                 if (m_DicBcc == null )
                     m_DicBcc = new Dictionary< string , string >();
                 return m_DicBcc;
             }
         }
         // 附件集合
         Collection<Attachment> m_Attachments;
         Collection<Attachment> Attachments
         {
             get
             {
                 if (m_Attachments == null )
                     m_Attachments = new Collection<Attachment>();
                 return m_Attachments;
             }
         }
         // 指定一个电子邮件不同格式显示的副本。
         Collection<AlternateView> m_AlternateViews;
         Collection<AlternateView> AlternateViews
         {
             get
             {
                 if (m_AlternateViews == null )
                     m_AlternateViews = new Collection<AlternateView>();
                 return m_AlternateViews;
             }
         }
 
         #endregion
 
         #region 公开属性
 
         /// <summary>
         /// 设置此电子邮件的发信人地址。
         /// </summary>
         public string From { get ; set ; }
         /// <summary>
         /// 设置此电子邮件的发信人地址。
         /// </summary>
         public string FromDisplayName { get ; set ; }
 
         /// <summary>
         /// 设置此电子邮件的主题。
         /// </summary>
         public string Subject { get ; set ; }
         /// <summary>
         /// 设置邮件正文。
         /// </summary>
         public string Body { get ; set ; }
 
         /// <summary>
         /// 设置邮件正文是否为 Html 格式的值。
         /// </summary>
         public bool IsBodyHtml { get ; set ; }
 
         private int priority = 0;
         /// <summary>
         /// 设置此电子邮件的优先级  0-Normal   1-Low   2-High
         /// 默认Normal。
         /// </summary>
         public int Priority
         {
             get { return this .priority; }
             set
             {
                 if (value < 0 || value > 2)
                     priority = 0;
                 else
                     priority = value;
             }
         }
 
         #endregion
 
         /// <summary>
         /// 重置 MailHelper 实例信息
         /// 不释放 SmtpClient 实例和相关的AutoReleaseSimple字段,因为存在异步发送。。这两个字段由SetSmtpClient方法设置
         /// </summary>
         public void Reset()
         {
             From = String.Empty;
             FromDisplayName = String.Empty;
             if (m_DicTo != null )
                 m_DicTo.Clear();
             if (m_DicCC != null )
                 m_DicCC.Clear();
             if (m_DicBcc != null )
                 m_DicBcc.Clear();
             if (m_Attachments != null )
                 m_Attachments.Clear();
             if (m_AlternateViews != null )
                 m_AlternateViews.Clear();
 
             Subject = String.Empty;
             Body = String.Empty;
             IsBodyHtml = false ;
             priority = 0;
 
             AsycUserState = null ;
 
             // 1、不重置SmtpClient。根据 m_autoDisposeSmtp 参数自动释放或由外部主动释放
             // 2、不重置:异步待发送队列及队列计数,AutoResetEvent实例,执行异步发送线程,是否启用异步发送标识
         }
 
         #region SmtpClient 相关方法
 
         /// <summary>
         /// 检查此 MailHelper 实例是否已经设置了 SmtpClient
         /// </summary>
         /// <returns>true代表已设置</returns>
         public bool ExistsSmtpClient()
         {
             return m_SmtpClient != null ? true : false ;
         }
 
         /// <summary>
         /// 设置 SmtpClient 实例 和是否自动释放Smtp的唯一入口
         /// 1、将内部 计划数量 和 已完成数量 清零,重新统计以便自动释放SmtpClient
         /// 2、若要对SmtpClent设置SendCompleted事件,请在调用此方法前进行设置
         /// </summary>
         /// <param name="mSmtpClient"> SmtpClient 实例</param>
         /// <param name="autoReleaseSmtp">设置在 MailHelper 类内部,发送完邮件后是否自动释放 SmtpClient 实例</param>
         public void SetSmtpClient(SmtpClient mSmtpClient, bool autoReleaseSmtp)
         {
#if DEBUG
             Debug.WriteLine( "设置SmtpClient,自动释放为"   (autoReleaseSmtp ? "TRUE" : "FALSE" ));
#endif
             m_SmtpClient = mSmtpClient;
             m_autoDisposeSmtp = autoReleaseSmtp;
 
             // 将内部 计划数量 和 已完成数量 清零,重新统计以便自动释放SmtpClient  (MailHelper实例唯一的清零地方)
             m_PrepareSendCount = 0;
             m_CompletedSendCount = 0;
 
             if (m_IsAsync && autoReleaseSmtp)
             {
                 // 注册内部释放回调事件.释放对象---该事件不进行取消注册,只在释放SmtpClient时,一起释放   (所以SmtpClient与MailHelper绑定后,就不要再单独使用了)
                 m_SmtpClient.SendCompleted  = new SendCompletedEventHandler(SendCompleted4Dispose);
             }
         }
 
         /// <summary>
         /// 释放 SmtpClient
         /// </summary>
         public void ManualDisposeSmtp()
         {
             this .InnerDisposeSmtp();
         }
 
         /// <summary>
         /// 释放SmtpClient
         /// </summary>
         private void AutoDisposeSmtp()
         {
             if (m_autoDisposeSmtp && m_SmtpClient != null )
             {
                 if (PrepareSendCount == 0)
                 {
                     // PrepareSendCount=0 说明还未设置计划批量邮件数,所以不自动释放SmtpClient。
                     // 不能因为小于CompletedSendCount就报错,因为可能是先发送再设置计划邮件数量
                 }
                 else if (PrepareSendCount < CompletedSendCount)
                 {
                     throw new Exception(MailValidatorHelper.EMAIL_ADDRESS_RANGE_ERROR);
                 }
                 else if (PrepareSendCount == CompletedSendCount)
                 {
                     this .InnerDisposeSmtp();
                 }
             }
             else
             {
                 // 不清空和Dispose()内部的SmtpClient字段,即用在需要重复使用时不需要再调用 SetSmtpClient() 进行设置。
             }
         }
 
         /// <summary>
         /// 释放SmtpClient
         /// </summary>
         private void InnerDisposeSmtp()
         {
             if (m_SmtpClient != null )
             {
#if DEBUG
                 Debug.WriteLine( "释放SMtpClient" );
#endif
                 m_SmtpClient.Dispose();
                 m_SmtpClient = null ;
 
                 // 在设置 SmtpClient 入口处重新进行设置
                 m_autoDisposeSmtp = false ;
 
                 PrepareSendCount = 0;
                 CompletedSendCount = 0;
             }
         }
 
         #endregion
 
         #region MessageAddress、Attachment、AlternateView 相关方法
 
         #region 添加收件人、抄送人、密送人(每个类型中,若地址有重复,只保留第一个地址)
 
         /// <summary>
         /// 添加收件人、抄送人、密送人(每个类型中,若地址有重复,只保留第一个地址)
         /// </summary>
         /// <param name="type">类型:收件人、抄送人、密送人</param>
         /// <param name="addressList">Email地址列表</param>
         public void AddReceive(EmailAddrType type, IEnumerable< string > addressList)
         {
             MailValidatorHelper.ValideArgumentNull<IEnumerable< string >>(addressList, "addressList" );
             if (addressList.Count() > 0)
             {
                 Dictionary< string , string > dic = null ;
                 switch (type)
                 {
                     case EmailAddrType.To:
                         dic = DicTo;
                         break ;
                     case EmailAddrType.CC:
                         dic = DicCC;
                         break ;
                     case EmailAddrType.Bcc:
                         dic = DicBcc;
                         break ;
                     case EmailAddrType.From:
                         throw new Exception(MailValidatorHelper.EMAIL_ADDRESS_RANGE_ERROR);
                 }
 
                 foreach ( string address in addressList)
                 {
                     MailValidatorHelper.ValideStrNullOrEmpty(address, "addressList" , MailValidatorHelper.EMAIL_ADDRESS_LIST_ERROR);
                     if (dic.Count > 0 && !dic.ContainsKey(address))
                         dic.Add(address, String.Empty);
                 }
             }
         }
 
         /// <summary>
         /// 添加收件人、抄送人、密送人(每个类型中,若地址有重复,只保留第一个地址)
         /// </summary>
         /// <param name="type">类型:收件人、抄送人、密送人</param>
         /// <param name="address">Email地址</param>
         /// <param name="displayName">显示名称</param>
         public void AddReceive(EmailAddrType type, string address, string displayName)
         {
             MailValidatorHelper.ValideStrNullOrEmpty(address, "address" );
 
             Dictionary< string , string > dic = null ;
             switch (type)
             {
                 case EmailAddrType.To:
                     dic = DicTo;
                     break ;
                 case EmailAddrType.CC:
                     dic = DicCC;
                     break ;
                 case EmailAddrType.Bcc:
                     dic = DicBcc;
                     break ;
                 case EmailAddrType.From:
                     throw new Exception(MailValidatorHelper.EMAIL_ADDRESS_RANGE_ERROR);
             }
 
             if (dic.Count == 0 || !dic.ContainsKey(address))
                 dic.Add(address, displayName);
 
         }
 
         /// <summary>
         /// 添加收件人、抄送人、密送人(每个类型中,若地址有重复,只保留第一个地址)
         /// </summary>
         /// <param name="type">类型:收件人、抄送人、密送人</param>
         /// <param name="dicAddress">Email地址,显示名称</param>
         public void AddReceive(EmailAddrType type, Dictionary< string , string > dicAddress)
         {
             MailValidatorHelper.ValideArgumentNull<Dictionary< string , string >>(dicAddress, "dicAddress" );
             if (dicAddress.Count > 0)
             {
                 Dictionary< string , string > dic = null ;
                 switch (type)
                 {
                     case EmailAddrType.To:
                         dic = DicTo;
                         break ;
                     case EmailAddrType.CC:
                         dic = DicCC;
                         break ;
                     case EmailAddrType.Bcc:
                         dic = DicBcc;
                         break ;
                     case EmailAddrType.From:
                         throw new Exception(MailValidatorHelper.EMAIL_ADDRESS_RANGE_ERROR);
                 }
 
                 foreach (KeyValuePair< string , string > keyValue in dicAddress)
                 {
                     MailValidatorHelper.ValideStrNullOrEmpty(keyValue.Key, "dicAddress" , MailValidatorHelper.EMAIL_ADDRESS_DIC_ERROR);
                     if (dic.Count > 0 && !dic.ContainsKey(keyValue.Key))
                         dic.Add(keyValue.Key, keyValue.Value);
                 }
             }
         }
 
         #endregion
 
         #region 添加附件
 
         /// <summary>
         /// 添加单个附件
         /// </summary>
         /// <param name="attachment">Attachment附件实例</param>
         public void AddAttachment(Attachment attachment)
         {
             MailValidatorHelper.ValideArgumentNull<Attachment>(attachment, "attachment" );
             Attachments.Add(attachment);
         }
 
         /// <summary>
         /// 添加单个附件
         /// </summary>
         /// <param name="fieldPath">待上传文件路径</param>
         /// <param name="fileName">文件显示名称(不带后缀)</param>
         public void AddAttachment( string fieldPath, string fileName = "" )
         {
             MailValidatorHelper.ValideStrNullOrEmpty(fieldPath, "fieldPath" );
 
             this .InnerAddAttachment(fieldPath, fileName, false , String.Empty);
         }
 
         /// <summary>
         /// 添加内嵌资源(eg:图片,mp3等等)
         /// </summary>
         /// <param name="fieldPath">内嵌资源的文件路径</param>
         /// <param name="cidName">设置此附件的 MIME 内容 ID</param>
         public void AddInlineAttachment( string fieldPath, string cidName)
         {
             MailValidatorHelper.ValideStrNullOrEmpty(fieldPath, "fieldPath" );
             MailValidatorHelper.ValideStrNullOrEmpty(cidName, "cidName" );
 
             this .InnerAddAttachment(fieldPath, String.Empty, true , cidName);
         }
 
         private void InnerAddAttachment( string fieldPath, string fileName, bool isInline, string cidName)
         {
             // 因为Attachment中存储的时FilePath对应文件的Stream,所以这边在获取FileInfo信息的时候,同时转化为Stream传递给Attachment实例,
             // 避免再次根据FilePath获取文件内容
 
             FileInfo file = new FileInfo(fieldPath);
 
             Stream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
             Attachment data = new Attachment(stream, String.Empty);
 
             //实例邮件内容
             ContentDisposition disposition = data.ContentDisposition;
 
             if (isInline)
             {
                 disposition.Inline = true ;
                 // 设置此附件的 MIME 内容 ID。
                 data.ContentId = cidName;
             }
 
             // 设置文件附件的创建日期。
             disposition.CreationDate = file.CreationTime;
             // 设置文件附件的修改日期。
             disposition.ModificationDate = file.LastWriteTime;
             // 设置文件附件的读取日期。
             disposition.ReadDate = file.LastAccessTime;
             // 设定文件名称 (内嵌资源设置文件名后下载下来才有默认后缀)
             if (String.IsNullOrEmpty(fileName))
                 disposition.FileName = file.Name.ToString();
             else
             {
 
                 disposition.FileName = fileName   Path.GetExtension(fieldPath);
             }
 
             Attachments.Add(data);
         }
 
         #endregion
 
         #region 添加AlternateView
         // 指定一个电子邮件不同格式的副本。
         //(eg:发送HTML格式的邮件,可能希望同时提供邮件的纯文本格式,以防止一些收件人使用的电子邮件阅读程序无法显示html内容)
 
         /// <summary>
         /// 添加一个电子邮件不同格式的副本。
         /// </summary>
         /// <param name="filePath">包含电子邮件内容的文件路径</param>
         public void AddAlterViewPath( string filePath)
         {
             MailValidatorHelper.ValideStrNullOrEmpty(filePath, "filePath" );
             AlternateViews.Add( new AlternateView(filePath));
         }
 
         /// <summary>
         /// 添加一个电子邮件不同格式的副本。
         /// </summary>
         /// <param name="mailContent">电子邮件内容</param>
         public void AddAlterViewContent( string mailContent)
         {
             MailValidatorHelper.ValideStrNullOrEmpty(mailContent, "mailContent" );
             AlternateViews.Add(AlternateView.CreateAlternateViewFromString(mailContent));
         }
 
         /// <summary>
         /// 添加一个电子邮件不同格式的副本。
         /// </summary>
         /// <param name="contentStream">电子邮件内容流</param>
         public void AddAlterViewStream(Stream contentStream)
         {
             MailValidatorHelper.ValideArgumentNull<Stream>(contentStream, "contentStream" );
             AlternateViews.Add( new AlternateView(contentStream));
         }
 
         /// <summary>
         /// 添加一个电子邮件不同格式的副本。
         /// </summary>
         /// <param name="alternateView">电子邮件视图</param>
         public void AddAlternateView(AlternateView alternateView)
         {
             MailValidatorHelper.ValideArgumentNull<AlternateView>(alternateView, "alternateView" );
             AlternateViews.Add(alternateView);
         }
 
         #endregion
 
         #endregion
 
         #region 发送邮件 相关方法
 
         /// <summary>
         /// 计划批量发送邮件的个数,配合自动释放SmtpClient。(批量邮件发送不调用此方法就不会自动释放SmtpClient)
         /// 0、此方法可以在发送邮件方法之前或之后调用
         /// 1、只有设置后才会自动根据 m_autoDisposeSmtp 字段进行释放SmtpClient。
         /// 2、若 m_autoDisposeSmtp = false 即由自己手动进行设置的无需调用此方法设置预计邮件数
         /// </summary>
         /// <param name="preCount">计划邮件数量</param>
         public void SetBatchMailCount( long preCount)
         {
             PrepareSendCount = preCount;
 
             if (preCount < CompletedSendCount)
             {
                 throw new ArgumentOutOfRangeException( "preCount" , MailValidatorHelper.EMAIL_ADDRESS_RANGE_ERROR);
             }
             else if (preCount == CompletedSendCount)
             {
                 if (m_autoDisposeSmtp)
                     this .InnerDisposeSmtp();
             }
         }
 
         /// <summary>
         /// 同步发送一封Email
         /// </summary>
         public void SendOneMail()
         {
             m_PrepareSendCount = 1;
             this .InnerSendMessage();
         }
 
         /// <summary>
         /// 批量同步发送Email
         /// </summary>
         public void SendBatchMail()
         {
             this .InnerSendMessage();
         }
 
         /// <summary>
         /// 取消异步邮件发送
         /// </summary>
         public void SendAsyncCancel()
         {
             // 因为此类为非线程安全类,所以 SendAsyncCancel 和发送邮件方法中操作MessageQueue部分的代码肯定是串行化的。
             // 所以不存在一边入队,一边出队导致无法完全取消所有邮件发送
 
             // 1、清空队列。
             // 2、取消正在异步发送的mail。
             // 3、设置计划数量=完成数量
             // 4、执行 AutoDisposeSmtp()
 
             if (m_IsAsync)
             {
                 // 1、清空队列。
                 MailUserState tempMailUserState = null ;
                 while (MessageQueue.TryDequeue( out tempMailUserState))
                 {
                     Interlocked.Decrement( ref m_messageQueueCount);
                     MailMessage message = tempMailUserState.CurMailMessage;
                     this .InnerDisposeMessage(message);
                 }
                 tempMailUserState = null ;
                 // 2、取消正在异步发送的mail。
                 m_SmtpClient.SendAsyncCancel();
                 // 3、设置计划数量=完成数量
                 PrepareSendCount = CompletedSendCount;
                 // 4、执行 AutoDisposeSmtp()
                 this .AutoDisposeSmtp();
             }
             else
             {
                 throw new Exception(MailValidatorHelper.EMAIL_ASYNC_CALL_ERROR);
             }
         }
 
         /// <summary>
         /// 发送Email
         /// </summary>
         private void InnerSendMessage()
         {
 
             bool hasError = false ;
             MailMessage mMailMessage = null ;
 
             #region 构建 MailMessage
             try
             {
                 mMailMessage = new MailMessage();
 
                 mMailMessage.From = new MailAddress(From, FromDisplayName);
 
                 this .InnerSetAddress(EmailAddrType.To, mMailMessage);
                 this .InnerSetAddress(EmailAddrType.CC, mMailMessage);
                 this .InnerSetAddress(EmailAddrType.Bcc, mMailMessage);
 
                 mMailMessage.Subject = Subject;
                 mMailMessage.Body = Body;
 
                 if (m_Attachments != null && m_Attachments.Count > 0)
                 {
                     foreach (Attachment attachment in m_Attachments)
                         mMailMessage.Attachments.Add(attachment);
                 }
 
                 mMailMessage.SubjectEncoding = Encoding.UTF8;
                 mMailMessage.BodyEncoding = Encoding.UTF8;
                 // SmtpClient 的 Headers 中会根据 MailMessage 默认设置些值,所以应该为 UTF8 。
                 mMailMessage.HeadersEncoding = Encoding.UTF8;
 
                 mMailMessage.IsBodyHtml = IsBodyHtml;
 
                 if (m_AlternateViews != null && m_AlternateViews.Count > 0)
                 {
                     foreach (AlternateView alternateView in AlternateViews)
                     {
                         mMailMessage.AlternateViews.Add(alternateView);
                     }
                 }
 
                 mMailMessage.Priority = (MailPriority)Priority;
             }
             catch (ArgumentNullException argumentNullEx)
             {
                 hasError = true ;
                 throw argumentNullEx;
             }
             catch (ArgumentException argumentEx)
             {
                 hasError = true ;
                 throw argumentEx;
             }
             catch (FormatException formatEx)
             {
                 hasError = true ;
                 throw formatEx;
             }
             finally
             {
                 if (hasError)
                 {
                     if (mMailMessage != null )
                     {
                         this .InnerDisposeMessage(mMailMessage);
                         mMailMessage = null ;
                     }
                     this .InnerDisposeSmtp();
                 }
             }
 
             #endregion
 
             if (!hasError)
             {
                 if (m_IsAsync)
                 {
                     #region 异步发送邮件
 
                     if (PrepareSendCount == 1)
                     {
                         // 情况一:不重用 SmtpClient 实例会将PrepareSendCount设置为1
                         // 情况二:计划发送只有一条
 
                         // PrepareSendCount 是发送单条邮件。
                         MailUserState state = new MailUserState()
                         {
                             AutoReleaseSmtp = m_autoDisposeSmtp,
                             CurMailMessage = mMailMessage,
                             CurSmtpClient = m_SmtpClient,
                             IsSmpleMail = true ,
                             UserState = AsycUserState,
                         };
                         if (m_autoDisposeSmtp)
                             // 由发送完成回调函数根据 IsSmpleMail 字段进行释放
                             m_SmtpClient = null ;
 
                         ThreadPool.QueueUserWorkItem((userState) =>
                         {
                             // 无需 catch 发送异常,因为是异步,所以这里 catch 不到。
                             MailUserState curUserState = userState as MailUserState;
                             curUserState.CurSmtpClient.SendAsync(mMailMessage, userState);
                         }, state);
 
                     }
                     else
                     {
                         // 情况一:重用 SmtpClient 逻辑,即我们可以直接操作全局的 m_SmtpClient
                         // 情况二:批量发送邮件 PrepareSendCount>1
                         // 情况三:PrepareSendCount 还未设置,为0。比如场景在循环中做些判断,再决定发邮件,循环完才调用 SetBatchMailCount 设置计划邮件数量
 
                         MailUserState state = new MailUserState()
                         {
                             AutoReleaseSmtp = m_autoDisposeSmtp,
                             CurMailMessage = mMailMessage,
                             CurSmtpClient = m_SmtpClient,
                             UserState = AsycUserState,
                         };
 
                         MessageQueue.Enqueue(state);
                         Interlocked.Increment( ref m_messageQueueCount);
 
                         if (m_SendMailThread == null )
                         {
                             m_SendMailThread = new Thread(() =>
                             {
                                 // noItemCount 次获取不到元素,就抛出线程异常
                                 int noItemCount = 0;
                                 while ( true )
                                 {
                                     if (PrepareSendCount != 0 && PrepareSendCount == CompletedSendCount)
                                     {
                                         // 已执行完毕。
                                         this .AutoDisposeSmtp();
                                         break ;
                                     }
                                     else
                                     {
                                         MailUserState curUserState = null ;
 
                                         if (!MessageQueue.IsEmpty)
                                         {
#if DEBUG
                                             Debug.WriteLine( "WaitOne"   Thread.CurrentThread.ManagedThreadId);
#endif
                                             // 当执行异步取消时,会清空MessageQueue,所以 WaitOne 必须在从MessageQueue中取到元素之前
                                             AutoResetEvent.WaitOne();
 
                                             if (MessageQueue.TryDequeue( out curUserState))
                                             {
                                                 Interlocked.Decrement( ref m_messageQueueCount);
                                                 m_SmtpClient.SendAsync(curUserState.CurMailMessage, curUserState);
                                             }
                                         }
                                         else
                                         {
                                             if (noItemCount >= 10)
                                             {
                                                 // 没有正确设置 PrepareSendCount 值。导致已没有邮件但此线程出现死循环
                                                 this .InnerDisposeSmtp();
 
                                                 throw new Exception(MailValidatorHelper.EMAIL_PREPARESENDCOUNT_NOTSET_ERROR);
                                             }
 
                                             Thread.Sleep(1000);
                                             noItemCount  ;
                                         }
                                     }
                                     // SmtpClient 为null表示异步预计发送邮件数已经发送完,在 OnSendCompleted 进行了 m_SmtpClient 释放
                                     if (m_SmtpClient == null )
                                         break ;
                                 }
 
                                 m_SendMailThread = null ;
                             });
                             m_SendMailThread.Start();
                         }
                     }
 
                     #endregion
                 }
                 else
                 {
                     #region 同步发送邮件
                     try
                     {
                         m_SmtpClient.Send(mMailMessage);
                         m_CompletedSendCount  ;
                     }
                     catch (ObjectDisposedException smtpDisposedEx)
                     {
                         throw smtpDisposedEx;
                     }
                     catch (InvalidOperationException smtpOperationEx)
                     {
                         throw smtpOperationEx;
                     }
                     catch (SmtpFailedRecipientsException smtpFailedRecipientsEx)
                     {
                         throw smtpFailedRecipientsEx;
                     }
                     catch (SmtpException smtpEx)
                     {
                         throw smtpEx;
                     }
                     finally
                     {
                         if (mMailMessage != null )
                         {
                             this .InnerDisposeMessage(mMailMessage);
                             mMailMessage = null ;
                         }
                         this .AutoDisposeSmtp();
                     }
                     #endregion
                 }
             }
         }
 
         /// <summary>
         /// 将收件人、抄送人、密送人添加到 MailMessage 中
         /// </summary>
         /// <param name="type">收件人、抄送人、密送人</param>
         /// <param name="mMailMessage">待发送的MailMessage类</param>
         private void InnerSetAddress(EmailAddrType type, MailMessage mMailMessage)
         {
             MailAddressCollection receiveCol = null ;
             Dictionary< string , string > dicReceive = null ;
             bool hasAddress = false ;
             switch (type)
             {
                 case EmailAddrType.To:
                     {
                         if (m_DicTo != null && m_DicTo.Count > 0)
                         {
                             dicReceive = m_DicTo;
                             receiveCol = mMailMessage.To;
                             hasAddress = true ;
                         }
                     }
                     break ;
                 case EmailAddrType.CC:
                     {
                         if (m_DicCC != null && m_DicCC.Count > 0)
                         {
                             dicReceive = m_DicCC;
                             receiveCol = mMailMessage.CC;
                             hasAddress = true ;
                         }
                     }
                     break ;
                 case EmailAddrType.Bcc:
                     {
                         if (m_DicBcc != null && m_DicBcc.Count > 0)
                         {
                             dicReceive = m_DicBcc;
                             receiveCol = mMailMessage.Bcc;
                             hasAddress = true ;
                         }
                     }
                     break ;
                 case EmailAddrType.From:
                     throw new Exception(MailValidatorHelper.EMAIL_ADDRESS_RANGE_ERROR);
             }
             if (hasAddress)
             {
                 foreach (KeyValuePair< string , string > keyValue in dicReceive)
                 {
                     receiveCol.Add( new MailAddress(keyValue.Key, keyValue.Value));
                 }
             }
         }
 
         /// <summary>
         /// 释放 MailMessage 对象
         /// </summary>
         private void InnerDisposeMessage(MailMessage message)
         {
             if (message != null )
             {
                 if (message.AlternateViews.Count > 0)
                 {
                     message.AlternateViews.Dispose();
                 }
 
                 message.Dispose();
                 message = null ;
             }
         }
 
         /// <summary>
         /// 声明在 SmtpClient.SendAsync() 执行完后释放相关对象的回调方法   最后触发的委托
         /// </summary>
         protected void SendCompleted4Dispose( object sender, AsyncCompletedEventArgs e)
         {
             MailUserState state = e.UserState as MailUserState;
 
             if (state.CurMailMessage != null )
             {
                 MailMessage message = state.CurMailMessage;
                 this .InnerDisposeMessage(message);
                 state.CurMailMessage = null ;
             }
 
             if (state.IsSmpleMail)
             {
                 if (state.AutoReleaseSmtp && state.CurSmtpClient != null )
                 {
#if DEBUG
                     Debug.WriteLine( "释放SmtpClient" );
#endif
                     state.CurSmtpClient.Dispose();
                     state.CurSmtpClient = null ;
                 }
             }
             else
             {
                 if (!e.Cancelled)   // 取消的就不计数
                     CompletedSendCount  ;
 
                 if (state.AutoReleaseSmtp)
                 {
                     this .AutoDisposeSmtp();
                 }
 
                 // 若批量异步发送,需要设置信号
#if DEBUG
                 Debug.WriteLine( "Set"   Thread.CurrentThread.ManagedThreadId);
#endif
                 AutoResetEvent.Set();
             }
 
             // 先释放资源,处理错误逻辑
             if (e.Error != null && !state.IsErrorHandle)
             {
                 throw e.Error;
             }
         }
 
         #endregion
 
         #region 异步发送邮件,MessageQueue队列中缓冲的待发邮件数量,使用者可根据此数量来限制邮件数量,以免内存浪费
 
         /// <summary>
         /// 获取异步发送邮件,MessageQueue队列中缓冲的待发邮件数量
         /// (使用者可根据此数量来限制邮件数量,以免内存浪费)
         /// </summary>
         public int GetAwaitMailCountAsync()
         {
             if (m_IsAsync)
             {
                 return Thread.VolatileRead( ref m_messageQueueCount);
             }
             else
             {
                 throw new Exception(MailValidatorHelper.EMAIL_ASYNC_CALL_ERROR);
             }
 
         }
 
         #endregion
 
         #region 发送邮件前检查 相关方法
 
         /// <summary>
         /// 发送邮件前检查需要设置的信息是否完整,收集(提示 错误)信息
         /// </summary>
         public Dictionary<MailInfoType, string > CheckSendMail()
         {
             Dictionary<MailInfoType, string > dicMsg = new Dictionary<MailInfoType, string >();
 
             this .InnerCheckSendMail4Info(dicMsg);
             this .InnerCheckSendMail4Error(dicMsg);
 
             return dicMsg;
         }
 
         /// <summary>
         /// 发送邮件前检查需要设置的信息是否完整,收集 提示 信息
         /// </summary>
         public Dictionary<MailInfoType, string > CheckSendMail4Info()
         {
             Dictionary<MailInfoType, string > dicMsg = new Dictionary<MailInfoType, string >();
 
             this .InnerCheckSendMail4Info(dicMsg);
 
             return dicMsg;
         }
 
         /// <summary>
         /// 发送邮件前检查需要设置的信息是否完整,收集 错误 信息
         /// </summary>
         public Dictionary<MailInfoType, string > CheckSendMail4Error()
         {
             Dictionary<MailInfoType, string > dicMsg = new Dictionary<MailInfoType, string >();
 
             this .InnerCheckSendMail4Error(dicMsg);
 
             return dicMsg;
         }
 
         /// <summary>
         /// 发送邮件前检查需要设置的信息是否完整,收集 提示 信息
         /// </summary>
         /// <param name="dicMsg">将检查信息收集到此集合</param>
         private void InnerCheckSendMail4Info(Dictionary<MailInfoType, string > dicMsg)
         {
             // 注意每个验证使用完 infoBuilder 都要清零 infoBuilder 。
             StringBuilder infoBuilder = new StringBuilder(128);
 
             this .InnerCheckAddress(infoBuilder, dicMsg, EmailAddrType.CC);
             this .InnerCheckAddress(infoBuilder, dicMsg, EmailAddrType.Bcc);
 
             // 邮件主题
             if (Subject.Length == 0)
                 dicMsg.Add(MailInfoType.SubjectEmpty, MailInfoHelper.GetMailInfoStr(MailInfoType.SubjectEmpty));
 
             // 邮件内容
             if (Body.Length == 0 &&
                 (m_Attachments == null || (m_Attachments != null && m_Attachments.Count == 0))
                 )
             {
                 dicMsg.Add(MailInfoType.BodyEmpty, MailInfoHelper.GetMailInfoStr(MailInfoType.BodyEmpty));
             }
         }
 
         /// <summary>
         /// 发送邮件前检查需要设置的信息是否完整,收集 错误 信息
         /// </summary>
         /// <param name="dicMsg">将检查信息收集到此集合</param>
         private void InnerCheckSendMail4Error(Dictionary<MailInfoType, string > dicMsg)
         {
             // 注意每个验证使用完 infoBuilder 都要清零 infoBuilder 。
             StringBuilder infoBuilder = new StringBuilder(128);
 
             this .InnerCheckAddress(infoBuilder, dicMsg, EmailAddrType.From);
             this .InnerCheckAddress(infoBuilder, dicMsg, EmailAddrType.To);
 
             // SmtpClient 实例未设置
             if (m_SmtpClient == null )
                 dicMsg.Add(MailInfoType.SmtpClientEmpty, MailInfoHelper.GetMailInfoStr(MailInfoType.SmtpClientEmpty));
             else
             {
                 // SMTP 主服务器设置  (默认端口为25)
                 if (m_SmtpClient.Host.Length == 0)
                     dicMsg.Add(MailInfoType.HostEmpty, MailInfoHelper.GetMailInfoStr(MailInfoType.HostEmpty));
                 // SMPT 凭证
                 if (m_SmtpClient.EnableSsl && m_SmtpClient.ClientCertificates.Count == 0)
                     dicMsg.Add(MailInfoType.CertificateEmpty, MailInfoHelper.GetMailInfoStr(MailInfoType.CertificateEmpty));
             }
         }
 
         /// <summary>
         /// 检查 发件人、收件人、抄送人、密送人 邮箱地址
         /// </summary>
         /// <param name="infoBuilder">StringBuilder实例</param>
         /// <param name="dicMsg">将检查信息收集到此集合</param>
         /// <param name="type">接收邮件地址类型</param>
         private void InnerCheckAddress(StringBuilder infoBuilder, Dictionary<MailInfoType, string > dicMsg, EmailAddrType type)
         {
             Dictionary< string , string > dic = null ;
             MailInfoType addressFormat = MailInfoType.None;
             MailInfoType addressEmpty = MailInfoType.None;
             bool allowEmpty = true ;
             // 只有 发件人 是单个地址,特别进行处理
             bool hasHandle = false ;
             switch (type)
             {
                 case EmailAddrType.From:
                     {
                         // 标识为已处理
                         hasHandle = true ;
 
                         allowEmpty = false ;
                         if (From.Length == 0)
                         {
                             dicMsg.Add(MailInfoType.FromEmpty, MailInfoHelper.GetMailInfoStr(MailInfoType.FromEmpty));
                         }
                         else if (!MailValidatorHelper.IsEmail(From))
                         {
                             string strTemp = infoBuilder.AppendFormat(MailInfoHelper.GetMailInfoStr(MailInfoType.FromFormat), FromDisplayName, From).ToString();
                             dicMsg.Add(MailInfoType.FromFormat, strTemp);
                             infoBuilder.Length = 0;
                         }
                     }
                     break ;
                 case EmailAddrType.To:
                     {
                         dic = m_DicTo;
                         addressEmpty = MailInfoType.ToEmpty;
 
                         allowEmpty = false ;
                         addressFormat = MailInfoType.ToFormat;
                     }
                     break ;
                 case EmailAddrType.CC:
                     {
                         dic = m_DicCC;
                         addressFormat = MailInfoType.CCFormat;
 
                         allowEmpty = true ;
                         addressEmpty = MailInfoType.None;
                     }
                     break ;
                 case EmailAddrType.Bcc:
                     {
                         dic = m_DicBcc;
                         addressFormat = MailInfoType.BccFormat;
 
                         allowEmpty = true ;
                         addressEmpty = MailInfoType.None;
                     }
                     break ;
             }
 
 
             #region 处理 收件人、抄送人、密送人
 
             if (!hasHandle)
             {
                 if (dic == null )
                 {
                     if (!allowEmpty)
                     {
                         // 地址为空
                         dicMsg.Add(addressEmpty, MailInfoHelper.GetMailInfoStr(addressEmpty));
                     }
                 }
                 else
                 {
                     if (dic.Count > 0)
                     {
                         string strTemp = String.Empty;
                         // 邮件地址格式
                         foreach (KeyValuePair< string , string > keyValue in dic)
                         {
                             if (keyValue.Key.Length == 0)
                             {
                                 if (!allowEmpty)
                                 {
                                     // 地址为空
                                     dicMsg.Add(addressEmpty, MailInfoHelper.GetMailInfoStr(addressEmpty));
                                 }
                             }
                             else if (!MailValidatorHelper.IsEmail(keyValue.Key))
                             {
                                 if (strTemp.Length == 0)
                                     strTemp = MailInfoHelper.GetMailInfoStr(addressFormat);
                                 if (infoBuilder.Length > 0)
                                     infoBuilder.AppendLine();
                                 infoBuilder.AppendFormat(strTemp, keyValue.Value, keyValue.Key);
                             }
                         }
                         if (infoBuilder.Length > 0)
                         {
                             dicMsg.Add(addressFormat, infoBuilder.ToString());
                             infoBuilder.Length = 0;
                         }
                     }
                     else if (!allowEmpty)
                     {
                         // 地址为空
                         dicMsg.Add(addressEmpty, MailInfoHelper.GetMailInfoStr(addressEmpty));
                     }
                 }
             }
 
             #endregion
         }
 
         #endregion
 
     }
 
     /// <summary>
     /// 异步发送邮件时保存的信息,用于释放和传递数据
     /// </summary>
     public class MailUserState
     {
         #region 由MailHelper内部的SendCompleted注册的事件使用
         // 用于释放 MailMessage 和 SmtpClient
         public MailMessage CurMailMessage { get ; set ; }
         public bool AutoReleaseSmtp { get ; set ; }
         public SmtpClient CurSmtpClient { get ; set ; }
         // 只发送单封邮件的时候使用此进行判断释放 
         public bool IsSmpleMail { get ; set ; }
         #endregion
 
         /// <summary>
         /// 用户传递的状态对象
         /// </summary>
         public object UserState { get ; set ; }
 
         /// <summary>
         /// 当异步发送报错时可通过此标识是否已经处理该异常
         /// </summary>
         public bool IsErrorHandle { get ; set ; }
     }
 
}

转载于:https://www.cnblogs.com/dengpplive/p/5537507.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值