聚焦WCF行为的扩展

简介:

WCF以其灵活的可扩展架构为开发者提供了方便,其中对行为的扩展或许是应用中最为常见的。自 定义对行为的扩展并不复杂,但仍有许多细节需要注意。在服务端,一般是对DispatchRuntime和DispatchOperation进行扩展, 扩展点包括了对参数和消息的检查,以及操作调用程序,它们对应的接口分别为 IParameterInspector,IDispatchMessageInspector以及IOperationInvoker。而在客户端,则是对ClientRuntime和ClientOperation进行扩展,扩展点包括对参数和消息的检查,对应的接口分别为 IParameterInspector和IClientMessageInspector。这些接口类型均被定义在 System.ServiceModel.Dispatcher命名空间下,其中IParameterInspector接口可以同时作用在服务端和客户 端。

对这些接口的实现,有点类似于AOP的实现,可以对方法调用前和调用后注入一些额外的逻辑,所以通常会将这些扩展称为侦听器。例如IParameterInspector接口,就定义了如下方法:

void AfterCall( string operationName,  object[] outputs,  object returnValue,  object correlationState);       
object BeforeCall( string operationName,  object[] inputs);

在调用服务对象的目标方法前,会调用BeforeCall方法,而在调用后则会调用AfterCall方法。例如我们可在方法调用前检验计算方法的参数是否小于0,如果小于0则抛出异常:

public  class  CalculatorParameterInspector:IParameterInspector
{
     public  void BeforeCall( string operationName,  object[] inputs)
     {
         int x = inputs[0]  as  int;
         int y = inputs[1]  as  int;
         if (x <0 || y < 0)
         {
            throw  new FaultException( "The number can not be less than zero.");
         }
         return  null;
     }

     public  void AfterCall( string operationName,  object[] outputs,  object returnValue,  objectcorrelationState)
     {
         //empty;
     }
}

对消息的检查区分了服务端和客户端,接口方法根据消息传递的顺序刚好相反[注]。我们可以通过接口方法对消息进行处理,例如打印消息的Header:

public  class  PrintMessageInterceptor : IDispatchMessageInspector
{
     #region IDispatchMessageInspector Members

     public  object AfterReceiveRequest( ref System.ServiceModel.Channels.Message request, IClientChannel channel, InstanceContext instanceContext)
     {
        MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
        request = buffer.CreateMessage();

        Console.WriteLine( "After Receive Request:");
         foreach (MessageHeader header  in request.Headers)
         {
            Console.WriteLine(header);
         }
        Console.WriteLine( new  string( '*', 20));
         return  null;
     }

     public  void BeforeSendReply( ref System.ServiceModel.Channels.Message reply,  object correlationState)
     {
        MessageBuffer buffer = reply.CreateBufferedCopy(Int32.MaxValue);
        reply = buffer.CreateMessage();

        Console.WriteLine( "Before Send Request:");
         foreach (MessageHeader header  in reply.Headers)
         {
            Console.WriteLine(header);
         }
        Console.WriteLine( new  string( '*', 20));
     }

     #endregion
}

WCF提供了四种类型的行为:服务行为、终结点行为、契约行为和操作行为。 这四种行为分别定义了四个接口:IServiceBehavior,IEndpointBehavior,IContractBehavior以及 IOperationBehavior。虽然是四个不同的接口,但它们的接口方法却基本相同,分别为 AddBindingParameters(),ApplyClientBehavior()以及ApplyDispatchBehavior()。注 意,IServiceBehavior由于只能作用在服务端,因此并不包含ApplyClientBehavior()方法

我们可以定义自己的类实现这些接口,但需要注意几点:
1、行为的作用范围,可以用如下表格表示:

作用域

接口

影响范围

服务

终结点

契约

操作

服务

IServiceBehavior

Y

Y

Y

Y

终结点

IEndpointBehavior

 

Y

Y

Y

契约

IContractBehavior

 

 

Y

Y

操作

IOperationBehavior

 

 

 

Y

2、可以利用自定义特性的方式添加扩展的服务行为、契约行为和操作行为,但不能添加终结点行为;可以利用配置文件添加扩展服务行为和终结点行为,但不能添加契约行为和操作行为。但这些扩展的行为都可以通过ServiceDescription添加

利用特性添加行为,意味着我们在定义自己的扩展行为时,可以将其派生自Attribute类,然后以特性方式添加。例如:

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)]
public  class  MyServiceBehavior:Attribute, IServiceBehavior...

