使用BehaviorExtension对WCF功能进行扩展(一)——对接口调用进行参数检查和日志记录

微软家的东西大多是基于消息(Message)和上下文(Context)实现的。所以总会有一个Manager的调度器存在负责传递消息和上下文。本文主要目的是通过编程实现获取接口调用时的参数,在前文中是通过 RequestContext 实现的,在这里则对WCF功能进行扩展。

参考文档:

扩展WCF

最重要的架构图

这个架构图非常清晰的描述了这个 rpc 消息在 WCF 内部是怎么被调度的。(然而实际上我在写扩展的时候还是靠看 .net 源代码的

项目代码Demo:https://github.com/HDRorz/WcfServiceDemo

翻阅了 System.Servicemodel.Dispatcher 命名空间下大部分东西,发现 IOperationInvoker 是真正接口方法调用前最后一个调度器,是符合我们需求的。查看源代码,IOperationInvoker 是 DispatchOperation 的子属性,DispatchOperation 是DispatchRuntime 的子属性和 IOperationBehavior 的 ApplyDispatchBehavior 方法参数。按照使用行为配置和扩展运行时上的描述。只有 IServiceBehavior 和 IEndpointBehavior 可以在配置文件中应用扩展。(不然可以继承整个 ServiceHostBase,那就是重写WCF了)

经过实验,发现 IEndpointBehavior 的 ApplyDispatchBehavior 方法可以通过参数 endpointDispatcher 获取到 DispatchRuntime。那么我就这样实现了一个 VedaEndpointExtension 和 SyncNadleehOperationInvoker。赋值 DispatchRuntime.Operations 的 Invoker 属性。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace WcfExtensions
{

    /// <summary>
    /// 吠陀终结点扩展
    /// </summary>
    public class VedaEndpointBehavior : IEndpointBehavior
    {

        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {

        }

        /// <summary>
        /// 只有在客户端时才会被调用
        /// </summary>
        /// <param name="endpoint"></param>
        /// <param name="clientRuntime"></param>
        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {

        }

        /// <summary>
        /// 增加调度器扩展
        /// </summary>
        /// <param name="endpoint"></param>
        /// <param name="endpointDispatcher"></param>
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            endpointDispatcher.DispatchRuntime.ChannelDispatcher.ErrorHandlers.Add(new ExiaErrorHandler());

            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new DynamesMessageInspector());

            foreach (var operation in endpointDispatcher.DispatchRuntime.Operations)
            {
                //原为null。这里设置没有效果,因为实际调用时并没有使用这里的设置
                operation.Invoker = new SyncNadleehOperationInvoker(endpoint.Contract.ContractType, operation.Name);
                operation.ParameterInspectors.Add(new AstraeaParameterInspector());
            }
        }

        public void Validate(ServiceEndpoint endpoint)
        {

        }
    }
}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.ServiceModel.Dispatcher;
using System.Text;
using WcfExtensions.Util;

namespace WcfExtensions
{
    /// <summary>
    /// 娜德雷同步方法调用者
    /// 扩展软爹内部的SyncMethodInvoker
    /// 在方法调用前后插入自定义的操作
    /// </summary>
    public class SyncNadleehOperationInvoker : IOperationInvoker
    {

        private IOperationInvoker innerInvoker = null;

        private MethodInfo method;
        private string methodName;
        private int inputParameterCount;
        private int outputParameterCount;
        private Type type;

        public SyncNadleehOperationInvoker(Type type, string methodName)
        {
            this.type = type;
            this.methodName = methodName;
            this.method = this.type.GetMethod(this.methodName);
            GetSyncMethodInvoker(type, methodName);

            inputParameterCount = method.GetParameters().Count(e => e.IsIn);
            outputParameterCount = method.GetParameters().Count(e => e.IsOut);

            BeforerInvokerHandles.Add(BeforeMethodInvoke.SetStopWatch);
            BeforerInvokerHandles.Add(BeforeMethodInvoke.TokenValid);

            AfterInvokerHandles.Add(AfterMethodInvoke.OperatingLog);
        }

        public bool IsSynchronous
        {
            get
            {
                return true;
            }
        }

        public MethodInfo Method
        {
            get
            {
                return this.method;
            }
        }
        
        public string MethodName
        {
            get
            {
                if (this.methodName == null)
                {
                    this.methodName = this.method.Name;
                }
                return this.methodName;
            }
        }

        /// <summary>
        /// 方法调用前的自定义操作列表
        /// </summary>
        private List<BeforeMethodInvokeDelegate> BeforerInvokerHandles = new List<BeforeMethodInvokeDelegate>();

