ASP.NET自動傳送郵件功能的實現

iDotNetSpace發表於2010-05-31

有時我們需要在網站中加入傳送郵件的功能,例如一個網上投稿系統,當稿件被採用的時候傳送郵件通知作者。下面就以這個功能為例說明如何實現自動傳送郵件。

  實現傳送郵件功能

  首先說一下在.Net下如何傳送郵件。.Net已經為我們準備好了與傳送郵件相關的類,只要直接呼叫即可,非常方便。下面是我自己寫的一個郵件通知類:

  ///

  /// 郵件通知服務類。

  ///

  public class EmailNotificationService {

  ///

  /// 構造一個郵件通知服務類的例項。

  ///

  /// SMTP伺服器的IP地址

  /// 是否使用SSL連線SMTP伺服器器

  /// SMTP伺服器埠

  /// 用於登入SMTP伺服器的使用者名稱

  /// 登入密碼

  public EmailNotificationService(

  string smtpService,

  bool enableSSL,

  int port,

  string loginName,

  string password) {

  this.m_smtpService = smtpService;

  this.m_loginName = loginName;

  this.m_password = password;

  this.m_enableSSL = enableSSL;

  this.m_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;

  ///

  /// 傳送郵件通知到指定的EMAIL地址。

  ///

  /// 顯示在“發件人”一欄上的名稱

  /// 目的EMAIL地址

  /// 郵件標題

  /// 郵件內容

  public void SendTo(string senderName, string address, string title, string content) {

  MailMessage mail = new MailMessage();

  mail.To.Add(address);

  mail.From = new MailAddress(this.m_loginName, senderName, Encoding.UTF8);

  mail.Subject = title;

  mail.Body = content;

  mail.BodyEncoding = Encoding.UTF8;

  mail.IsBodyHtml = false;

  mail.Priority = MailPriority.Normal;

  SmtpClient smtp = new SmtpClient();

  smtp.Credentials = new NetworkCredential(this.m_loginName, this.m_password);

  smtp.Host = this.m_smtpService;

  smtp.EnableSsl = this.m_enableSSL;

  smtp.Port = this.m_port;

  smtp.Send(mail);

  }

  }

在使用時,首先構造一個EmailNotificationService類,再呼叫SendTo方法即可。例如:

  EmailNotificationService mailNotificationService = new EmailNotificationService("smtp.gmail.com", true, 587, "LoginName@gmail.com", "LoginPassword");

  mailNotificationService.SendTo("SenderName", "TargetAddress@qq.com", "Title", "Content");

  傳送郵件實現方案

  上面建立好了一個負責傳送郵件的類,接下來的問題是應該在什麼時候呼叫這個類。傳送電子郵件需要進行網路通訊,耗時比較多,而且SmtpClient的Send方法是會阻塞呼叫執行緒的,一旦呼叫了該方法,就要等到郵件傳送完畢或出錯才能結束方法呼叫,所以不能將對EmailNotificationService的呼叫放在ASP.NET頁面的程式碼中。如果這麼做,客戶端就要等待很長時間才能獲得響應,使用者體驗是比較差的。

  SmtpClient還有一個SendAsync方法,該方法與Send方法的區別是,SendAsync是非同步的,呼叫該方法之後會產生一個新的執行緒來負責傳送郵件,之後呼叫執行緒立即返回,不會再等待郵件傳送結束。那麼我們是不是可以用SendAsync代替Send,並在頁面程式碼中呼叫呢?答案是否定的,雖然客戶端可以很快獲得相應,但郵件根本沒有傳送出去。這是由ASP.NET頁面生命週期的特性決定的,客戶端向伺服器的每一次請求,頁面都會經歷一個由產生到銷燬的過程,當頁面銷燬的時候,負責傳送郵件的執行緒還沒有完成傳送郵件的工作就被強制結束了。

  由於ASP.NET頁面生命週期的特性,我們不能將呼叫程式碼放在頁面的程式碼中。我們需要一個與頁面無關的執行緒,一個在網站執行時始終存在的執行緒。我的方案是使用一個全域性物件來管理一個髮送郵件執行緒,同時維護一個待傳送郵件連結串列。當全域性物件建立的時候,連結串列中沒有任何內容,傳送郵件執行緒處於掛起狀態。當某個頁面中的處理需要傳送電子郵件時,就將與傳送郵件相關的資訊新增到待傳送郵件連結串列中。此時連結串列不為空,傳送郵件執行緒開始工作,逐個取出連結串列中的郵件資訊併傳送,一直到連結串列為空,再次進入掛起狀態。如此迴圈反覆。

  實現傳送郵件功能

  基本的構思已經確定好了,接下來就是寫程式碼實現了。首先定義一個類來封裝待傳送郵件的相關資訊,本文開頭已經說過要以一個網上投稿系統作為例子,所以這裡所用的資訊與該應用有關。

  ///

  /// 封裝傳送郵件時所需資訊的類。

  ///

  public class MailNotifyInfo  {

  ///

  /// 獲取或設定稿件的標題。

  ///

  public string Title {

  get;

  set;

  }

  ///

  /// 獲取或設定稿件的作者名稱。

  ///

  public string Author {

  get;

  set;

  }

  ///

  /// 獲取或設定作者的電子郵件地址。

  ///

  public string EmailAddress {

  get;

  set;

  }

  ///

  /// 獲取或設定稿件的狀態。

  ///

  public ArticleStatus ArticleStatus {

  get;

  set;

  }

  }

  然後是全域性物件類的定義,我使用了單件模式來實現其全域性性。

  ///

  /// 處理郵件傳送功能的類。

  ///

  public class NotificationHandler {

  ///

  /// 該類的靜態例項。

  ///

  private static readonly NotificationHandler g_instance = new NotificationHandler();

  ///

  /// 獲取該類的唯一例項。

  ///

  public static NotificationHandler Instance {

  get {

  return g_instance;

  }

  }
///

  /// 預設構造方法。

  ///

  private NotificationHandler() {

  this.m_lockObject = new object();

  this.m_mailNotifyInfos = new LinkedList();

  this.m_threadEvent = new ManualResetEvent(false);

  this.m_workThread = new Thread(this.ThreadStart);

  this.m_workThread.Start();

  }

  private readonly LinkedList m_mailNotifyInfos;

  private readonly Thread m_workThread;

  private readonly ManualResetEvent m_threadEvent;

  private readonly Object m_lockObject;

  ///

  /// 新增待傳送郵件的相關資訊。

  ///

  public void AppendNotification(MailNotifyInfo mailNotifyInfo) {

  lock (this.m_lockObject) {

  this.m_mailNotifyInfos.AddLast(mailNotifyInfo);

  if (this.m_mailNotifyInfos.Count != 0) {

  this.m_threadEvent.Set();

  }

  }

  }

  ///

  /// 傳送郵件執行緒的執行方法。

  ///

  private void ThreadStart() {

  while (true) {

  this.m_threadEvent.WaitOne();

  MailNotifyInfo mailNotifyInfo = this.m_mailNotifyInfos.First.Value;

  EmailNotificationService mailNotificationService = new EmailNotificationService("smtp.gmail.com", true, 587, "LoginName@gmail.com", "LoginPassword");

  mailNotificationService.SendTo("稿件中心",

  mailNotifyInfo.EmailAddress,

  "稿件狀態變更通知",

  String.Format("{0}你的稿件{1}狀態已變更為{2}", mailNotifyInfo.Author, mailNotifyInfo.Title, mailNotifyInfo.ArticleStatus));

  lock (this.m_lockObject) {

  this.m_mailNotifyInfos.Remove(mailNotifyInfo);

  if (this.m_mailNotifyInfos.Count == 0) {

  this.m_threadEvent.Reset();

  }

  }

  }

  }

  該類比較簡單,首先在建構函式中初始化成員變數,然後啟動傳送郵件執行緒,此時該執行緒是掛起的。

  當外部呼叫AppendNotification方法時,會在連結串列中新增一個MailNotifyInfo物件,然後喚醒傳送郵件執行緒。由於在生產環境下可能會出現同時呼叫AppendNotification方法的情形,所以這裡要進行同步。

  傳送郵件執行緒喚醒後進入一個死迴圈,等待事件物件觸發。當事件物件出發之後就開始傳送郵件了。郵件傳送完畢後從連結串列中刪除已傳送的郵件,然後檢查連結串列是否為空,如果是則重置事件物件,重新進入掛起狀態。同樣地,在對連結串列進行操作時也要進行同步。

  至此,傳送郵件的功能實現完畢。需要傳送郵件的時候只要像這樣呼叫即可:

  MailNotifyInfo mailNotifyInfo = new MailNotifyInfo();

  .....

  NotificationHandler.Instance.AppendNotification(mailNotifyInfo);

  這只是一個很粗陋的框架,而且還不完善。例如,這裡假設網站是不間斷執行的系統,沒有考慮當網站關閉時傳送郵件執行緒的處理。大家可以在這個基礎上添磚加瓦,使其更加完善。另外,自動傳送郵件也是常見的功能,例如定時檢查某個條件,如果成立則傳送郵件。要實現自動傳送郵件的話,只要對本文的方案稍加修改即可:在NotificationHandler中新增一個Timer,定時執行某個方法,在這個方法中進行條件檢查並觸發事件即可。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-664115/,如需轉載,請註明出處,否則將追究法律責任。

相關文章