有时我们需要在网站中加入发送邮件的功能例如一个网上投稿系统当稿件被采用的时候发送邮件通知作者下面就以这个功能为例说明如何实现自动发送邮件
实现发送邮件功能
首先说一下在Net下如何发送邮件Net已经为我们准备好了与发送邮件相关的类只要直接调用即可非常方便下面是我自己写的一个邮件通知类
/// <summary>
/// 邮件通知服务类
/// </summary>
public class EmailNotificationService {
/// <summary>
/// 构造一个邮件通知服务类的实例
/// </summary>
/// <param name=smtpService>SMTP服务器的IP地址</param>
/// <param name=enableSSL>是否使用SSL连接SMTP服务器器</param>
/// <param name=port>SMTP服务器端口</param>
/// <param name=loginName>用于登录SMTP服务器的用户名</param>
/// <param name=password>登录密码</param>
public EmailNotificationService(
string smtpService
bool enableSSL
int port
string loginName
string password) {
thism_smtpService = smtpService;
thism_loginName = loginName;
thism_password = password;
thism_enableSSL = enableSSL;
thism_port = port;
}
private readonly string m_smtpService;
private readonly string m_loginName;
private readonly string m_password;
private readonly bool m_enableSSL;
private readonly int m_port;
/// <summary>
/// 发送邮件通知到指定的EMAIL地址
/// </summary>
/// <param name=senderName>显示在发件人一栏上的名称</param>
/// <param name=address>目的EMAIL地址</param>
/// <param name=title>邮件标题</param>
/// <param name=content>邮件内容</param>
public void SendTo(string senderName string address string title string content) {
MailMessage mail = new MailMessage();
mailToAdd(address);
mailFrom = new MailAddress(thism_loginName senderName EncodingUTF);
mailSubject = title;
mailBody = content;
mailBodyEncoding = EncodingUTF;
mailIsBodyHtml = false;
mailPriority = MailPriorityNormal;
SmtpClient smtp = new SmtpClient();
smtpCredentials = new NetworkCredential(thism_loginName thism_password);
smtpHost = thism_smtpService;
smtpEnableSsl = thism_enableSSL;
smtpPort = thism_port;
smtpSend(mail);
}
}
在使用时首先构造一个EmailNotificationService类再调用SendTo方法即可例如
EmailNotificationService mailNotificationService = new EmailNotificationService( true LoginPassword);
mailNotificationServiceSendTo(SenderName Title Content);
发送邮件实现方案
上面创建好了一个负责发送邮件的类接下来的问题是应该在什么时候调用这个类发送电子邮件需要进行网络通信耗时比较多而且SmtpClient的Send方法是会阻塞调用线程的一旦调用了该方法就要等到邮件发送完毕或出错才能结束方法调用所以不能将对EmailNotificationService的调用放在ASPNET页面的代码中如果这么做客户端就要等待很长时间才能获得响应用户体验是比较差的
SmtpClient还有一个SendAsync方法该方法与Send方法的区别是SendAsync是异步的调用该方法之后会产生一个新的线程来负责发送邮件之后调用线程立即返回不会再等待邮件发送结束那么我们是不是可以用SendAsync代替Send并在页面代码中调用呢?答案是否定的虽然客户端可以很快获得相应但邮件根本没有发送出去这是由ASPNET页面生命周期的特性决定的客户端向服务器的每一次请求页面都会经历一个由产生到销毁的过程当页面销毁的时候负责发送邮件的线程还没有完成发送邮件的工作就被强制结束了
由于ASPNET页面生命周期的特性我们不能将调用代码放在页面的代码中我们需要一个与页面无关的线程一个在网站运行时始终存在的线程我的方案是使用一个全局对象来管理一个发送邮件线程同时维护一个待发送邮件链表当全局对象创建的时候链表中没有任何内容发送邮件线程处于挂起状态当某个页面中的处理需要发送电子邮件时就将与发送邮件相关的信息添加到待发送邮件链表中此时链表不为空发送邮件线程开始工作逐个取出链表中的邮件信息并发送一直到链表为空再次进入挂起状态如此循环反复
实现发送邮件功能
基本的构思已经确定好了接下来就是写代码实现了首先定义一个类来封装待发送邮件的相关信息本文开头已经说过要以一个网上投稿系统作为例子所以这里所用的信息与该应用有关
/// <summary>
/// 封装发送邮件时所需信息的类
/// </summary>
public class MailNotifyInfo {
/// <summary>
/// 获取或设置稿件的标题
/// </summary>
public string Title {
get;
set;
}
/// <summary>
/// 获取或设置稿件的作者名称
/// </summary>
public string Author {
get;
set;
}
/// <summary>
/// 获取或设置作者的电子邮件地址
/// </summary>
public string EmailAddress {
get;
set;
}
/// <summary>
/// 获取或设置稿件的状态
/// </summary>
public ArticleStatus ArticleStatus {
get;
set;
}
}
然后是全局对象类的定义我使用了单件模式来实现其全局性
/// <summary>
/// 处理邮件发送功能的类
/// </summary>
public class NotificationHandler {
/// <summary>
/// 该类的静态实例
/// </summary>
private static readonly NotificationHandler g_instance = new NotificationHandler();
/// <summary>
/// 获取该类的唯一实例
/// </summary>
public static NotificationHandler Instance {
get {
return g_instance;
}
}
/// <summary>
/// 默认构造方法
/// </summary>
private NotificationHandler() {
thism_lockObject = new object();
thism_mailNotifyInfos = new LinkedList<MailNotifyInfo>();
thism_threadEvent = new ManualResetEvent(false);
thism_workThread = new Thread(thisThreadStart);
thism_workThreadStart();
}
private readonly LinkedList<MailNotifyInfo> m_mailNotifyInfos;
private readonly Thread m_workThread;
private readonly ManualResetEvent m_threadEvent;
private readonly Object m_lockObject;
/// <summary>
/// 添加待发送邮件的相关信息
/// </summary>
public void AppendNotification(MailNotifyInfo mailNotifyInfo) {
lock (thism_lockObject) {
thism_mailNotifyInfosAddLast(mailNotifyInfo);
if (thism_mailNotifyInfosCount != ) {
thism_threadEventSet();
}
}
}
/// <summary>
/// 发送邮件线程的执行方法
/// </summary>
private void ThreadStart() {
while (true) {
thism_threadEventWaitOne();
MailNotifyInfo mailNotifyInfo = thism_mailNotifyInfosFirstValue;
EmailNotificationService mailNotificationService = new EmailNotificationService( true LoginPassword);
mailNotificationServiceSendTo(稿件中心
mailNotifyInfoEmailAddress
稿件状态变更通知
StringFormat({}你的稿件{}状态已变更为{} mailNotifyInfoAuthor mailNotifyInfoTitle mailNotifyInfoArticleStatus));
lock (thism_lockObject) {
thism_mailNotifyInfosRemove(mailNotifyInfo);
if (thism_mailNotifyInfosCount == ) {
thism_threadEventReset();
}
}
}
}
该类比较简单首先在构造函数中初始化成员变量然后启动发送邮件线程此时该线程是挂起的
当外部调用AppendNotification方法时会在链表中添加一个MailNotifyInfo对象然后唤醒发送邮件线程由于在生产环境下可能会出现同时调用AppendNotification方法的情形所以这里要进行同步
发送邮件线程唤醒后进入一个死循环等待事件对象触发当事件对象出发之后就开始发送邮件了邮件发送完毕后从链表中删除已发送的邮件然后检查链表是否为空如果是则重置事件对象重新进入挂起状态同样地在对链表进行操作时也要进行同步
至此发送邮件的功能实现完毕需要发送邮件的时候只要像这样调用即可
MailNotifyInfo mailNotifyInfo = new MailNotifyInfo();
NotificationHandlerInstanceAppendNotification(mailNotifyInfo);
这只是一个很粗陋的框架而且还不完善例如这里假设网站是不间断运行的系统没有考虑当网站关闭时发送邮件线程的处理大家可以在这个基础上添砖加瓦使其更加完善另外自动发送邮件也是常见的功能例如定时检查某个条件如果成立则发送邮件要实现自动发送邮件的话只要对本文的方案稍加修改即可在NotificationHandler中添加一个Timer定时执行某个方法在这个方法中进行条件检查并触发事件即可