最近做了一個使用 C# 寫了一個發送郵件的 windows 服務,在這里記錄一下。
首先使用 Visual Studio 2015 創建一個 windows 服務項目。
然后在設計器上面右擊添加安裝程序。如下圖。
安裝好后,選擇安裝程序設計界面,選擇服務和安裝程序右擊選擇屬性修改一些屬性值。
PS:如果不給服務添加安裝程序,后面是沒法把服務安裝至 windows 系統里的。
在數據庫創建一個表,用於存儲需要發送的郵件信息。
create table MainInfo ( MainInfoID int not null identity(1,1) primary key, Mail_To nvarchar(64) not null, -- 收件人郵箱 Title nvarchar(128) not null, -- 郵件標題 Content nvarchar(max) null, -- 郵件內容 Mode int not null default(0), -- 發送方式,0為默認發送,1為抄送,2為密送 SendState int not null default(0), -- 發送狀態,0為未發送,1為發送成功,2為發送失敗 IsTimer int not null default(0), -- 0為即時發送,1為定時發送 SendTime nvarchar(64) null, -- 定時發送的時間 AttAchFileUrl nvarchar(max) null, -- 添加附件的地址 AttAchFileName nvarchar(128) null, -- 附件名稱 IsServerUrl int null default(0) -- 附件地址是否為服務器地址 )
下面開始貼出代碼:
SqlHelper.cs,訪問數據庫類。

