紧接前文WCF4.0新特性体验(5):路由服务Routing Service(上)。今天我们介绍WCF4.0消息路由的实现机制,然后会讲解路由服务的实现过程。
【4】WCF与路由服务:
其实在介绍WCF消息路由的时候,我们前面已经消息介绍了WS-Routing和WS-Address规范。但是这些知识还不够。我们现在来了解一下WCF如何调用一个特定的服务方法的。这也是WCF路由服务涉及到的重要内容。下面我们先来介绍一下WCF服务方法的调用原理。
【4.1】 WCF方法调用的原理:
我们知道,在客户端,我们可以通过代理类来调用一个服务的方法。这其实在背后,WCF框架为我们生成了一个SOAP消息。而这个SOAP消息包含我们要调用服务的必要信息。我们看一下简单的SOAP消息的例子:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/IService1/GetData</Action>
</s:Header>
<s:Body>
<GetData xmlns="http://tempuri.org/">
<value>1</value>
</GetData>
</s:Body>
</s:Envelope>
<s:Header>
<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/IService1/GetData</Action>
</s:Header>
<s:Body>
<GetData xmlns="http://tempuri.org/">
<value>1</value>
</GetData>
</s:Body>
</s:Envelope>
在WCF中,每个服务端点实际上有两个地址:
-
逻辑地址:逻辑地址(“To”)是 SOAP 消息的目标地址。
-
物理地址:物理地址(“Via”)是 WCF侦听消息的实际传输特定网络地址。
WCF通道基础结构针对的是物理地址,因为物理地址负责使用特定的传输协议在特定的位置ListenURI接收传入的消息。 WCF调度程序避开了这种联网细节,而是关注将传入消息映射到一个端点,并最终到达方法调用。
【4.2】 WCF消息筛选器:
那么WCF根据什么来实现消息的匹配的呢?这里就要介绍一个重要的概念:消息过滤器。MessageFilter实例负责消息的调度。前面的SOAP中的Action值,是一个消息调度的方式。
当传入消息时,WCF使用消息过滤器器确定匹配端点。我们也可以自己定义消息过滤器器类型。WCF对于自定义消息过滤器器的支持,带来了很大的灵活性。我们可以摆脱传统调度模型,实现SOAP 以外的调度方式 :实现 REST/POX 样式的服务。
每个终结点实际上关联着两个过滤器:
- 地址过滤器:确定传入消息是否匹配端点的“To”地址和任何必需的地址标头,
- 契约过滤器:确定它是否匹配端点的契约。
两个筛选器都被调度程序用来确定目标端点。
WCF类型系统定义了6个消息过滤器类型以满足不同的需求,它们是:
- XPathMessageFilter 使用 XPath 1.0 表达式来指定匹配的条件,它是实现基于内容路由的核心消息过滤器。
- MatchAllMessageFilter 与所有消息相匹配。
- MatchNoneMessageFilter 与所有消息都不匹配。
- ActionMessageFilter 测试消息操作是否为指定的操作集之一。也就操作匹配。
- EndpointAddressMessageFilter 测试消息是否满足指定的终结点地址。
- PrefixEndpointAddressMessageFilter 对消息 URI 的前缀进行匹配,只要传入的“To”地址与端点地址有相同的地址前缀(一种松散匹配),将导致两者匹配。
WCF过滤器只是 MessageFilter的子类。MessageFilter 有几个内置实现,包括 EndpointAddressMessageFilter 和 ActionMessageFilter,它们分别作为默认地址和契约过滤器。
EndpointAddressMessageFilter 仅仅将“To”地址与端点地址进行比较,预期它们完全匹配。它也将传入消息中获得的寻址标头和终结点要求的一组寻址标头进行比较。
ActionMessageFilter 将传入的“Action”值和约定上的操作进行比较,再次预期完全匹配。
PrefixEndpointAddressMessageFilter 只要传入的“To”地址与端点地址有相同的地址前缀(一种松散匹配),将导致两者匹配。
MatchAllMessageFilter,它导致所有消息匹配给定端点。
我们可以通过 [ServiceBehavior] 的 AddressFilterMode 属性来选择消息过滤器。使用 PrefixEndpointAddressMessageFilter的例子代码:
[ServiceBehavior(AddressFilterMode=AddressFilterMode.Prefix)]
public class CalculatorService : ISimpleMath, IScientif
public class CalculatorService : ISimpleMath, IScientif
【4.3】WCF路由服务类型RoutingService:
WCF4.0实现了一个路由服务类型RoutingService,它可以支持多种消息交换模式的消息路由场景:单向、请求/应答、双工。RoutingService的定义如下:
namespace System.ServiceModel.Routing
{
// Summary:
// Defines the routing service, which is responsible for routing messages between
// endpoints based on filter criteria.
[ServiceBehavior(AddressFilterMode = AddressFilterMode.Any, InstanceContextMode = InstanceContextMode.PerSession, UseSynchronizationContext = false, ValidateMustUnderstand = false)]
public sealed class RoutingService : ISimplexDatagramRouter, ISimplexSessionRouter, IRequestReplyRouter, IDuplexSessionRouter
{
}
}
{
// Summary:
// Defines the routing service, which is responsible for routing messages between
// endpoints based on filter criteria.
[ServiceBehavior(AddressFilterMode = AddressFilterMode.Any, InstanceContextMode = InstanceContextMode.PerSession, UseSynchronizationContext = false, ValidateMustUnderstand = false)]
public sealed class RoutingService : ISimplexDatagramRouter, ISimplexSessionRouter, IRequestReplyRouter, IDuplexSessionRouter
{
}
}
IRequestReplyRouter、IDisposable。为了实现回会话路由,它也实现了ISimplexSessionRouter、IDuplexSessionRouter接口。路由服务会根据消息过滤器的条件在服务终结点之间来转发消息。也就是路由消息。
【5】WCF4.0 路由服务开发过程详解:
了解了WCF路由的一些机制以后,我们就来实现今天的消息路由的Demo。这个程序呢,包含4个部分。客户端、路由服务和2个WCF服务。路由服务的结构图如下所示:
![](https://i-blog.csdnimg.cn/blog_migrate/cba81e069794bb0b3d7ada7d770ea3df.png)
客户端通过basicHttp发送消息到路由服务,路由服务根据消息过滤器设置来转发消息到服务A或者B,但是使用的绑定有所改变,发送给服务A的绑定为WSHttpBinding,而服务B交互使用的绑定为NetTcpBinding。
我们知道这个机制实际上对于SOAP的版本做了改变,但是可以制止完整的消息路由,这里我们使用MEP,也就是请求/应答消息交换模式。消息交换模式在《WCF技术内幕》里做过详细的介绍。
【5.1】服务A和服务B:
这里定义的服务A和服务B,只是两个简单的测试服务,分别暴露的方法为服务A:Hello,而服务B为Bye。
服务A的实现代码如下:
//1.服务契约
[ServiceContract(Namespace = "http://www.cnblogs.com/frank_xl/")]
public interface IWCFServiceA
{
//操作契约
[OperationContract(Action = "SayHello", ReplyAction = "SayHelloAReply")]
string SayHello(string name);
}
//2.服务类,继承接口。实现服务契约定义的操作
public class WCFServiceA : IWCFServiceA
{
//实现接口定义的方法
public string SayHello(string name)
{
Console.WriteLine("Hello! {0},Calling at {1} ...", name,DateTime.Now.ToLongTimeString());
return "Hello ! " + name;
}
}
[ServiceContract(Namespace = "http://www.cnblogs.com/frank_xl/")]
public interface IWCFServiceA
{
//操作契约
[OperationContract(Action = "SayHello", ReplyAction = "SayHelloAReply")]
string SayHello(string name);
}
//2.服务类,继承接口。实现服务契约定义的操作
public class WCFServiceA : IWCFServiceA
{
//实现接口定义的方法
public string SayHello(string name)
{
Console.WriteLine("Hello! {0},Calling at {1} ...", name,DateTime.Now.ToLongTimeString());
return "Hello ! " + name;
}
}
服务B的实现代码如下:
//1.服务契约
[ServiceContract(Namespace = "http://www.cnblogs.com/frank_xl/")]
public interface IWCFServiceB
{
//操作契约
[OperationContract(Action = "SayBye", ReplyAction = "SayByeReply")]
string SayBye(string name);
}
//2.服务类,继承接口。实现服务契约定义的操作
public class WCFServiceB : IWCFServiceB
{
//实现接口定义的方法
public string SayBye(string name)
{
Console.WriteLine("Bye! {0},Calling at {1} ...", name,DateTime.Now.ToLongTimeString());
return "Bye ! " + name;
}
}
[ServiceContract(Namespace = "http://www.cnblogs.com/frank_xl/")]
public interface IWCFServiceB
{
//操作契约
[OperationContract(Action = "SayBye", ReplyAction = "SayByeReply")]
string SayBye(string name);
}
//2.服务类,继承接口。实现服务契约定义的操作
public class WCFServiceB : IWCFServiceB
{
//实现接口定义的方法
public string SayBye(string name)
{
Console.WriteLine("Bye! {0},Calling at {1} ...", name,DateTime.Now.ToLongTimeString());
return "Bye ! " + name;
}
}
【5.2】路由服务:
路由服务的实现代码也是比较简单,也是基于Console控制台宿主托管,这里我们要实例化一个服务Host的实例。代码如下:
using (ServiceHost host = new ServiceHost(typeof(RoutingService)))
{
判断是否以及打开连接,如果尚未打开,就打开侦听端口
if (host.State != CommunicationState.Opening)
host.Open();
//显示运行状态
}
{
判断是否以及打开连接,如果尚未打开,就打开侦听端口
if (host.State != CommunicationState.Opening)
host.Open();
//显示运行状态
}
和普通的WCF服务托管没有区别。这里比较难的部分在于消息过滤器的设置。这里我们直接使用配置文件时下,当然你也可以使用C#代码来进行配置。这里我们首先要配置一下服务A和B的地址,因为路由服务要使用这个地址来转发消息:
<client>
<endpoint name="EndPointA"
address="http://localhost:9001/WCFServiceA"
binding="wsHttpBinding" bindingConfiguration="WSConfig"
contract="*" />
<endpoint name="EndPointB" bindingConfiguration="netTcpConfig"
address="net.tcp://localhost:9002/WCFServiceB"
binding="netTcpBinding"
contract="*" />
</client>
<endpoint name="EndPointA"
address="http://localhost:9001/WCFServiceA"
binding="wsHttpBinding" bindingConfiguration="WSConfig"
contract="*" />
<endpoint name="EndPointB" bindingConfiguration="netTcpConfig"
address="net.tcp://localhost:9002/WCFServiceB"
binding="netTcpBinding"
contract="*" />
</client>
另外就是启用消息过滤器,这里我们使用的是基于Action的路由方式,当然也可以使用其它的消息过滤器。这里可以直接在配置文件里配置:
<routing>
<filters>
<filter name="MatchA"
filterType="Action" filterData="SayHello" />
<filter name="MatchB"
filterType="Action" filterData="SayBye" />
</filters>
<filterTables>
<filterTable name="WCFRoutingTable">
<add filterName="MatchA"
endpointName="EndPointA" />
<add filterName="MatchB"
endpointName="EndPointB" />
</filterTable>
</filterTables>
</routing>
<filters>
<filter name="MatchA"
filterType="Action" filterData="SayHello" />
<filter name="MatchB"
filterType="Action" filterData="SayBye" />
</filters>
<filterTables>
<filterTable name="WCFRoutingTable">
<add filterName="MatchA"
endpointName="EndPointA" />
<add filterName="MatchB"
endpointName="EndPointB" />
</filterTable>
</filterTables>
</routing>
这里配置了消息终结点和SOAP Action之间的对应关系,我们可以从过滤器表
filterTable设置这个对应关系。配置完消息过滤器以后,我们还需要在服务行为节点里使用这个
filterTable。配置信息如下:
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<routing filterTableName="WCFRoutingTable" />
</behavior>
</serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<routing filterTableName="WCFRoutingTable" />
</behavior>
</serviceBehaviors>
关于路由消息的配置已经基本完成,现在我们还需要为客户端暴露一个可以调用的路由服务的地址。这个终结点地址就是客户端要发送的消息的地址。路由服务的配置也很简单:
<services>
<service behaviorConfiguration="ServiceBehavior"
name="System.ServiceModel.Routing.RoutingService">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8001/"/>
</baseAddresses>
</host>
<endpoint address="WCFRoutingService"
binding="basicHttpBinding"
name="requestReplyEndpoint"
contract="System.ServiceModel.Routing.IRequestReplyRouter" />
</service>
</services>
<service behaviorConfiguration="ServiceBehavior"
name="System.ServiceModel.Routing.RoutingService">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8001/"/>
</baseAddresses>
</host>
<endpoint address="WCFRoutingService"
binding="basicHttpBinding"
name="requestReplyEndpoint"
contract="System.ServiceModel.Routing.IRequestReplyRouter" />
</service>
</services>
我们这里要注意路由服务使用的契约
System.ServiceModel.Routing.IRequestReplyRouter和服务名称System.ServiceModel.Routing.RoutingService。
【5.3】客户端:
为了演示方便,我们这里的客户端使用ChannelFactory直接创建客户端代理,与服务进行通信。这里指的注意的地方就是关于路由服务地址的配置。因为客户端都向同一个路由服务的地址来发送消息。但是如何区分不同的服务A和B呢。这里就需要作出配置。这里是通过契约不同来指定最后的服务A或者是B的。
<system.serviceModel>
<client>
<endpoint name="WCFServiceA" address="http://localhost:8001/WCFRoutingService" binding="basicHttpBinding" contract="WCFServiceA.IWCFServiceA"/>
<endpoint name="WCFServiceB" address="http://localhost:8001/WCFRoutingService" binding="basicHttpBinding" contract="WCFServiceB.IWCFServiceB"/>
</client>
</system.serviceModel>
<client>
<endpoint name="WCFServiceA" address="http://localhost:8001/WCFRoutingService" binding="basicHttpBinding" contract="WCFServiceA.IWCFServiceA"/>
<endpoint name="WCFServiceB" address="http://localhost:8001/WCFRoutingService" binding="basicHttpBinding" contract="WCFServiceB.IWCFServiceB"/>
</client>
</system.serviceModel>
当然这些都可以通过代码来进行设置。
【5.4】运行结果:
客户端分别通过路由服务调用了服务A和服务B,代码如下:
IWCFServiceA channelA = new ChannelFactory<IWCFServiceA>("WCFServiceA").CreateChannel();
result = channelA.SayHello("A");
Console.WriteLine("Service A:{0} !", result);
IWCFServiceB channelB = new ChannelFactory<IWCFServiceB>("WCFServiceB").CreateChannel();
result = channelB.SayBye("B");
Console.WriteLine("Service B:{0} !", result);
result = channelA.SayHello("A");
Console.WriteLine("Service A:{0} !", result);
IWCFServiceB channelB = new ChannelFactory<IWCFServiceB>("WCFServiceB").CreateChannel();
result = channelB.SayBye("B");
Console.WriteLine("Service B:{0} !", result);
启动程序以后,我们可以看到各个控制台的输出信息:
![](https://i-blog.csdnimg.cn/blog_migrate/267f025b68fde3e924cc17287b9c25e0.png)
这里客户端,我们首先通过路由服务,调用了服务A,服务A返回结果给客户端。并且打印到控制台窗口。
客户端又通过路由服务调用了服务B,服务B返回结果给A。并且打印到控制台窗口。
两次的调用成功。由于篇幅有限,这里没有做出更多的测试。大家也可以对于单向和双工消息交换模式做出测试。
【6】总结:
WCF4.0路由服务的内容就到此结束。
(1)消息路由不是一个新的概念,其实很多平台或者系统都实现了对于消息路由的支持,WCF路由服务也是对于已有WS-Address规范的实现。这是消息路由的本质。
(2)WCF框架为了实现基于不同机制的消息路由,提供了多种消息过滤器类型来满足要求,我们也可以定义自己的消息过滤器,来实现消息的调度。
(3)如果你想实现基于消息内容的路由,也就是content-based routing,就需要借助于Xpath消息过滤器,来根据消息的内容来分析消息路由的信息,进行基于内容的路由功能。
(4)以上就是全部的WCF消息路由的全部内容,下面给出本次课程的代码供大家参考。
/Files/frank_xl/WCF4RoutingDemoByFrankXuLei.zip另外欢迎继续关注《WCF4.0新特性体验》的系列文章。
参考文章:
本文转自 frankxulei 51CTO博客,原文链接:http://blog.51cto.com/frankxulei/320280
,如需转载请自行联系原作者