65.9K
CodeProject 正在变化。 阅读更多。
Home

关注 WCF 行为的扩展

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (15投票s)

2009 年 6 月 10 日

CPOL

5分钟阅读

viewsIcon

78960

WCF 为开发者提供了一个灵活且可扩展的架构。最常见的情况是自定义行为的扩展。这并不复杂,但需要注意一些问题。本文讨论了如何在 WCF 中扩展行为。

引言

WCF 为开发者提供了一个灵活且可扩展的架构。最常见的情况是自定义行为的扩展。这并不复杂,但需要注意一些问题。本文讨论了如何在 WCF 中扩展行为。

在服务端,如果我们想扩展行为,我们需要扩展 DispatchRuntimeDispatchOperation。扩展点包括检查参数、消息和操作的调用者。相应的接口是 IParameterInspector(用于检查参数)、IDispatchMessageInspector(用于检查消息)和 IOperationInvoker(用于调用操作)。在客户端,我们应该扩展 ClientRuntimeClientOperation,扩展点包括检查参数和消息。相应的接口是 IParameterInspectorIClientMessageInspector。所有接口都位于 System.ServiceModel.Dispatcher 命名空间中。请注意,**IParameterInspector 可应用于服务和客户端**。

实现这些接口似乎是在实现 AOP(面向切面编程)。我们可以注入一些额外的逻辑来调用相关方法之前和之后,因此我们称这些扩展为“侦听器”。例如,IParameterInspector 接口中有一些方法,如下所示:

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

object BeforeCall(string operationName, object[] inputs);

BeforeCall() 方法将在调用服务对象的目标方法之前被调用,而 AfterCall() 方法将在目标方法被调用之后发生。例如,我们可以在调用方法之前验证参数值是否小于零。如果小于零,则会抛出异常。

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,
        object correlationState)
    {
        //empty;
    }
}

在检查参数时,服务和客户端之间存在区别,接口的方法顺序与消息传递的顺序非常相反(**注意**:IDispatchMessageInspector 接口包含 BeforeSendReply()AfterReceiveRequest();而 IClientMessageInspector 接口包含 BeforeSendRequest()AfterReceiveReply())。我们可以通过此接口的方法来处理消息,例如打印消息头。

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
}

有四种不同类型的行为,包括**服务行为**、**终结点行为**、**契约行为**和**操作行为**。它们对应的接口是 IServiceBehaviorIEndpointBehaviorIContractBehaviorIOperationBehavior。尽管它们在本质上是不同的接口,但它们的方法几乎相似,包括:AddBindingParameters()ApplyClientBehavior()ApplyDispatchBehavior()

注意:由于 IServiceBehavior 仅在服务端使用,因此它没有 ApplyClientBehavior() 方法。

我们可以自定义类来实现这些接口,但需要强调一些关键要素。

  1. 行为的范围。表 1 描述了所有情况。

     

    行为类型

    接口

    范围

       

    Service

    端点

    契约

    操作 (Operation)

    Service

    IServiceBehavior

    Y

    Y

    Y

    Y

    端点

    IEndpointBehavior

     

    Y

    Y

    Y

    契约

    IContractBehavior

     

     

    Y

    Y

    操作 (Operation)

    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();
    }
}

如果需要配置的元素添加了新属性,我们必须在该新属性上应用 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=“https://: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>

请注意粗体内容。<myBehaviorExtensionElement> 是我们的扩展行为,而 providerNameMyBehaviorExtensionElement 的新属性。如果您扩展了 IEndpointBehavior,则应将 <serviceBehaviors> 部分替换为 <endpointBehaviors>。自定义行为的扩展将放在 <extensions></extensions> 部分。name 属性的值必须与 <behavior> 部分的配置匹配,两者都是“myBehaviorExtensionElement”。

您要添加的 <behaviorExtensions> 部分中的 type 值必须是类型的完整名称。完整名称的第一部分是完整的类型名称,第二部分是命名空间。VersionCulturePublicKeyToken 也是必不可少的元素。类型名称的 string 使用逗号作为分隔符。**逗号后必须留有空格**,否则将无法正常添加扩展行为的配置。为什么会有如此糟糕的约束?因为该值是为反射技术准备的。我同意这是一个缺陷。我希望 Microsoft 在 WCF 的下一个版本中能解决这个问题。

在相关方法的正文中,我们需要添加检查参数、消息和操作调用者的扩展。它们之间的关系在此处存在。对于检查参数,扩展的逻辑可能被添加到 IOperationBehavior 接口的 ApplyClientBehavior()ApplyDispatchBehavior() 中。例如,我们可以定义一个 CalculatorParameterValidation 类来处理 CalculatorParameterInspector

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
}

如果不需要将检查器与扩展行为分开,一个更好的解决方案是让自定义类同时实现 IParameterInspectorIOperationBehavior。例如:

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 相关联,但实际上它会与 DispatchOperationInvoker 属性一起工作。假设我们定义了一个实现 IOperationInvoker 接口的 MyOperationInvoker 类,解决方案如下:

public class MyOperationInvoker : IOperationInvoker
{
    //some implementation
}

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 的消息检查而言,我们可以通过 IServiceBehaviorIEndpointBehaviorIContractBehavior 所拥有的 DispatchRuntime 中的 MessageInspectors 属性来添加。就与 Client 的消息检查而言,我们可以通过 IEndpointBehaviorIContractBehaviorIServiceBehavior 不能在客户端使用,所以不属于 IServiceBehavior 的业务)所拥有的 ClientRuntime 中的 MessageInspectors 属性来添加。例如:

public class PrintMessageInspectorBehavior : IDispatchMessageInspector,
    IEndpointBehavior
{
    #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
 
    //The implementation of DispatchMessageInspector; Omitted
}

如果我们的行为实现了 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);
        }
    }
}

历史

  • 2009年6月10日:初始发布
  • 2014 年 9 月 18 日
© . All rights reserved.