[MyServiceBehavior]
public  interface IService...

如果以配置文件的方式添加行为,则必须定义一个类继承自BehaviorExtensionElement(属 于命名空间System.ServiceModel.Configuration),然后重写属性BehaviorType以及 CreateBehavior()方法。BehaviorType属性返回的是扩展行为的类型,而CreateBehavior()方法则负责创建该扩展行为的对象实例:

public  class  MyBehaviorExtensionElement:BehaviorExtensionElement
{
     public MyBehaviorExtensionElement()  {  }
     public  override Type BehaviorType
     {
         get  {  return  typeof(MyServiceBehavior);  }
     }

     protected  override  object CreateBehavior()
     {
         return  new MyServiceBehavior();
     }
}

如果配置的Element添加了新的属性,则需要为新增的属性应用ConfigurationPropertyAttribute,例如:

[ConfigurationProperty("providerName",IsRequired = true)]
public  virtual  string ProviderName
{
     get
     {
         return  this[ "ProviderName"as  string;
     }
     set
     {
         this[ "ProviderName"] =  value;
     }
}

配置文件中的配置方法如下所示:

<configuration>
  <system.serviceModel>   
    <services>
      <service name= "MessageInspectorDemo.Calculator">
        <endpoint  behaviorConfiguration="messageInspectorBehavior"
                  address= "http://localhost:801/Calculator"                  
                  binding= "basicHttpBinding"
                  contract= "MessageInspectorDemo.ICalculator"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name= "messageInspectorBehavior">
          <myBehaviorExtensionElement  providerName= "Test"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <extensions>
      <behaviorExtensions>
        < add name= "myBehaviorExtensionElement"
              type="MessageInspectorDemo.MyBehaviorExtensionElement, MessageInspectorDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
      </behaviorExtensions>
    </extensions>
  </system.serviceModel>
</configuration>

注 意,在<serviceBehaviors>一节中,<behavior>下 的<myBehaviorExtensionElement>就是我们扩展的行为,providerName则是 MyBehaviorExtensionElement增加的属性。如果扩展了IEndpointBehavior,则配置节的名称 为<endpointBehaviors>。<extensions>节负责添加自定义行为的扩展。其 中,<add>中的name值与<behavior>下 的<myBehaviorExtensionElement>对应。

特别注意的是<extensions>下的 type值,必须是类型的full name。第一个逗点前的内容为完整的类型名(包括命名空间),第二部分为完整的命名空间。Version,Culture以及 PublicKeyToken也是缺一不可的。每个逗点后必须保留一个空格,否则无法正确添加扩展行为的配置。这与反射有关,但太容易让人忽略这一小细节。希望微软能在后来的版本中修订这个瑕疵。

3、在行为扩展的适当方法中,需要添加参数检查、消息检查或操作调用程序的扩展。这之间存在一定的对应关系。对于参数检查,我们需要在IOperationBehavior接口类型中的ApplyClientBehavior()以及ApplyDispatchBehavior()中添加。例如对于之前的CalculatorParameterInspector,我们可以定义一个类CalculatorParameterValidation:

public  class  CalculatorParameterValidation:Attribute, IOperationBehavior
{
         #region IOperationBehavior Members
         public  void AddBindingParameters(OperationDescription operationDescription,
            BindingParameterCollection bindingParameters)
         {
         }

         public  void ApplyClientBehavior(OperationDescription operationDescription,
            ClientOperation clientOperation)
         {
            CalculatorParameterInspector inspector =  new CalculatorParameterInspector();
            clientOperation.ParameterInspectors.Add(inspector);
         }

         public  void ApplyDispatchBehavior(OperationDescription operationDescription,
            DispatchOperation dispatchOperation)
         {
            CalculatorParameterInspector inspector =  new CalculatorParameterInspector();
            dispatchOperation.ParameterInspectors.Add(inspector);
         }

         public  void Validate(OperationDescription operationDescription)
         {
         }
         #endregion
}

如果检查器与扩展行为在职责上没有分离的必要,一个更好的方法是定义一个类同时实现IParameterInspector和IOperationBehavior接口,例如:

public  class  CalculatorParameterValidation:Attribute,  IParameterInspector, IOperationBehavior
{
         #region IParameterInspector Members
         public  void BeforeCall( string operationName,  object[] inputs)
         {
             int x = inputs[0]  as  int;
             int y = inputs[1]  as  int;
             if (x <0 || y < 0)
             {
                throw  new FaultException( "The number can not be less than zero.");
             }
             return  null;
         }

         public  void AfterCall( string operationName,  object[] outputs,  object returnValue,  object correlationState)
         {
             //empty;
         }
         #endregion


         #region IOperationBehavior Members
         public  void AddBindingParameters(OperationDescription operationDescription,
            BindingParameterCollection bindingParameters)
         {
         }

         public  void ApplyClientBehavior(OperationDescription operationDescription,
            ClientOperation clientOperation)
         {
            CalculatorParameterInspector inspector =  new CalculatorParameterInspector();
            clientOperation.ParameterInspectors.Add(this);
         }

         public  void ApplyDispatchBehavior(OperationDescription operationDescription,
            DispatchOperation dispatchOperation)
         {
            CalculatorParameterInspector inspector =  new CalculatorParameterInspector();
            dispatchOperation.ParameterInspectors.Add(this);
         }

         public  void Validate(OperationDescription operationDescription)
         {
         }
         #endregion
}
操作调用程序虽然通过IOperationBehavior进行关联,但确是通过DispatchOperation的Invoker属性。假定我们已经定义了一个实现IOperationInvoker的类MyOperationInvoker,则关联的方法为:
public  class  MyOperationInvokerBehavior : Attribute, IOperationBehavior
{
     #region IOperationBehavior Members
     public  void AddBindingParameters(OperationDescription operationDescription,
        BindingParameterCollection bindingParameters)
     {
     }
     public  void ApplyClientBehavior(OperationDescription operationDescription,
        ClientOperation clientOperation)
     {           
     }
     public  void ApplyDispatchBehavior(OperationDescription operationDescription,
        DispatchOperation dispatchOperation)
     {
        dispatchOperation.Invoker =  new MyOperationInvoker(dispatchOperation.Invoker);
     }
     public  void Validate(OperationDescription operationDescription)
     {
     }
     #endregion
}

 

至 于对Dispatch的消息检查,则可以通过IServiceBehavior,IEndpointBehavior或 IContractBehavior中DispatchRuntime的MessageInspectors添加;而对Client的消息检查则可以通过 IEndpointBehavior或IContractBehavior中ClientRuntime的MessageInspectors添加(注 意,此时与IServiceBehavior无关,因为它不会作用于客户端代理)。例如:

public  class  PrintMessageInspectorBehavior : IDispatchMessageInspector,IEndpointBehavior
     {
         //略去IDispatchMessageInspector接口成员的实现;

         #region IEndpointBehavior Members
         public  void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
         {
             //empty;
         }
         public  void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
         {
              clientRuntime. MessageInspectors.Add(this);
         }
         public  void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
         {
            endpointDispatcher. DispatchRuntime.MessageInspectors.Add(this);
         }
         public  void Validate(ServiceEndpoint endpoint)
         {
             //empty;
         }
         #endregion
     }

如果实现的是IServiceBehavior接口,则需要遍历ApplyDispatchBehavior()方法中的ServiceHostBase对象:

public  void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
    foreach (ChannelDispatcher channelDispatcher  in serviceHostBase.ChannelDispatchers)
    {
       foreach (EndpointDispatcher endpointDispatcher  in channelDispatcher .Endpoints)
       {
          endpointDispatcher.DispatchRuntime.MessageInspectors.Add( this);
       }
    }
}

注:IDispatchMessageInspector 接口的方法为BeforeSendReply()和AfterReceiveRequest();而IClientMessageInspector接口 的方法则为BeforeSendRequest()和AfterReceiveReply()。








本文转自wayfarer51CTO博客,原文链接:http://blog.51cto.com/wayfarer/280087,如需转载请自行联系原作者

相关文章
一起谈.NET技术,基于CallContextInitializer的WCF扩展导致的严重问题
  WCF是一个具有极高扩展度的分布式通信框架,无论是在信道层(Channel Layer)还是服务模型层(Service Model),我们都可以自定义相关组件通过相应的扩展注入到WCF运行环境中。在WCF众多可扩展点中,ICallContextInitializer可以帮助我们在服务操作执行前后完成一些额外的功能,这实际上就是一种AOP的实现方式。
852 0
|
数据库 开发框架 数据安全/隐私保护