.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
; }
}
}
.net 邮件批量发送功能源码的更多相关文章
- 出售Illustrator脚本插件面板(包含面板源码,以及面板上所有的功能源码)
出售Illustrator脚本插件面板(包含面板源码,以及面板上所有的功能源码) 购买后可提供相应的小修改,以及教你使用往这个多列面里再加上按钮功能! 这套源码可作为工作使用,也可用为新手学习AI脚面 ...
- Delphi阿里云邮件推送【支持单一发信、邮件批量发送和获取指定条件下的发送数据】
作者QQ:(648437169) 点击下载➨Delphi阿里云邮件推送 阿里云api文档 [Delphi阿里云邮件推送]支持SingleSendMail(单一发信接口). ...
- ios自动滚动图片功能源码
源码AdScrollerView,一个已经封装好的UIScrollView的子类,可以自动滚动图片以及对应的描述语,类似淘宝app首页的广告滚动效果.滚动图片数量不限,并且显示pageControl. ...
- 微信跳转外部浏览器打开指定H5链接的功能源码
通常大家在微信内转发分享H5链接的时候都很容易碰到H5链接在微信内无法打开或在微信内无法打开app下载页的情况.通常这种情况微信会给个提示 “已停止访问该网址” ,那么导致这个情况的因素有哪些呢,主要 ...
- 微信最新跳转浏览器功能源码,实现微信内跳转手机浏览器访问网页url
微信最新自动跳转外部浏览器下载app/打开指定页面源码 源码说明: 适用安卓和苹果系统,支持任何网页链接.并且无论链接是否已经被微信拦截,均可实现微信内自动跳转浏览器打开. 生成的跳转链接具有极佳的防 ...
- Java中分页功能源码实例
一.源码(后附使用说明) package com.zhiyou100.crm.util; /** * 分页功能 * @author YangXianSheng * */ public class Pa ...
- 开源安卓Android流媒体音视频播放器实现声音自动停止、恢复、一键静音功能源码
本文转自EasyDarwin团队John的博客:http://blog.csdn.net/jyt0551/article/details/60802145 我们在开发安卓Android流媒体音视频播放 ...
- python实现最简单的计算器功能源码
import re def calc(formula): formula = re.sub(' ', '', formula) formula_ret = 0 match_brackets = re. ...
- vue购物车功能源码
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" ...
随机推荐
- PC问题-使用BAT方法清理Delphi临时文件
@echo offdel /S *.~*del /S *.dcudel /S *.dskdel /S *.hppdel /S *.ddpdel /S *.mpsdel /S *.mptdel /S * ...
- IOS-- UIView中的坐标转换
// 将像素point由point所在视图转换到目标视图view中,返回在目标视图view中的像素值 - (CGPoint)convertPoint:(CGPoint)point toView:(UI ...
- Java任务调度
最近项目要用到任务调度的相关知识,昨天信心满满的去官网学习,结果被坑个半死,我用的最新版的quartz,文档里说是兼容所有版本,但是代码连编译都报错,无奈只好从网上找资料,摸着石头过河总算有点眉目,在 ...
- ELK beats平台介绍(11th)
beats是一个代理,将不同类型的数据发送到elasticsearch.beats可以直接将数据发送到elasticsearch,也可以通过logstash将数据发送elasticsearch. be ...
- 【Stage3D学习笔记续】山寨Starling(十一):Touch事件体系
我们的山寨Starling版本将会在这里停止更新了,主要还是由于时间比较有限,而且我们的山寨版本也很好的完成了他的任务“了解Starling的核心渲染”,接下来的Starling解析我们将会直接阅读S ...
- C++学习笔记(十一):void*指针、类型转换和动态内存分配
void*指针 void关键字表示“空类型”的概念.但是,这里的“空类型”不表示“任意类型”,而是表示不存在的意思,也就是说C/C++不允许你写语句void a,不存在类型为void的东西. void ...
- java反射快速入门(二)
上一遍博文 , 简单介绍java 反射的常用接口,本遍博文, 我会结合项目开发的实际例子讲解下 java反射的使用 现在有个需求, 要将一个对象转换成xml格式, 或者将一串xml转换一个对象, 这时 ...
- java正则表达式入门基础
一.正则表达式术语 1)元字符 : 非一般字符,具有某种意义的字符.如 : \bX : \b边界符, 以 X开始的单词 2) 常用 : \d : 匹配一个数字 : \d , 匹配至少一个以上数字 \ ...
- vs2015 配置opencv3.0遇到的问题
1.问题 严重性 代码 说明 项目 文件 行 禁止显示状态 错误 C2872 "ACCESS_MASK": 不明确的符号 FaceFeature_GSF C:\Program Fi ...
- mysql服务的启动和停止 net stop mysql net start mysql
第一招.mysql服务的启动和停止 net stop mysql net start mysql 第二招.登陆mysql 语法如下: mysql -u用户名-p用户密码 键入命令mysql -uroo ...