概述
在WCF专题系列(3):深入WCF寻址Part 3—消息过滤引擎一文中,详细介绍了WCF中的消息筛选引擎,包括消息筛选器和筛选器表,每个EndpointDispatcher都包含了两个消息筛选器,默认的地址过滤器是EndpointAddressMessageFilter,默认的契约过滤器是ActionMessageFilter,这些是可以通过Behavior来改变的。本文我们将学习如何创建一个自定义的消息过滤器,并通过自定义Behavior来改变EndpointDispatcher的默认过滤器。
自定义过滤器
在默认情况下,默认情况下,仅当消息的“To”标头为终结点的 EndpointAddress 并且消息的动作与终结点操作的动作之一匹配时,终结点的消息筛选器才与此消息匹配。在本文中,我们将自定义一个消息过滤器,它不要求消息的“To”标头完全与EndpointAddress完全匹配,而只是检测SOAP消息中的“To”标头中是否包含某些特定的字符。所有的消息过滤器都从MessageFilter基类继承,如下代码所示:
/// <summary>
/// Author: TerryLee
/// Url: http://www.cnblogs.com/terrylee
/// </summary>
public class SpecialCharactersMessageFilter : MessageFilter
{
private String _characters = String.Empty;
public SpecialCharactersMessageFilter(string characters)
{
this._characters = characters;
}
public override bool Match(Message message)
{
Uri to = message.Headers.To;
if (to == null)
return false;
return to.AbsoluteUri.Contains(_characters);
}
public override bool Match(MessageBuffer buffer)
{
return Match(buffer.CreateMessage());
}
}
SpecialCharactersMessageFilter的实现非常简单,仅仅是查找“To”标头是否包含某些特定字符,这些字符我们会在配置文件中进行配置。
定义EndpointBehavior
现在我们自定义一个EndpointBehavior,使用它来替换EndpointDispatcher上的地址过滤器和契约过滤器,它实现自IendpointBehavior接口,如下代码所示:
/// <summary>
/// Author: TerryLee
/// Url: http://www.cnblogs.com/terrylee
/// </summary>
public class FilteringEndpointBehavior : IEndpointBehavior
{
MessageFilter addressFilter;
MessageFilter contractFilter;
public FilteringEndpointBehavior(MessageFilter addressFilter,
MessageFilter contractFilter)
{
this.addressFilter = addressFilter;
this.contractFilter = contractFilter;
}
public void AddBindingParameters(ServiceEndpoint endpoint,
BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint,
ClientRuntime clientRuntime)
{
throw new InvalidOperationException(
"This behavior should only be used on the server.");
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.AddressFilter = this.addressFilter;
endpointDispatcher.ContractFilter = this.contractFilter;
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
这里只是实现了ApplyDispatchBehavior方法,其它方法暂时先不用考虑,另外,由于该Behavior只是用在服务端,所以在ApplyClientBehavior方法中抛出一个异常。
定义行为扩展
接下来定义一个行为扩展元素,这里定义了一个Characters的属性,用来在配置文件中指定消息过滤器中用到的特殊字符,如下代码所示:
/// <summary>
/// Author: TerryLee
/// Url: http://www.cnblogs.com/terrylee
/// </summary>
public class FilteringEndpointBehaviorExtension
: BehaviorExtensionElement
{
protected override object CreateBehavior()
{
MessageFilter addressFilter =
new SpecialCharactersMessageFilter(Characters);
MessageFilter contractFilter =
new MatchAllMessageFilter();
return new FilteringEndpointBehavior(addressFilter, contractFilter);
}
public override Type BehaviorType
{
get
{
return typeof(FilteringEndpointBehavior);
}
}
[ConfigurationProperty("characters",
DefaultValue = "terrylee", IsRequired = true)]
public String Characters
{
get {
return base["characters"].ToString();
}
set {
base["characters"] = value;
}
}
}
配置服务
定义一个简单的服务契约以及服务的实现,代码如下,不再多说:
/// <summary>
/// Author: TerryLee
/// Url: http://www.cnblogs.com/terrylee
/// </summary>
[ServiceContract(Namespace = "http://www.cnblogs.com/terrylee/")]
public interface IEchoService
{
[OperationContract]
string Echo(string msg);
}
public class EchoService : IEchoService
{
public string Echo(string msg)
{
return "Hello:" + msg;
}
}
现在来看一下服务端的配置,除了必须的终结点配置之外,为服务注册一个新的Behavior,代码如下所示:
<extensions>
<behaviorExtensions>
<add name="filteringEndpointBehavior"
type="TerryLee.CustomizeMessageFilter.Service.
FilteringEndpointBehaviorExtension, Service,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
创建EndpointBehavior配置:
<endpointBehaviors>
<behavior name="filterBehavior">
<filteringEndpointBehavior characters="terrylee" />
</behavior>
</endpointBehaviors>
服务终结点使用behaviorConfiguration:
<endpoint address=""
binding ="wsHttpBinding"
contract="TerryLee.CustomizeMessageFilter.Service.IEchoService"
behaviorConfiguration="filterBehavior">
</endpoint>
最终完整的配置文件:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="TerryLee.CustomizeMessageFilter.Service.EchoService"
behaviorConfiguration="echoBehavior">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8887/EchoService"/>
</baseAddresses>
</host>
<endpoint address=""
binding ="wsHttpBinding"
contract="TerryLee.CustomizeMessageFilter.Service.IEchoService"
behaviorConfiguration="filterBehavior">
</endpoint>
</service>
</services>
<extensions>
<behaviorExtensions>
<add name="filteringEndpointBehavior"
type="TerryLee.CustomizeMessageFilter.Service.
FilteringEndpointBehaviorExtension, Service,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<behaviors>
<serviceBehaviors>
<behavior name="echoBehavior">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="filterBehavior">
<filteringEndpointBehavior characters="terrylee" />
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
至此服务端配置完成,根据配置,只有SOAP消息“To”标头中包含有“terrylee”的字符时,此时终结点的消息过滤器才与此消息匹配。现在在控制台中输出一下,看看针对配置的终结点它所使用的地址过滤器和契约过滤器分别是什么,如图1所示:
图1
可以看到,终结点所用的地址过滤器不再是默认的EndpointAddressMessageFilter,而是我们自定义的SpecialCharactersMessageFilter。
客户端测试
通过上面的配置,我们知道,只有SOAP消息的“To”标头中带有“terrylee”字符,消息才会被分发到相应的终结点上。现在编写一个简单的测试客户端程序,如下代码所示:
/// <summary>
/// Author: TerryLee
/// Url: http://www.cnblogs.com/terrylee
/// </summary>
static void Main()
{
Uri serviceVia = new Uri("http://localhost:8887/EchoService");
WSHttpBinding binding = new WSHttpBinding();
ChannelFactory<IEchoService> factory = new ChannelFactory<IEchoService>
(binding, new EndpointAddress(serviceVia));
String address = "http://localhost/terrylee";
Console.WriteLine(String.Format("Sending message to {0}...", address));
IEchoService channel = factory.CreateChannel(
new EndpointAddress(address), serviceVia);
try
{
String reply = channel.Echo("cnblogs");
Console.WriteLine(reply.ToString());
((IClientChannel)channel).Close();
}
catch (CommunicationException ce)
{
Console.WriteLine("Exception: {0}", ce.Message);
((IClientChannel)channel).Abort();
}
Console.WriteLine("Press <ENTER> to terminate client.");
Console.ReadLine();
}
由于定义了终结点的逻辑地址为“http://localhost/terrylee”,符合我们上面所讲的条件,可以看到输出结果如图2所示:
图2
可以看到,服务正确的返回我们想要的消息,但是如果修改一下address为“http://localhost/anytao”,由于没有包含特定的字符“terrylee”,服务端将返回错误的信息:
String address = "http://localhost/anytao";
输出结果如图3所示:
图3
结束语
本文我介绍了如何自定义消息筛选器,以及如何通过Behavior来修改EndpointDispatcher的默认过滤器,消息过滤是消息分发过程中非常重要的一个环节,也是WCF寻址中一个重要的部分,希望对于大家有所帮助。 WCF相关文章:
WCF专题系列(5):深入WCF寻址Part 5—逻辑地址和物理地址
WCF专题系列(3):深入WCF寻址Part 3—消息过滤引擎
WCF专题系列(2):深入WCF寻址Part 2—自定义寻址报头
WCF专题系列(1):深入WCF寻址Part 1—Web服务寻址规范