关注 WCF 行为的扩展






4.90/5 (15投票s)
WCF 为开发者提供了一个灵活且可扩展的架构。最常见的情况是自定义行为的扩展。这并不复杂,但需要注意一些问题。本文讨论了如何在 WCF 中扩展行为。
引言
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()
方法将在目标方法被调用之后发生。例如,我们可以在调用方法之前验证参数值是否小于零。如果小于零,则会抛出异常。
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
}
有四种不同类型的行为,包括**服务行为**、**终结点行为**、**契约行为**和**操作行为**。它们对应的接口是 IServiceBehavior
、IEndpointBehavior
、IContractBehavior
和 IOperationBehavior
。尽管它们在本质上是不同的接口,但它们的方法几乎相似,包括:AddBindingParameters()
、ApplyClientBehavior()
和 ApplyDispatchBehavior()
。
注意:由于 IServiceBehavior
仅在服务端使用,因此它没有 ApplyClientBehavior()
方法。
我们可以自定义类来实现这些接口,但需要强调一些关键要素。
- 行为的范围。表 1 描述了所有情况。
行为类型
接口
范围
Service
端点
契约
操作 (Operation)
Service
IServiceBehavior
Y
Y
Y
Y
端点
IEndpointBehavior
Y
Y
Y
契约
IContractBehavior
Y
Y
操作 (Operation)
IOperationBehavior
Y
- 我们可以通过应用自定义属性来添加服务行为、契约行为和操作行为的扩展,但不能以这种方式添加终结点行为的扩展。我们可以使用*配置*文件添加服务行为和终结点行为的扩展,但不能以这种方式添加契约行为和操作行为的扩展。所有行为都可以通过
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>
是我们的扩展行为,而 providerName
是 MyBehaviorExtensionElement
的新属性。如果您扩展了 IEndpointBehavior
,则应将 <serviceBehaviors>
部分替换为 <endpointBehaviors>
。自定义行为的扩展将放在 <extensions></extensions>
部分。name
属性的值必须与 <behavior>
部分的配置匹配,两者都是“myBehaviorExtensionElement
”。
您要添加的 <behaviorExtensions>
部分中的 type 值必须是类型的完整名称。完整名称的第一部分是完整的类型名称,第二部分是命名空间。Version
、Culture
和 PublicKeyToken
也是必不可少的元素。类型名称的 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
}
如果不需要将检查器与扩展行为分开,一个更好的解决方案是让自定义类同时实现 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 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
的消息检查而言,我们可以通过 IServiceBehavior
、IEndpointBehavior
或 IContractBehavior
所拥有的 DispatchRuntime
中的 MessageInspectors
属性来添加。就与 Client
的消息检查而言,我们可以通过 IEndpointBehavior
或 IContractBehavior
(IServiceBehavior
不能在客户端使用,所以不属于 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 日