        /// <summary>
        /// 方法调用后的自定义操作列表
        /// </summary>
        private List<AfterMethodInvokeDelegate> AfterInvokerHandles = new List<AfterMethodInvokeDelegate>();

        /// <summary>
        /// 获取软爹内部的SyncMethodInvoker
        /// </summary>
        /// <param name="type"></param>
        /// <param name="methodName"></param>
        private void GetSyncMethodInvoker(Type type, string methodName)
        {
            Type innerInvokerType = typeof(DispatchRuntime).Assembly.GetType("System.ServiceModel.Dispatcher.SyncMethodInvoker");
            var constructorMethod = innerInvokerType.GetConstructor(new Type[2] { typeof(Type), typeof(String) });
            innerInvoker = (IOperationInvoker)constructorMethod.Invoke(new object[2] { type, methodName });
        }

        public object[] AllocateInputs()
        {
            return innerInvoker.AllocateInputs();
        }

        /// <summary>
        /// 在方法调用前后插入自定义的操作
        /// </summary>
        /// <param name="instance"></param>
        /// <param name="inputs"></param>
        /// <param name="outputs"></param>
        /// <returns></returns>
        public object Invoke(object instance, object[] inputs, out object[] outputs)
        {
            bool ifBreak = false;
            object ret = null;
            ConcurrentDictionary<string, object> invokeContext = new ConcurrentDictionary<string, object>();
            foreach (var beforeInvoke in BeforerInvokerHandles)
            {
                var temp = beforeInvoke.Invoke(invokeContext, method, instance, inputs, out ifBreak);
                if (ifBreak)
                {
                    ret = temp;
                    break;
                }
            }

            if (!ifBreak)
            {
                ret = innerInvoker.Invoke(instance, inputs, out outputs); ;
            }
            else
            {
                outputs = new object[outputParameterCount];
            }

            foreach (var afterInvoke in AfterInvokerHandles)
            {
                afterInvoke.Invoke(invokeContext, method, instance, inputs, outputs, ret);
            }

            return ret;
        }

        public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
        {
            throw new NotImplementedException();
        }

        public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
        {
            throw new NotImplementedException();
        }
    }
}

然后新增扩展配置节点 VedaEndpointBehaviorExtensionElement,并加到配置文件中。

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Configuration;
using System.Text;

namespace WcfExtensions.Element
{
    public class VedaEndpointBehaviorExtensionElement : BehaviorExtensionElement
    {
        public override Type BehaviorType
        {
            get { return typeof(VedaEndpointBehavior); }
        }

