之所以把发消息拿出来,完全是因为微软的orchard项目,在这个项目里,将公用的与领域无关的功能模块进行抽象,形成了一个个的组件,这些组件通过引用和注入的方式进行工作,感觉对于应用程序的扩展性上有很大的提高,消息组件的提出是因为它的不固定性,从小方面说,项目模块的发消息的方式可能是不同的,有过模块是email,有的是数据库,有的是短信;而从大的方面说,对于项目与项目来说,它们发消息的方式也可能不同,所以,把它抽象出来,就显得很必要了。
对于一个消息来说,它的行为很固定,即发消息,Send,而考虑到网络阻塞问题,我们也同样提供了异常消息的发送,接口规范如下:
/// <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); }
有了规范,就是实现这个规范,我们以Email为例,看一下 Email消息发送的实现代码:
/// <summary> /// 默认发消息服务 /// </summary> public class EmailMessageManager : IMessageManager { #region Delegate & Event /// <summary> /// 加入发送队列中 /// </summary> /// <param name="context"></param> public delegate void SendingEventHandler(MessageContext context); /// <summary> /// 发送消息 /// </summary> /// <param name="context"></param> public delegate void SentEventHandler(MessageContext context); /// <summary> /// 加入发送队列中 /// </summary> public event SendingEventHandler Sending; /// <summary> /// 发送消息 /// </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,//设置为true会出现"服务器不支持安全连接的错误" 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; }); //发送完成后要做的事件,可能是写日志 } #endregion }
OK,有了消息的行业,我们再来看它的主体,即消息体,一般由发送者,接受者集合,主题,正文,是否立即发送等几个参数组成,下面来分享一下:补充一句,如果消息体的实现是单一的,我们可以把行为写在消息体里,形成一个消息上下文,微软很多架构都是这样做的,如EF数据上下文DbContext,ObjectContext,linq to sql上下文DataContext,Http上下文HttpContext等等。
/// <summary> /// 消息实体 /// </summary> public class MessageContext { /// <summary> /// 消息类型 /// </summary> public MessageType Type { get; set; } /// <summary> /// 消息头 /// </summary> public string Subject { get; set; } /// <summary> /// 消息正文 /// </summary> public string Body { get; set; } /// <summary> /// 接受方地址列表 /// </summary> public IEnumerable<string> Addresses { get; set; } /// <summary> /// 是否处于准备发送状态 /// </summary> public bool MessagePrepared { get; set; } public MessageContext() { Addresses = Enumerable.Empty<string>(); } }
有了主体,再来看一下主要消息的设置,我们可以使用config中的section节点来做这事,如下:
/// <summary> /// Message块,在web.config中提供Message块定义 /// </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> /// 端口号 /// </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> /// 发送之后,在收件人端显示的名称 /// </summary> [ConfigurationProperty("DisplayName", DefaultValue = "占占")] public string DisplayName { get { return (string)this["DisplayName"]; } set { this["DisplayName"] = value; } } }
config中的配置信息如下:
<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="大占"></MessageSection> </configuration>
OK,我们再来看一下,消息的类型,目前可以定义两种消息,当然,为了组件的扩展性,你的类型可以通过IoC去代替它,这样可以避免程序中的硬编码,维护更方便
/// <summary> /// 消息类型 /// </summary> public enum MessageType { /// <summary> /// 电子邮件 /// </summary> Email, /// <summary> /// 短信息 /// </summary> ShortMessage, }
最后来看一下生产消息的对象,使用了简单工厂模式,当然这只是个例子,实现中,它应该是使用IoC动态生产的,呵呵。
/// <summary> /// 消息生产者 /// </summary> public class MessageFactory { /// <summary> /// 消息对象 /// </summary> public static IMessageManager Instance { get { MessageType messageType = MessageType.Email;//通过配置产生 switch (messageType) { case MessageType.Email: return new EmailMessageManager(); case MessageType.ShortMessage: throw new NotImplementedException("没实现呢,呵呵"); default: throw new ArgumentException("您输入的参数有误"); } } } }
本文转自博客园张占岭(仓储大叔)的博客,原文链接:我心中的核心组件(可插拔的AOP)~第五回 消息组件,如需转载请自行联系原博主。