前言

公司目前楼主负责的项目正在改版升级,对之前的服务也在作调整,项目里有个操作日志的模块,就决定把日志单独提取出来,做个日志服务,所以就有了这篇文章

正文

MSMQ作为消息队列,B/S项目调用日志服务,日志服务往消息队列发送消息,事件代理服务负责处理消息队列的消息,贴下核心代码

事件代理服务契约

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
using TestService.Contract.Faults; namespace TestService.Contract
{
/// <summary>
/// 事件代理
/// </summary>
[ServiceContract(Namespace = "http://TestService.Contract",
SessionMode = SessionMode.Required,
CallbackContract = typeof(IEventBrokerCallback))]
public interface IEventBroker
{
[OperationContract(IsOneWay = false)]
[FaultContract(typeof(EventBrokerException))]
void Subscribe(Guid subscriptionId, string[] eventNames); [OperationContract(IsOneWay = true)]
void EndSubscription(Guid subscriptionId);
}
}

事件代理服务回调处理契约

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
using TestService.Contract.Data; namespace TestService.Contract
{
/// <summary>
/// 事件代理回调
/// </summary>
public interface IEventBrokerCallback
{
[OperationContract(IsOneWay = true)]
void ReceiveStreamingResult(RealTimeEventMessage streamingResult);
}
}

事件代理服务异常实体

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text; namespace TestService.Contract.Faults
{
[DataContract]
public class EventBrokerException
{
[DataMember]
public string Message
{
get;
set;
} public EventBrokerException(string message)
{
Message = message;
}
}
}

消息处理实体

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text; namespace TestService.Contract.Data
{
/// <summary>
/// 数据实体
/// </summary>
[DataContract]
public class RealTimeEventMessage
{ public RealTimeEventMessage()
{ } public RealTimeEventMessage(MessageModel msg, string eventName, string entityIdType,
string description, string additionalData, DateTime date)
{
this.Msg = msg;
this.EventName = eventName;
this.EntityIdType = entityIdType;
this.Description = description;
this.AdditionalData = additionalData;
this.Date = date;
} [DataMember]
public MessageModel Msg { get; set; } [DataMember]
public string EventName { get; set; } [DataMember]
public string EntityIdType { get; set; } [DataMember]
public string Description { get; set; } [DataMember]
public string AdditionalData { get; set; } [DataMember]
public DateTime? Date { get; set; }
}
}

以上是事件代理服务的契约部分,下面看看实现,先看EventBroker的实现

