#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; }
    }
 
}

.net 邮件批量发送功能源码的更多相关文章

  1. 出售Illustrator脚本插件面板(包含面板源码,以及面板上所有的功能源码)

    出售Illustrator脚本插件面板(包含面板源码,以及面板上所有的功能源码) 购买后可提供相应的小修改,以及教你使用往这个多列面里再加上按钮功能! 这套源码可作为工作使用,也可用为新手学习AI脚面 ...

  2. Delphi阿里云邮件推送【支持单一发信、邮件批量发送和获取指定条件下的发送数据】

    作者QQ:(648437169) 点击下载➨Delphi阿里云邮件推送               阿里云api文档 [Delphi阿里云邮件推送]支持SingleSendMail(单一发信接口). ...

  3. ios自动滚动图片功能源码

    源码AdScrollerView,一个已经封装好的UIScrollView的子类,可以自动滚动图片以及对应的描述语,类似淘宝app首页的广告滚动效果.滚动图片数量不限,并且显示pageControl. ...

  4. 微信跳转外部浏览器打开指定H5链接的功能源码

    通常大家在微信内转发分享H5链接的时候都很容易碰到H5链接在微信内无法打开或在微信内无法打开app下载页的情况.通常这种情况微信会给个提示 “已停止访问该网址” ,那么导致这个情况的因素有哪些呢,主要 ...

  5. 微信最新跳转浏览器功能源码,实现微信内跳转手机浏览器访问网页url

    微信最新自动跳转外部浏览器下载app/打开指定页面源码 源码说明: 适用安卓和苹果系统,支持任何网页链接.并且无论链接是否已经被微信拦截,均可实现微信内自动跳转浏览器打开. 生成的跳转链接具有极佳的防 ...

  6. Java中分页功能源码实例

    一.源码(后附使用说明) package com.zhiyou100.crm.util; /** * 分页功能 * @author YangXianSheng * */ public class Pa ...

  7. 开源安卓Android流媒体音视频播放器实现声音自动停止、恢复、一键静音功能源码

    本文转自EasyDarwin团队John的博客:http://blog.csdn.net/jyt0551/article/details/60802145 我们在开发安卓Android流媒体音视频播放 ...

  8. python实现最简单的计算器功能源码

    import re def calc(formula): formula = re.sub(' ', '', formula) formula_ret = 0 match_brackets = re. ...

  9. vue购物车功能源码

    <!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" ...

随机推荐

  1. PC问题-使用BAT方法清理Delphi临时文件

    @echo offdel /S *.~*del /S *.dcudel /S *.dskdel /S *.hppdel /S *.ddpdel /S *.mpsdel /S *.mptdel /S * ...

  2. IOS-- UIView中的坐标转换

    // 将像素point由point所在视图转换到目标视图view中,返回在目标视图view中的像素值 - (CGPoint)convertPoint:(CGPoint)point toView:(UI ...

  3. Java任务调度

    最近项目要用到任务调度的相关知识,昨天信心满满的去官网学习,结果被坑个半死,我用的最新版的quartz,文档里说是兼容所有版本,但是代码连编译都报错,无奈只好从网上找资料,摸着石头过河总算有点眉目,在 ...

  4. ELK beats平台介绍(11th)

    beats是一个代理,将不同类型的数据发送到elasticsearch.beats可以直接将数据发送到elasticsearch,也可以通过logstash将数据发送elasticsearch. be ...

  5. 【Stage3D学习笔记续】山寨Starling(十一):Touch事件体系

    我们的山寨Starling版本将会在这里停止更新了,主要还是由于时间比较有限,而且我们的山寨版本也很好的完成了他的任务“了解Starling的核心渲染”,接下来的Starling解析我们将会直接阅读S ...

  6. C++学习笔记(十一):void*指针、类型转换和动态内存分配

    void*指针 void关键字表示“空类型”的概念.但是,这里的“空类型”不表示“任意类型”,而是表示不存在的意思,也就是说C/C++不允许你写语句void a,不存在类型为void的东西. void ...

  7. java反射快速入门(二)

    上一遍博文 , 简单介绍java 反射的常用接口,本遍博文, 我会结合项目开发的实际例子讲解下 java反射的使用 现在有个需求, 要将一个对象转换成xml格式, 或者将一串xml转换一个对象, 这时 ...

  8. java正则表达式入门基础

    一.正则表达式术语 1)元字符 : 非一般字符,具有某种意义的字符.如 : \bX : \b边界符, 以 X开始的单词 2) 常用 : \d : 匹配一个数字 : \d ,  匹配至少一个以上数字 \ ...

  9. vs2015 配置opencv3.0遇到的问题

    1.问题 严重性 代码 说明 项目 文件 行 禁止显示状态 错误 C2872 "ACCESS_MASK": 不明确的符号 FaceFeature_GSF C:\Program Fi ...

  10. mysql服务的启动和停止 net stop mysql net start mysql

    第一招.mysql服务的启动和停止 net stop mysql net start mysql 第二招.登陆mysql 语法如下: mysql -u用户名-p用户密码 键入命令mysql -uroo ...