        protected override object CreateBehavior()
        {
            return new VedaEndpointBehavior();
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<configuration>

  <system.serviceModel>
    <services>
      <service name="WcfServiceLibrary1.Service1">
        <endpoint address="" behaviorConfiguration="restfulBehavior" binding="webHttpBinding" bindingConfiguration="" contract="WcfServiceLibrary1.IService1">
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8733/Service1/"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <endpointBehaviors>
        <behavior name="restfulBehavior">
          <webHttp/>
          <WcfExtension/>
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <extensions>
      <behaviorExtensions>
        <add name="WcfExtension" type="WcfExtensions.Element.VedaEndpointBehaviorExtensionElement, WcfExtensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
      </behaviorExtensions>
    </extensions>
  </system.serviceModel>
  
</configuration>

然而这样一通操作后,测试时发现这个 Invoker 没有被调用,查了源代码才发现这个 DispatchRuntime.Operations.Invoker 没有其他引用,也难怪在赋值前是null(妈的,微软爸爸真是坑爹)。

只能通过实现 IOperationBehavior 来操作这个Invoker。然而 IOperationBehavior 并不能通过配置来实现,查看源代码发现 IOperationBehavior 是 OperationDescription 的 子属性,在 IEndpointBehavior 的 ApplyDispatchBehavior 的 endpoint 参数可以得到 OperationDescription 的引用。那么实现就变成这样了。

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Text;

namespace WcfExtensions
{

    /// <summary>
    /// 吠陀终结点扩展
    /// </summary>
    public class VedaEndpointBehavior : IEndpointBehavior
    {

        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {

        }

        /// <summary>
        /// 只有在客户端时才会被调用
        /// </summary>
        /// <param name="endpoint"></param>
        /// <param name="clientRuntime"></param>
        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {

        }

        /// <summary>
        /// 增加调度器扩展
        /// </summary>
        /// <param name="endpoint"></param>
        /// <param name="endpointDispatcher"></param>
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            endpointDispatcher.DispatchRuntime.ChannelDispatcher.ErrorHandlers.Add(new ExiaErrorHandler());

            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new DynamesMessageInspector());

            foreach (var operation in endpointDispatcher.DispatchRuntime.Operations)
            {
                operation.ParameterInspectors.Add(new AstraeaParameterInspector());
            }

            foreach (var operation in endpoint.Contract.Operations)
            {
                operation.Behaviors.Add(new VirtueOperationBehavior());
            }
        }

        public void Validate(ServiceEndpoint endpoint)
        {

        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Text;

namespace WcfExtensions
{
    /// <summary>
    /// 德天使方法扩展
    /// </summary>
    public class VirtueOperationBehavior : IOperationBehavior
    {
        public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
        {

        }

        public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
        {

        }

        /// <summary>
        /// 增加方法扩展
        /// </summary>
        /// <param name="operationDescription"></param>
        /// <param name="dispatchOperation"></param>
        public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
        {
            dispatchOperation.Invoker = new SyncNadleehOperationInvoker(operationDescription.SyncMethod);
        }

        public void Validate(OperationDescription operationDescription)
        {

        }
    }
}
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.ServiceModel.Dispatcher;
using System.Text;
using WcfExtensions.Util;

namespace WcfExtensions
{
    /// <summary>
    /// 娜德雷同步方法调用者
    /// 扩展软爹内部的SyncMethodInvoker
    /// 在方法调用前后插入自定义的操作
    /// </summary>
    public class SyncNadleehOperationInvoker : IOperationInvoker
    {

        private IOperationInvoker innerInvoker = null;

        private MethodInfo method;
        private string methodName;
        private int inputParameterCount;
        private int outputParameterCount;
        private Type type;

        public SyncNadleehOperationInvoker(IOperationInvoker invoker, MethodInfo method)
        {
            this.method = method;
            innerInvoker = invoker;

            inputParameterCount = method.GetParameters().Count(e => e.IsIn);
            outputParameterCount = method.GetParameters().Count(e => e.IsOut);

            BeforerInvokerHandles.Add(BeforeMethodInvoke.SetStopWatch);
            BeforerInvokerHandles.Add(BeforeMethodInvoke.TokenValid);

            AfterInvokerHandles.Add(AfterMethodInvoke.OperatingLog);
        }

        public bool IsSynchronous
        {
            get
            {
                return true;
            }
        }

        public MethodInfo Method
        {
            get
            {
                return this.method;
            }
        }
        
        public string MethodName
        {
            get
            {
                if (this.methodName == null)
                {
                    this.methodName = this.method.Name;
                }
                return this.methodName;
            }
        }

        /// <summary>
        /// 方法调用前的自定义操作列表
        /// </summary>
        private List<BeforeMethodInvokeDelegate> BeforerInvokerHandles = new List<BeforeMethodInvokeDelegate>();

        /// <summary>
        /// 方法调用后的自定义操作列表
        /// </summary>
        private List<AfterMethodInvokeDelegate> AfterInvokerHandles = new List<AfterMethodInvokeDelegate>();

        public object[] AllocateInputs()
        {
            return innerInvoker.AllocateInputs();
        }

        /// <summary>
        /// 在方法调用前后插入自定义的操作
        /// </summary>
        /// <param name="instance"></param>
        /// <param name="inputs"></param>
        /// <param name="outputs"></param>
        /// <returns></returns>
        public object Invoke(object instance, object[] inputs, out object[] outputs)
        {
            bool ifBreak = false;
            object ret = null;
            ConcurrentDictionary<string, object> invokeContext = new ConcurrentDictionary<string, object>();
            foreach (var beforeInvoke in BeforerInvokerHandles)
            {
                var temp = beforeInvoke.Invoke(invokeContext, method, instance, inputs, out ifBreak);
                if (ifBreak)
                {
                    ret = temp;
                    break;
                }
            }

            if (!ifBreak)
            {
                ret = innerInvoker.Invoke(instance, inputs, out outputs); ;
            }
            else
            {
                outputs = new object[outputParameterCount];
            }

            foreach (var afterInvoke in AfterInvokerHandles)
            {
                afterInvoke.Invoke(invokeContext, method, instance, inputs, outputs, ret);
            }

            return ret;
        }

        public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
        {
            return innerInvoker.InvokeBegin(instance, inputs, callback, state);
        }

        public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
        {
            return innerInvoker.InvokeEnd(instance, out outputs, result);
        }
    }
}

这样我们就成功侵入到接口方法调度器内部了,我在调度器中增加了 BeforerInvokerHandles 和 AfterInvokerHandles 列表用于在方法调用前后运行其他自定义函数,我在我的demo里增加了token校验和接口日志功能。

 

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注