using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Data.SqlClient; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SendMail { public class Conn { public static string StrConn { get { //讀取文本文件(txt) //return Conn.getValue(@"C:\Users\Brambling\Desktop\Demo\SendMail\SendMail\DB_Config\DB_Config.txt", "StrConn"); //讀取配置文件 //return ConfigurationManager.ConnectionStrings["StrConn"].ToString(); //return ConfigurationManager.AppSettings["Conn"].ToString(); //直接返回數據庫連接字符串 return "Data Source=.;Initial Catalog=Test;User ID=sa;Pwd=xxxxxx;Enlist=true;Pooling=true;Max Pool Size=300;Min Pool Size=0;Connection Lifetime=300;packet size=1000"; } } public static SqlConnection SqlConn { get { return new SqlConnection(StrConn); } } private static string getValue(string path, string name) { string[] str = File.ReadAllLines(path); for (int i = 0; i < str.Length; i++) { if (str[i].StartsWith(name)) { return str[i].Replace(name + "=", ""); } } return ""; } } public class SqlHelper { public DataSet GetDataSet(string sql) { DataSet ds = new DataSet(); SqlConnection conn = Conn.SqlConn; try { conn.Open(); SqlDataAdapter sda = new SqlDataAdapter(sql, conn); sda.Fill(ds); } catch (Exception) { } finally { conn.Close(); } return ds; } public DataTable GetDataTable(string sql) { DataSet ds = new DataSet(); DataTable dt = new DataTable(); SqlConnection conn = Conn.SqlConn; try { conn.Open(); SqlDataAdapter sda = new SqlDataAdapter(sql, conn); sda.Fill(ds); if (ds != null && ds.Tables.Count > 0) { dt = ds.Tables[0]; } } catch (Exception) { } finally { conn.Close(); } return dt; } public bool ExecSql(string sql) { int num = 0; SqlConnection conn = Conn.SqlConn; try { conn.Open(); SqlCommand cmd = new SqlCommand(sql, conn); num = cmd.ExecuteNonQuery(); } catch (Exception) { } finally { conn.Close(); } return num > 0; } } }
這里我嘗試了讀取配置文件的數據庫連接串,但是好像 windows 服務讀取不到配置文件。還有個辦法就是讀取一個文本文件(txt)。
文本文件(txt)的連接串寫法:
StrConn=Data Source=.;Initial Catalog=Test;User ID=sa;Pwd=xxxxxx;Enlist=true; Pooling=true;Max Pool Size=300;Min Pool Size=0;Connection Lifetime=300;packet size=1000
Mail.cs,發送郵件類。

using System; using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; using System.Net; using System.Net.Mail; using System.Text; using System.Threading.Tasks; namespace SendMail { public class Mail { SqlHelper sqlhelper = new SqlHelper(); public void SendMail() { MailMessage mailmsg = null; NetworkCredential credential = null; SmtpClient client = null; string sql = " select top 1 * from MainInfo where SendState='0' and (IsTimer='0' or (IsTimer='1' and SendTime is not null and Convert(datetime,SendTime)<=getdate())) order by IsTimer,SendTime "; DataTable dt = sqlhelper.GetDataTable(sql); if (dt != null && dt.Rows.Count > 0) { string Id = dt.Rows[0]["MainInfoID"].ToString(); try { //創建一個身份憑證,即發送郵件的用戶名和密碼 credential = new NetworkCredential("980095349@qq.com", "xxxxxx"); //發送郵件的實例,服務器和端口 client = new SmtpClient("smtp.qq.com", 25); //發送郵件的方式,通過網絡發送 client.DeliveryMethod = SmtpDeliveryMethod.Network; //是否啟用 SSL client.EnableSsl = true; //指定發送郵件的身份憑證 client.Credentials = credential; //發送的郵件信息 mailmsg = new MailMessage(); // 指定發件人郵箱和顯示的發件人名稱 mailmsg.From = new MailAddress("980095349@qq.com", "午夜游魂"); // 指定收件人郵箱 MailAddress mailto = new MailAddress(dt.Rows[0]["Mail_To"].ToString()); if (dt.Rows[0]["Mode"].ToString() == "1") { mailmsg.CC.Add(mailto); // 抄送 } else if (dt.Rows[0]["Mode"].ToString() == "2") { mailmsg.Bcc.Add(mailto); // 密送 } else { mailmsg.To.Add(mailto); // 默認發送 } //郵件主題 mailmsg.Subject = dt.Rows[0]["Title"].ToString(); mailmsg.SubjectEncoding = Encoding.UTF8; //郵件內容 mailmsg.Body = dt.Rows[0]["Content"].ToString(); mailmsg.BodyEncoding = Encoding.UTF8; //添加附件 string url = dt.Rows[0]["AttAchFileUrl"].ToString(); // 附件地址 string name = dt.Rows[0]["AttAchFileName"].ToString(); // 附件名稱 if (dt.Rows[0]["IsServerUrl"].ToString() == "1") // 判斷附件地址是否為服務器地址 { if (!string.IsNullOrEmpty(url) && !string.IsNullOrEmpty(name)) { // 從指定的服務器附件地址加載附件,並轉換為 IO 流 添加到郵件附件中 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); HttpWebResponse response = (HttpWebResponse)request.GetResponse(); Stream stream = response.GetResponseStream(); mailmsg.Attachments.Add(new Attachment(stream, name)); } } else { if (!string.IsNullOrEmpty(url)) { mailmsg.Attachments.Add(new Attachment(@url)); // 本地路徑可直接加載 } } client.Send(mailmsg); // 發送郵件 UpdateState(Id, "1"); // 發送成功修改發送狀態為 1 } catch (Exception ex) { UpdateState(Id, "2"); // 發送失敗修改發送狀態為 2 } } } public bool UpdateState(string Id, string state) { string SendTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); string sql = " update MainInfo set SendState='" + state + "',SendTime='" + SendTime + "' where MainInfoID='" + Id + "' "; bool b = sqlhelper.ExecSql(sql); return b; } } }
在這里我把發送郵件的用戶、密碼、服務器、端口等都是寫死的。實際使用中可以考慮單獨建立一張發送郵件的配置表,和郵件信息表關聯起來。
SendMailMain.cs,設置定時器類。

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Timers; namespace SendMail { public class SendMailMain { // 設置定時器 private System.Timers.Timer t = null; public void Init() { try { if (t == null) { t = new System.Timers.Timer(); t.Elapsed += new ElapsedEventHandler(SendMail); // 綁定事件 t.Interval = 5000; // 指定執行的間隔時間 t.Enabled = true; // 是否啟用執行 System.Timers.Timer.Elapsed 事件 t.AutoReset = true; // 設置為 true 表示一直執行,false 為只執行一次 } } catch { t.Stop(); t.Dispose(); } } private void SendMail(object sender, ElapsedEventArgs args) { try { ((System.Timers.Timer)sender).Enabled = false; //單線程管控 Mail mail = new Mail(); mail.SendMail(); } catch (Exception) { throw; } finally { ((System.Timers.Timer)sender).Enabled = true; //單線程管控 } } } }
Service1.cs,服務開始類。

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Linq; using System.ServiceProcess; using System.Text; using System.Threading.Tasks; namespace SendMail { public partial class Service1 : ServiceBase { public Service1() { InitializeComponent(); // 啟用 暫停和恢復服務功能 //base.CanPauseAndContinue = true; } // 服務開始執行方法 protected override void OnStart(string[] args) { SendMailMain sm = new SendMailMain(); sm.Init(); } // 服務停止執行方法 protected override void OnStop() { } // 計算機關閉執行方法 protected override void OnShutdown() { } // 恢復服務執行方法 protected override void OnContinue() { } // 暫停服務執行方法 protected override void OnPause() { } } }
上面就是完全的代碼了,下面先創建一個測試單元,測試一下發送郵件。
首先在數據庫插入一條要發送的郵件信息的數據:
insert into MainInfo(Mail_To,Title,Content,AttAchFileUrl,AttAchFileName,IsServerUrl) values('1171588826@qq.com','測試郵件','測試郵件,請勿回復!', 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1494357502809&di=66d6a7909bfe54624a16e02caefb9838&imgtype=0&src=http%3A%2F%2F5.66825.com%2Fdownload%2Fpic%2F000%2F330%2F7599586ba2ba3bed5d76ea182883fca6.jpg', '孫悟空.jpg','1')
然后直接使用測試單元調用發送郵件的方法:
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using SendMail; using System.IO; namespace UnitTest { [TestClass] public class UnitTest1 { [TestMethod] public void TestMethod1() { Mail mail = new Mail(); mail.SendMail(); } } }
發送成功了。
下面開始安裝 windows 服務。
首先找到路徑 C:\Windows\Microsoft.NET\Framework\v4.0.30319 或者路徑 C:\Windows\Microsoft.NET\Framework\v2.0.50727 下面的 InstallUtil.exe,具體是哪一個下面的,根據版本而定。
然后新建一個文件夾,把剛剛找到的 InstallUtil.exe 文件和 bin\ Debug 或者 Release 文件夾下編譯好的文件全部復制到該文件夾下。
然后以管理員身份運行 cmd,輸入如下圖命令安裝 windows 服務。
使用 InstallUtil.exe SendMail.exe /u 命令卸載安裝的服務。
或者使用下面這種簡單的方法。
附上代碼:

@echo 啟動安裝服務中.... @Set installPath=C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe @Set serversName=SendMailService @goto checkFile :checkFile @echo 檢測Framework安裝路徑: %installPath% @IF NOT EXIST "%installPath%" GOTO filed @IF EXIST "%installPath%" GOTO success :run @rem @set /p type=請選擇服務操作模式,安裝(1),卸載(2),退出(3)... @IF "%type%"=="" goto run @IF "%type%"=="1" goto runInstall @IF "%type%"=="2" goto runUnInstall @IF "%type%"=="3" exit :success @echo 地址檢測完成 @rem @goto run :filed @echo 檢測失敗,當前路徑文件不存在... @set /p installPath=請重新指定物理路徑: @goto checkFile :runInstall %installPath% SendMail.exe @rem @net start %serversName% @pause @goto run :runUnInstall @rem @net stop %serversName% @sc delete %serversName% @pause @rem @goto run
把上面這一段代碼復制到新建的文本文件(txt)中,然后把后綴改為 bat,然后把它和 bin\ Debug 或者 Release 文件夾下編譯好的文件放到同一個文件夾下。然后執行這個后綴為 bat 的文件,根據提示的步驟就可以很簡單的安裝和卸載服務了。
啟動服務命令: net start SendMailService(服務的名稱)
停止服務命令: net stop SendMailService(服務的名稱)
PS:如果安裝的服務的文件進行了修改,但是路徑沒有變化的話是不需要重新注冊服務的,直接停止服務,然后用新的文件覆蓋原來的文件即可,如果路徑發生變化,應該先卸載這個服務,然后重新安裝這個服務。
最后一步,因為這個服務依賴於 sql server ,所以需要把服務設置為延遲啟動。
選中服務右擊,選擇屬性。把啟動類型設置為:自動(延遲啟動)。
這樣一個使用 windows 服務發送郵件的功能就完成了。現在可以把服務開啟,然后向數據庫插入一條郵件信息的數據,試試看效果。