我心中的核心组件(可插拔的AOP)~第五回 消息组件

时间:2021-07-25 17:21:16

回到目录

之所以把发消息拿出来,完全是因为微软的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 = "")]
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="" Password="gemini123" Host="smtp.qq.com" Port="" 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("您输入的参数有误");
}
}
}
}

回到目录