我心(xin)中的核心(xin)組件(可插拔的AOP)~第五回 消(xiao)息組件
之所以把發消(xiao)息拿出(chu)來(lai),完(wan)全是因為微(wei)軟的(de)(de)(de)(de)(de)(de)orchard項(xiang)目(mu),在這(zhe)個(ge)項(xiang)目(mu)里,將(jiang)公用的(de)(de)(de)(de)(de)(de)與(yu)(yu)領域(yu)無關的(de)(de)(de)(de)(de)(de)功能(neng)模塊(kuai)(kuai)進(jin)行抽象,形成了一個(ge)個(ge)的(de)(de)(de)(de)(de)(de)組件(jian),這(zhe)些組件(jian)通過引用和注(zhu)入的(de)(de)(de)(de)(de)(de)方(fang)式(shi)進(jin)行工作(zuo),感覺(jue)對于應(ying)用程序的(de)(de)(de)(de)(de)(de)擴(kuo)展(zhan)性上有(you)很大(da)的(de)(de)(de)(de)(de)(de)提(ti)高,消(xiao)息組件(jian)的(de)(de)(de)(de)(de)(de)提(ti)出(chu)是因為它的(de)(de)(de)(de)(de)(de)不固(gu)定性,從小方(fang)面說(shuo),項(xiang)目(mu)模塊(kuai)(kuai)的(de)(de)(de)(de)(de)(de)發消(xiao)息的(de)(de)(de)(de)(de)(de)方(fang)式(shi)可(ke)能(neng)是不同的(de)(de)(de)(de)(de)(de),有(you)過模塊(kuai)(kuai)是email,有(you)的(de)(de)(de)(de)(de)(de)是數據庫,有(you)的(de)(de)(de)(de)(de)(de)是短(duan)信;而從大(da)的(de)(de)(de)(de)(de)(de)方(fang)面說(shuo),對于項(xiang)目(mu)與(yu)(yu)項(xiang)目(mu)來(lai)說(shuo),它們(men)發消(xiao)息的(de)(de)(de)(de)(de)(de)方(fang)式(shi)也可(ke)能(neng)不同,所以,把它抽象出(chu)來(lai),就顯得很必要(yao)了。
對于一個消(xiao)(xiao)息來(lai)說(shuo),它的行為很固定(ding),即發(fa)消(xiao)(xiao)息,Send,而(er)考慮到網絡(luo)阻塞問(wen)題,我們也同樣(yang)提供了異常消(xiao)(xiao)息的發(fa)送,接口規范如下(xia):
/// <summary> /// Message Interface /// Author:Garrett /// </summary> public interface IMessageManager { /// <summary> /// Sends a message to a channel using a content item as the recipient /// </summary> /// <param name="recipient">A content item to send the message to.</param> /// <param name="type">A custom string specifying what type of message is sent. Used in even handlers to define the message.</param> /// <param name="service">The name of the channel to use, e.g. "email"</param> /// <param name="properties">A set of specific properties for the channel.</param> void Send(string recipient, MessageType type, string subject, string body); /// <summary> /// Sends a message to a channel using a set of content items as the recipients /// </summary> /// <param name="recipients">A set of content items to send the message to. Only one message may be sent if the channel manages it.</param> /// <param name="type">A custom string specifying what type of message is sent. Used in even handlers to define the message.</param> /// <param name="service">The name of the channel to use, e.g. "email"</param> /// <param name="properties">A set of specific properties for the channel.</param> void Send(IEnumerable<string> recipients, MessageType type, string subject, string body); /// <summary> /// Async Sends a message to a channel using a set of content items as the recipients /// </summary> /// <param name="recipients">A set of content items to send the message to. Only one message may be sent if the channel manages it.</param> /// <param name="type">A custom string specifying what type of message is sent. Used in even handlers to define the message.</param> /// <param name="service">The name of the channel to use, e.g. "email"</param> /// <param name="properties">A set of specific properties for the channel.</param> /// <param name="isAsync">is Async</param> void Send(IEnumerable<string> recipients, MessageType type, string subject, string body, bool isAsync); }
有了規范(fan),就是實(shi)現(xian)這個規范(fan),我們以Email為例,看(kan)一下 Email消息發送的實(shi)現(xian)代碼:
/// <summary> /// 默認發消息服(fu)務 /// </summary> public class EmailMessageManager : IMessageManager { #region Delegate & Event /// <summary> /// 加入發送(song)隊(dui)列(lie)中 /// </summary> /// <param name="context"></param> public delegate void SendingEventHandler(MessageContext context); /// <summary> /// 發送(song)消(xiao)息 /// </summary> /// <param name="context"></param> public delegate void SentEventHandler(MessageContext context); /// <summary> /// 加入發(fa)送隊列中 /// </summary> public event SendingEventHandler Sending; /// <summary> /// 發(fa)送(song)消息 /// </summary> public event SentEventHandler Sent; void OnSending(MessageContext context) { if (Sending != null) Sending(context); } void OnSent(MessageContext context) { if (Sent != null) Sent(context); } #endregion #region IMessageManager 成員 public void Send(string recipient, MessageType type, string subject, string body) { Send(new List<string> { recipient }, type, subject, body); } public void Send(IEnumerable<string> recipients, MessageType type, string subject, string body) { Send(recipients, type, subject, body, false); } public void Send(IEnumerable<string> recipients, MessageType type, string subject, string body, bool isAsync) { if (recipients != null && recipients.Any()) { using (SmtpClient client = new SmtpClient() { Host = MessageManager.Instance.Host, Port = MessageManager.Instance.Port, Credentials = new NetworkCredential(MessageManager.Instance.UserName, MessageManager.Instance.Password), EnableSsl = false,//設置為(wei)true會出(chu)現"服務器不(bu)支持安全連接(jie)的(de)錯誤" DeliveryMethod = SmtpDeliveryMethod.Network, }) { #region Send Message var mail = new MailMessage { From = new MailAddress(MessageManager.Instance.Address, MessageManager.Instance.DisplayName), Subject = subject, Body = body, IsBodyHtml = true, }; MailAddressCollection mailAddressCollection = new MailAddressCollection(); recipients.ToList().ForEach(i => { mail.To.Add(i); }); if (isAsync) { client.SendCompleted += new SendCompletedEventHandler(client_SendCompleted); client.SendAsync(mail, recipients); } else { client.Send(mail); } #endregion } } } void client_SendCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e) { string arr = null; (e.UserState as List<string>).ToList().ForEach(i => { arr += i; }); //發送完成(cheng)后要做的事件,可能(neng)是寫日志 } #endregion }
OK,有了消(xiao)(xiao)息(xi)(xi)的行(xing)業(ye),我們再(zai)來看它的主(zhu)體,即消(xiao)(xiao)息(xi)(xi)體,一般由發(fa)送(song)(song)者,接受者集合,主(zhu)題,正文(wen),是否立即發(fa)送(song)(song)等(deng)幾(ji)個參數(shu)組成,下(xia)(xia)面來分享一下(xia)(xia):補充一句,如果消(xiao)(xiao)息(xi)(xi)體的實(shi)現是單一的,我們可以把行(xing)為寫在消(xiao)(xiao)息(xi)(xi)體里(li),形成一個消(xiao)(xiao)息(xi)(xi)上(shang)下(xia)(xia)文(wen),微軟很多架(jia)構都是這(zhe)樣做的,如EF數(shu)據上(shang)下(xia)(xia)文(wen)DbContext,ObjectContext,linq to sql上(shang)下(xia)(xia)文(wen)DataContext,Http上(shang)下(xia)(xia)文(wen)HttpContext等(deng)等(deng)。
/// <summary> /// 消息實體 /// </summary> public class MessageContext { /// <summary> /// 消息類型 /// </summary> public MessageType Type { get; set; } /// <summary> /// 消息頭(tou) /// </summary> public string Subject { get; set; } /// <summary> /// 消息正文 /// </summary> public string Body { get; set; } /// <summary> /// 接受方地址(zhi)列表(biao) /// </summary> public IEnumerable<string> Addresses { get; set; } /// <summary> /// 是否處于準(zhun)備發(fa)送狀態 /// </summary> public bool MessagePrepared { get; set; } public MessageContext() { Addresses = Enumerable.Empty<string>(); } }
有(you)了主體,再(zai)來看(kan)一下主要消(xiao)息的設置,我(wo)們可以使用config中的section節點(dian)來做這事(shi),如下:
/// <summary> /// Message塊(kuai),在web.config中提供Message塊(kuai)定義(yi) /// </summary> internal class MessageSection : ConfigurationSection { /// <summary> /// 賬號 /// </summary> [ConfigurationProperty("UserName", DefaultValue = "bfyxzls")] public string UserName { get { return (string)this["UserName"]; } set { this["UserName"] = value; } } /// <summary> /// 密碼 /// </summary> [ConfigurationProperty("Password", DefaultValue = "gemini123")] public string Password { get { return (string)this["Password"]; } set { this["Password"] = value; } } /// <summary> /// 郵件服務器地址 /// </summary> [ConfigurationProperty("Host", DefaultValue = "smtp.sina.com")] public string Host { get { return (string)this["Host"]; } set { this["Host"] = value; } } /// <summary> /// 端(duan)口號 /// </summary> [ConfigurationProperty("Port", DefaultValue = "25")] public int Port { get { return (int)this["Port"]; } set { this["Port"] = value; } } /// <summary> /// 發件人的email地址 /// </summary> [ConfigurationProperty("Address", DefaultValue = "bfyxzls@sina.com")] public string Address { get { return (string)this["Address"]; } set { this["Address"] = value; } } /// <summary> /// 發送(song)之后,在收件(jian)人(ren)端顯示的名稱 /// </summary> [ConfigurationProperty("DisplayName", DefaultValue = "占(zhan)占(zhan)")] public string DisplayName { get { return (string)this["DisplayName"]; } set { this["DisplayName"] = value; } } }
config中的配(pei)置信息(xi)如(ru)下:
<configuration>
<configSections>
<section name="MessageSection" type="Messaging.MessageSection"/>
</configSections>
<MessageSection UserName="853066980" Password="gemini123" Host="smtp.qq.com" Port="25" Address="853066980@qq.com" DisplayName="大占(zhan)"></MessageSection>
</configuration>
OK,我們再來看一下,消息的(de)(de)類型(xing),目前可以(yi)(yi)定義兩(liang)種消息,當(dang)然,為(wei)了組件的(de)(de)擴展性,你的(de)(de)類型(xing)可以(yi)(yi)通(tong)過IoC去代替(ti)它,這(zhe)樣可以(yi)(yi)避(bi)免程(cheng)序中的(de)(de)硬(ying)編碼,維護更方便
/// <summary> /// 消(xiao)息類型(xing) /// </summary> public enum MessageType { /// <summary> /// 電子郵件(jian) /// </summary> Email, /// <summary> /// 短(duan)信(xin)息(xi) /// </summary> ShortMessage, }
最后來看一下生(sheng)產消息的(de)對(dui)象(xiang),使用(yong)了簡單工廠模式(shi),當然這只(zhi)是個例(li)子(zi),實現(xian)中,它應該是使用(yong)IoC動態生(sheng)產的(de),呵(he)呵(he)。
/// <summary> /// 消息生產者 /// </summary> public class MessageFactory { /// <summary> /// 消息對象(xiang) /// </summary> public static IMessageManager Instance { get { MessageType messageType = MessageType.Email;//通過配置產生 switch (messageType) { case MessageType.Email: return new EmailMessageManager(); case MessageType.ShortMessage: throw new NotImplementedException("沒實現(xian)呢,呵呵"); default: throw new ArgumentException("您(nin)輸入的參數有(you)誤"); } } } }