问题
在使用WCF的过程中,有时候需要在service端截取client和service之间的消息来做一些如写log,检查message是否合法的操作。 那么如何才能实现呢?
解决方案
使用WCF提供的Inspector功能。我们可以通过实现WCF提供的IParameterInspector或者IDispatchMessageInspector 接口来实现上述需求。以下是需要实现步骤:
1. 实现IParameterInspector或者IDispatchMessageInspector接口
2. 实现IServiceBehavior/IEndpointBehavior/IOperationBehavior接口并把步骤1中实现的Inspector加入到WCF的Message dispatch runtime中
3. 通过Attribute或者配置文件把步骤2中的Behavior应用到WCF service上
接下来我们看看每一步如何实践:
- 步骤一 --- 实现IParameterInspector或者IDispatchMessageInspector接口
实现IParameterInspector的类
public class LogParameterInspector : IParameterInspector { public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState) { } public object BeforeCall(string operationName, object[] inputs) { var cinfo = new LogInfo(); cinfo.Action = operationName; cinfo.StartTimeStamp = DateTime.Now; cinfo.ServiceName = OperationContext.Current.InstanceContext.GetServiceInstance().GetType().Name; return cinfo; } }
实现IDispatchMessageInspector的类
{ public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) { LogInfo cinfo = new LogInfo(); //Collect any info that passed in the headers from Client, if any. cinfo.ServerTimeStamp = DateTime.Now; //Timestamp after the call is received. cinfo.Platform = "WCF"; OperationDescription operationDesc = GetOperationDescription(OperationContext.Current); if (operationDesc != null) { Type contractType = operationDesc.DeclaringContract.ContractType; cinfo.Action = operationDesc.Name; cinfo.ServiceName = contractType.FullName; cinfo.AssemblyName = contractType.Assembly.GetName().Name; } cinfo.ServerName = Dns.GetHostName(); cinfo.ServerProcessName = System.AppDomain.CurrentDomain.FriendlyName; return cinfo; } public void BeforeSendReply(ref Message reply, object correlationState) { //correlationState is of type ClientInformation and it is set in AfterReceiveRequest event. if (correlationState != null) { LogInfo cinfo = correlationState as LogInfo; if (cinfo != null) { cinfo.EndTimeStamp = DateTime.Now; //It's okay to read the RequestMessage since the operation //has been completed and message is serialized from stream //(....stream...). //RequestMessage on OperationContext is short lived. //It would be too late to serialize the RequestMessage //in another thread (exception raised: Message is closed) cinfo.Request = OperationContext.Current.RequestContext.RequestMessage.ToString(); //Message can be read only once. //Create a BufferedCopy of the Message and be sure to set //the original message set to a value wich has not been //copied nor read. MessageBuffer mb = reply.CreateBufferedCopy(int.MaxValue); Message responseMsg = mb.CreateMessage(); reply = mb.CreateMessage(); var reader = responseMsg.GetReaderAtBodyContents(); var xodc = new XmlDocument(); xodc.LoadXml(reader.ReadOuterXml()); var oo = reader.ReadContentAsObject(); cinfo.Response = responseMsg.ToString(); if (reply.IsFault == true) { cinfo.IsError = true; } //Log cinfo async here; var serialzer = new XmlSerializer(cinfo.GetType()); var writer = new StringWriter(); serialzer.Serialize(writer, cinfo); File.WriteAllText(@"c:log.xml", writer.ToString()); } } } private OperationDescription GetOperationDescription(OperationContext operationContext) { OperationDescription od = null; string bindingName = operationContext.EndpointDispatcher.ChannelDispatcher.BindingName; string methodName; if (bindingName.Contains("WebHttpBinding")) { //REST request methodName = (string)operationContext.IncomingMessageProperties["HttpOperationName"]; } else { //SOAP request string action = operationContext.IncomingMessageHeaders.Action; methodName = operationContext.EndpointDispatcher.DispatchRuntime.Operations.FirstOrDefault(o => o.Action == action).Name; } EndpointAddress epa = operationContext.EndpointDispatcher.EndpointAddress; ServiceDescription hostDesc = operationContext.Host.Description; ServiceEndpoint ep = hostDesc.Endpoints.Find(epa.Uri); if (ep != null) { od = ep.Contract.Operations.Find(methodName); } return od; } }
- 步骤二 --- 实现IServiceBehavior或者IEndpointBehavior或者IOperationBehavior接口中的一个,以下以实现IServiceBehavior接口为例
实现IServiceBehavior的类
public class ServiceBehavior : Attribute, IServiceBehavior { public ServiceBehavior() { } public void AddBindingParameters( ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { } public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers) { ChannelDispatcher cd = cdb as ChannelDispatcher; if (cd != null) { foreach (EndpointDispatcher ed in cd.Endpoints) { if (!ed.DispatchRuntime.MessageInspectors.Any(inspector => inspector is LogDispatchMessageInspector)) { ed.DispatchRuntime.MessageInspectors.Add(new LogDispatchMessageInspector()); } foreach (DispatchOperation op in ed.DispatchRuntime.Operations) { if (!op.ParameterInspectors.Any(inspector => inspector is LogParameterInspector)) { op.ParameterInspectors.Add(new LogParameterInspector()); } } } } } } public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase) { } }
- 步骤三 --- 通过Attribute或者配置文件把步骤2中的Behavior应用到WCF service上
我们有如下两种方式把步骤二中实现的Behavior应用到具体的Service上:
1) 让Behavior类继承Attribute,然后把Behavior作为Attribute应用到具体的Service上,如前所示,步骤二中的Behavior已经继承Attribute了,
所以我们可以像下面这样把它应用到具体的service上:
[LogInspector.ServiceBehavior] public class Service1 : IService1
2) 通过配置文件
编写ServiceBehaviorExtensionElement并继承自BehaviorExtensionElement class,代码如下:
public class ServiceBehaviorExtensionElement : BehaviorExtensionElement { protected override object CreateBehavior() { return new ServiceBehavior(); } public override Type BehaviorType { get { return typeof(ServiceBehavior); } } }
然后在web.config文件中做如下配置:
<system.serviceModel> <extensions> <behaviorExtensions> <add name="LogInspectorExtension" type="LogInspector.ServiceBehaviorExtensionElement, LogInspector"/> </behaviorExtensions> </extensions> <behaviors> <serviceBehaviors> <behavior name="LogInpsectorBehavior"> <LogInspectorExtension></LogInspectorExtension> <!-- To avoid disclosing metadata information, set the values below to false before deployment --> <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/> <!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information --> <serviceDebug includeExceptionDetailInFaults="false"/> </behavior> </serviceBehaviors> </behaviors> <services> <service name="LogService.Service1" behaviorConfiguration="LogInpsectorBehavior"> <endpoint address="" contract="LogService.IService1" binding="wsHttpBinding"></endpoint> </service> </services> <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" /> </system.serviceModel>
本文代码下载地址:https://github.com/DerekLoveCC/WCF.git
https://blogs.msdn.microsoft.com/carlosfigueira/2011/04/18/wcf-extensibility-message-inspectors/
https://code.msdn.microsoft.com/Generic-WCF-Message-bdf4fb1f