using System;
using System.Collections.Generic;
using System.Linq;
using System.Messaging;
using System.ServiceModel;
using System.Threading.Tasks;
using TestService.ApplicationService.Data;
using TestService.ApplicationService.Services.Interface;
using TestService.Common.IoC;
using TestService.Contract;
using TestService.Contract.Data;
using TestService.Contract.Faults; namespace TestService.ApplicationService
{
[IocServiceBehavior]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class EventBroker : ApplicationBaseService, IEventBroker
{
Dictionary<string, List<UniqueCallbackHandle>> eventNameToCallbackLookups = new Dictionary<string, List<UniqueCallbackHandle>>();
private static Object syncObj = new Object();
private static string inputQueueName = "";
private bool shouldRun = true;
private static readonly TimeSpan queueReadTimeOut = TimeSpan.FromSeconds();
private static readonly TimeSpan queuePeekTimeOut = TimeSpan.FromSeconds();
private IXmlParserService _xmlParserService; public EventBroker(IXmlParserService xmlParserService)
{
inputQueueName = AppSettings.InputQueueName;
StartCollectingMessage();
_xmlParserService = xmlParserService;
} public void StartCollectingMessage()
{
try
{
GetMessageFromQueue();
}
catch (Exception ex)
{
throw new FaultException<EventBrokerException>(new EventBrokerException(ex.Message), new FaultReason(ex.Message));
}
} public void Subscribe(Guid subscriptionId, string[] eventNames)
{
try
{
CreateSubscription(subscriptionId, eventNames);
}
catch (Exception ex)
{
throw new FaultException<EventBrokerException>(new EventBrokerException(ex.Message), new FaultReason(ex.Message));
}
} public void EndSubscription(Guid subscriptionId)
{
lock (syncObj)
{
//create new dictionary that will be populated by those remaining
Dictionary<string, List<UniqueCallbackHandle>> remainingEventNameToCallbackLookups =
new Dictionary<string, List<UniqueCallbackHandle>>(); foreach (KeyValuePair<string, List<UniqueCallbackHandle>> kvp in eventNameToCallbackLookups)
{
//get all the remaining subscribers whos session id is not the same as the one we wish to remove
List<UniqueCallbackHandle> remainingMessageSubscriptions =
kvp.Value.Where(x => x.CallbackSessionId != subscriptionId).ToList();
if (remainingMessageSubscriptions.Any())
{
remainingEventNameToCallbackLookups.Add(kvp.Key, remainingMessageSubscriptions);
}
}
//now left with only the subscribers that are subscribed
eventNameToCallbackLookups = remainingEventNameToCallbackLookups;
}
} #region 私有方法 /// <summary>
/// 从消息队列中获取消息
/// </summary>
private void GetMessageFromQueue()
{
try
{
Task messageQueueReaderTask = Task.Factory.StartNew(() =>
{
using (MessageQueue queue = new MessageQueue(inputQueueName, QueueAccessMode.Receive))
{ queue.Formatter = new XmlMessageFormatter(new[] { typeof(string) }); while (shouldRun)
{
Message message = null; try
{
if (!queue.IsEmpty())
{
//Logs.Debug("接受队列里的消息");
message = queue.Receive(queueReadTimeOut);
ProcessMessage(message);
}
}
catch (MessageQueueException e)
{
if (e.MessageQueueErrorCode != MessageQueueErrorCode.IOTimeout)
{
Log.Warn("消息队列出现异常:", e);
}
}
catch (Exception e)
{
// Write the message details to the Error queue
Log.Warn("操作异常:", e);
}
}
}
}, TaskCreationOptions.LongRunning);
}
catch (AggregateException ex)
{
throw;
}
} /// <summary>
/// 处理消息
/// </summary>
/// <param name="msmqMessage"></param>
private void ProcessMessage(Message msmqMessage)
{
string messageBody = (string)msmqMessage.Body; #if DEBUG
Log.Info(string.Format("接受消息 : {0}", messageBody));
#endif RealTimeEventMessage messageToSendToSubscribers = _xmlParserService.ParseRawMsmqXml(messageBody);
if (messageToSendToSubscribers != null)
{
lock (syncObj)
{
if (messageToSendToSubscribers.Msg != null)
{
// 保存到数据库 } List<Guid> deadSubscribers = new List<Guid>(); if (eventNameToCallbackLookups.ContainsKey(messageToSendToSubscribers.EventName))
{
List<UniqueCallbackHandle> uniqueCallbackHandles =
eventNameToCallbackLookups[messageToSendToSubscribers.EventName];
foreach (UniqueCallbackHandle uniqueCallbackHandle in uniqueCallbackHandles)
{
try
{
uniqueCallbackHandle.Callback.ReceiveStreamingResult(messageToSendToSubscribers); }
catch (CommunicationObjectAbortedException coaex)
{
deadSubscribers.Add(uniqueCallbackHandle.CallbackSessionId);
}
}
} //end all subcriptions for dead subscribers
foreach (Guid deadSubscriberId in deadSubscribers)
{
EndSubscription(deadSubscriberId);
}
}
}
} private void CreateSubscription(Guid subscriptionId, string[] eventNames)
{
//Ensure that a subscription is created for each message type the subscriber wants to receive
lock (syncObj)
{
foreach (string eventName in eventNames)
{
if (!eventNameToCallbackLookups.ContainsKey(eventName))
{
List<UniqueCallbackHandle> currentCallbacks = new List<UniqueCallbackHandle>();
eventNameToCallbackLookups[eventName] = currentCallbacks;
}
eventNameToCallbackLookups[eventName].Add(
new UniqueCallbackHandle(subscriptionId, OperationContext.Current.GetCallbackChannel<IEventBrokerCallback>()));
}
}
} #endregion
}
}

事件代理实现里的回调处理实体

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TestService.Contract; namespace TestService.ApplicationService.Data
{
public class UniqueCallbackHandle
{
public UniqueCallbackHandle(Guid callbackSessionId, IEventBrokerCallback callback)
{
this.CallbackSessionId = callbackSessionId;
this.Callback = callback;
} public Guid CallbackSessionId { get; private set; } public IEventBrokerCallback Callback { get; private set; }
}
}

其中事件代理服务构造方法的AppSettings.InputQueueName是读取配置文件里的专用队列名称,这里有个构造方法,后面会使用Autofac进行注入,另外还有个IXmlParserService普通业务操作的,主要是解析消息队列里存放的xml消息

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TestService.Contract.Data; namespace TestService.ApplicationService.Services.Interface
{
public interface IXmlParserService
{
RealTimeEventMessage ParseRawMsmqXml(string messageBody);
}
}

实现

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using TestService.ApplicationService.Services.Interface;
using TestService.Contract.Data; namespace TestService.ApplicationService.Services.Impl
{
public class XmlParserService : IXmlParserService
{
private MessageModel msg; public XmlParserService(MessageModel msg)
{
this.msg = msg;
} public RealTimeEventMessage ParseRawMsmqXml(string messageBody)
{
try
{
RealTimeEventMessage info = new RealTimeEventMessage();
XElement xelement = XElement.Parse(messageBody);
MessageModel model = new MessageModel();
model.MessageId = GetSafeString(xelement, "messageId");
model.Content = GetSafeString(xelement, "content");
model.CreateTime = GetSafeDate(xelement, "createTime") ?? DateTime.Now; info.Msg = model;
info.EventName = GetSafeString(xelement, "eventName");
//info.EntityIdType = GetSafeString(xelement, "entityIdType");
//info.Description = GetSafeString(xelement, "description").Replace("\n\n", "\n\r");
//info.Date = GetSafeDate(xelement, "date");
//info.AdditionalData = GetSafeString(xelement, "additionalData");
return info; }
catch (Exception ex)
{
Log.Error("解析Xml消息出现异常:" + ex);
return null;
}
} public static Int32 GetSafeInt32(XElement root, string elementName)
{
try
{
XElement element = root.Elements().Where(node => node.Name.LocalName == elementName).Single();
return Convert.ToInt32(element.Value);
}
catch
{
return ;
}
} private static DateTime? GetSafeDate(XElement root, string elementName)
{
try
{
XElement element = root.Elements().Where(node => node.Name.LocalName == elementName).Single();
return DateTime.Parse(element.Value);
}
catch
{
return null;
}
} public static String GetSafeString(XElement root, string elementName)
{
try
{
XElement element = root.Elements().Where(node => node.Name.LocalName == elementName).Single();
return element.Value;
}
catch
{
return String.Empty;
}
} public static bool GetSafeBool(XElement root, string elementName)
{
try
{
XElement element = root.Elements().Where(node => node.Name.LocalName == elementName).Single();
return Convert.ToBoolean(element.Value);
}
catch
{
return false;
}
}
}
}

这里的xml节点主要是根据消息服务里发送消息的xml节点来定的,事件代理服务的就是上面的这么多,下面看看消息服务,

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
using TestService.Contract.Data; namespace TestService.Contract
{
[ServiceContract]
public interface IMessageService
{
[OperationContract]
bool Send(MessageModel model);
}
}

实现

using System;
using System.Collections.Generic;
using System.Linq;
using System.Messaging;
using System.Text;
using TestService.Common;
using TestService.Common.IoC;
using TestService.Contract;
using TestService.Contract.Data; namespace TestService.ApplicationService
{
/// <summary>
/// 消息服务
/// </summary>
[IocServiceBehavior]
public class MessageService : ApplicationBaseService, IMessageService
{
private static string inputQueueName = ""; public MessageService()
{
inputQueueName = AppSettings.InputQueueName;
} public bool Send(MessageModel model)
{
using (MessageQueue queue = new MessageQueue(inputQueueName, QueueAccessMode.Send))
{
queue.Formatter = new XmlMessageFormatter(new[] { typeof(string) });
try
{
Message message = new Message(
GetXmlData(model));
Log.Info(string.Format("发送消息: {0}", message.Body.ToString()));
queue.Send(message); return true;
}
catch (MessageQueueException mex)
{
if (mex.MessageQueueErrorCode != MessageQueueErrorCode.IOTimeout)
{
Log.Error(string.Format("Message queue exception occured", mex));
}
return false;
}
catch (Exception ex)
{
// Write the message details to the Error queue
Log.Error(ex);
return false;
}
}
} private string GetXmlData(MessageModel model)
{
StringBuilder sb = new StringBuilder("<realtimeEvent>");
sb.AppendFormat("<eventName>ClientDealEvent</eventName>");
sb.AppendFormat("<messageId>{0}</messageId>", model.MessageId);
sb.AppendFormat("<createTime>{0}</createTime>", model.CreateTime);
sb.AppendFormat("<content>{0}</content>", model.Content);
sb.AppendLine("</realtimeEvent>");
return sb.ToString();
}
}
}

消息服务比较简单,就是往消息队列里发送消息,细心的人会发现,我在每个服务实现里都加了个IocServiceBehavior特性,这个主要是标识了注入了该服务

核心代码就是上面介绍的那么多,有些操作没将代码贴出来,后面会将代码开源到码云上,今天就先写到这儿了

WCF基于MSMQ的事件代理服务的更多相关文章

  1. 基于MSMQ绑定的WCF服务实现总结

    一. 创建消息队列    1 1) 创建一个非事物性的私有队列    1 2)设置消息队列访问权限    2 二.创建WCF服务并绑定消息队列    4 1)创建HelloService服务    4 ...

  2. 基于A2DFramework的事件机制实现

    随笔- 102  文章- 3  评论- 476  发布订阅 - 基于A2DFramework的事件机制实现   SUMMARY 能做什么 DEMO 原理图 应用场景 能做什么 A2DFramework ...

  3. atitit.基于组件的事件为基础的编程模型--服务器端控件(1)---------服务器端控件和标签之间的关系

    atitit.基于组件的事件为基础的编程模型--服务器端控件(1)---------服务器端控件和标签之间的关系 1. server控件是要server了解了标签.种类型的server控件: 1 1. ...

  4. 重温.NET下Assembly的加载过程 ASP.NET Core Web API下事件驱动型架构的实现(三):基于RabbitMQ的事件总线

    重温.NET下Assembly的加载过程   最近在工作中牵涉到了.NET下的一个古老的问题:Assembly的加载过程.虽然网上有很多文章介绍这部分内容,很多文章也是很久以前就已经出现了,但阅读之后 ...

  5. 发布订阅 - 基于A2DFramework的事件机制实现

    SUMMARY 能做什么 DEMO 原理图 应用场景 能做什么 A2DFramework的事件机制是基于发布订阅模式改进得来的一套API,中间件部分实现了msmq.redis.Supersocket可 ...

  6. 跟我一起学WCF(1)——MSMQ消息队列

    一.引言 Windows Communication Foundation(WCF)是Microsoft为构建面向服务的应用程序而提供的统一编程模型,该服务模型提供了支持松散耦合和版本管理的序列化功能 ...

  7. 基于window.onerror事件 建立前端错误日志

    QA不是万能的,用户的浏览环境非常复杂,很多情况无法靠测试用例去覆盖,所以最好建立一个前端错误日志,在真实用户端收集bug. try&catch是一个捕获前端错误的常见方法,比如: { //给 ...

  8. C# 基于委托的事件

    事件基于多播委托的特性. 多播委托事实上就是一组类型安全的函数指针管理器,调用则执行顺序跳转到数组里所有的函数指针里执行. class Program { public class CarInfoEv ...

  9. WCF基于Cookie回传的系列(概述)

    1  WCF的基本知识(不作细述,园子里有很多的经典的文章系列) 2 WCF的执行过程 3 让服务通信像浏览器发送请求应答一样回传Cookie,并实现Cookie在不同的服务间共享 4  基于共享后的 ...

随机推荐

  1. mysql JDBC总结

    今天复习了下jdbc操作数据库,其实通过复习,感觉对类的熟悉和方法的运用都是小事,毕竟有API都可以查得到. 关键是一些设计, 1. 比如: Class.forName("");这 ...

  2. 【C++模版之旅】项目中一次活用C++模板(traits)的经历

    曾经曾在一个项目中碰到过一个挺简单的问题,但一时又不能用普通常规的方法去非常好的解决,最后通过C++模板的活用,通过traits相对照较巧妙的攻克了这个问题.本文主要想重现问题发生,若干解决方式的比較 ...

  3. Codeforces 135A-Replacement(思维)

    A. Replacement time limit per test 2 seconds memory limit per test 256 megabytes input standard inpu ...

  4. 变焦控制ZoomControls

    在安卓的webview中有这个点击放大缩小button,当时就在想如何实现那种效果,弄一个两个图标的ImageButton.但感觉又有些麻烦,昨天看疯狂安卓书.无意中发现另一个 ZoomButtons ...

  5. linux_无密登录

    使用下例中ssky-keygen和ssh-copy-id,仅需通过3个步骤的简单设置而无需输入密码就能登录远程Linux主机. ssh-keygen 创建公钥和密钥. ssh-copy-id 把本地主 ...

  6. javascript的语法作用域你真的懂了吗

    原文:javascript的语法作用域你真的懂了吗 有段时间没有更新了,思绪一下子有点转不过来.正应了一句古话“一天不读书,无人看得出:一周不读书,开始会爆粗:一月不读书,智商输给猪.”.再加上周五晚 ...

  7. CSharp设计模式读书笔记(13):代理模式(学习难度:★★★☆☆,使用频率:★★★★☆)

    代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问. 模式角色与结构: 示例代码: using System; using System.Collections.Generi ...

  8. 【百度地图API】你看过房产地图吗?你知道房产标注是如何建立的吗?

    原文:[百度地图API]你看过房产地图吗?你知道房产标注是如何建立的吗? 你是不是看过很多房产网站?例如安居客,新浪乐居. 你是不是也想做一个能写文字的标注? 你知道怎么去实现麼? 其实,上图这样的标 ...

  9. MapGuide应用程序演示样例——你好,MapGuide!

    图 3‑4显示了基于MapGuide的Web应用程序的开发流程,整个开发流程能够分为五个阶段.图中,矩形代表任务,椭圆形被任务使用的或被任务创建的实体,箭头代表数据流. 1) 载入文件类型的数据,配置 ...

  10. ckplayer

    ckplayer 的使用基本功能实现(一) 有个项目里用到视频播放功能,虽然是国产的插件,但我觉得做的还是不错,而且是免费使用,顺便支持下国内的一些项目(O(∩_∩)O~). 一.首先去官网下载 插件 ...