WCF Service端Inspector

问题

  在使用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

原文地址:https://www.cnblogs.com/dereklovecc/p/5243117.html