本主题概述了创建使用多个筛选器的路由配置所需的基本步骤。 在此示例中,消息将路由到计算器服务的两个实现,即 regularCalc 和 roundingCalc。 这两个实现都支持相同的操作;但是,其中一个服务在返回之前会将所有计算结果四舍五入到最接近的整数。 客户端应用程序必须能够指示是否使用服务的舍入版本;如果未表示任何服务首选项,则消息在两个服务之间进行负载均衡。 这两个服务公开的操作包括:
添加
减法
乘法
除法
由于这两个服务实现了相同的操作,因此不能使用操作筛选器,因为消息中指定的操作将不会是唯一的。 相反,必须执行其他工作,以确保消息路由到相应的终结点。
确定唯一数据
由于这两个服务实现处理相同的操作,并且本质上是相同的,除了它们返回的数据不同,因此从客户端应用程序发送的消息中包含的基本数据不够唯一,无法确定如何路由请求。 但是,如果客户端应用程序向消息添加唯一的标头值,则可以使用此值来确定应如何路由消息。
对于此示例,如果客户端应用程序需要舍入运算器处理消息,则通过以下代码添加自定义标头:
messageHeadersElement.Add(MessageHeader.CreateHeader("RoundingCalculator", "http://my.custom.namespace/", "rounding"));现在可以使用 XPath 筛选器检查此标头的消息,并将包含标头的消息路由到 roundCalc 服务。
此外,路由服务公开了两个可用于 EndpointName、EndpointAddress 或 PrefixEndpointAddress 筛选器的虚拟终结点服务,以便根据客户端应用程序将请求提交到的终结点,将传入消息正确路由到特定的计算器实现。
定义终结点
定义路由服务使用的终结点时,应首先确定客户端和服务使用的通道的形状。 在此方案中,目标服务都使用请求-回复模式,因此 IRequestReplyRouter 被使用。 以下示例定义路由服务公开的服务终结点。
<services> <service behaviorConfiguration="routingConfiguration" name="System.ServiceModel.Routing.RoutingService"> <host> <baseAddresses> <add baseAddress="http://localhost/routingservice/router" /> </baseAddresses> </host> <!--Set up the inbound endpoints for the Routing Service--> <!--first create the general router endpoint--> <endpoint address="general" binding="wsHttpBinding" name="routerEndpoint" contract="System.ServiceModel.Routing.IRequestReplyRouter" /> <!--create a virtual endpoint for the regular calculator service--> <endpoint address="regular/calculator" binding="wsHttpBinding" name="calculatorEndpoint" contract="System.ServiceModel.Routing.IRequestReplyRouter" /> <!--now create a virtual endpoint for the rounding calculator--> <endpoint address="rounding/calculator" binding="wsHttpBinding" name="roundingEndpoint" contract="System.ServiceModel.Routing.IRequestReplyRouter" /> </service> </services>使用此配置,路由服务会公开三个单独的终结点。 根据运行时选项,客户端应用程序会将消息发送到其中一个地址。 到达其中一个“虚拟”服务终结点(“舍入/计算器”或“常规/计算器”)的消息将转发到相应的计算器实现。 如果客户端应用程序未将请求发送到特定终结点,则会将消息发送到常规终结点。 无论选择哪种终结点,客户端应用程序也可以选择包含自定义标头,以指示应将消息转发到舍入计算器实现。
以下示例定义路由服务将消息路由到的客户端(目标)终结点。
<client> <endpoint name="regularCalcEndpoint" address="net.tcp://localhost:9090/servicemodelsamples/service/" binding="netTcpBinding" contract="*" /> <endpoint name="roundingCalcEndpoint" address="net.tcp://localhost:8080/servicemodelsamples/service/" binding="netTcpBinding" contract="*" /> </client>这些终结点在筛选器表中用于指示消息在与特定筛选器匹配时发送到的目标终结点。
定义筛选器
若要根据客户端应用程序添加到消息的“RoundingCalculator”自定义标头路由消息,请定义使用 XPath 查询检查此标头是否存在的筛选器。 由于此标头是使用自定义命名空间定义的,因此还添加一个命名空间条目,该条目定义 XPath 查询中使用的“custom”的自定义命名空间前缀。 以下示例定义必要的路由节、命名空间表和 XPath 筛选器。
<routing> <!-- use the namespace table element to define a prefix for our custom namespace--> <namespaceTable> <add prefix="custom" namespace="http://my.custom.namespace/"/> </namespaceTable> <filters> <!--define the different message filters--> <!--define an xpath message filter to look for the custom header coming from the client--> <filter name="XPathFilter" filterType="XPath" filterData="/s12:Envelope/s12:Header/custom:RoundingCalculator = 'rounding'"/> </filters> </routing>此 MessageFilter 在消息中查找一个名称为 RoundingCalculator 且包含值“rounding”的标头。 此标头由客户端设置,以指示消息应路由到 roundingCalc 服务。
注释
默认情况下,s12 命名空间前缀在命名空间表中定义,并表示命名空间
http://www.w3.org/2003/05/soap-envelope。还必须定义用于查找在两个虚拟终结点上接收的消息的筛选器。 第一个虚拟端点是“常规/计算器”端点。 客户端可以向此终结点发送请求,以指示消息应路由到 regularCalc 服务。 以下配置定义了一个筛选器,该筛选器使用EndpointNameMessageFilter来确定消息是否通过名称在filterData中指定的终结点到达。
<!--define an endpoint name filter looking for messages that show up on the virtual regular calculator endpoint--> <filter name="EndpointNameFilter" filterType="EndpointName" filterData="calculatorEndpoint"/>如果服务终结点收到名为“calculatorEndpoint”的消息,此筛选器的计算结果为
true。接下来,定义一个筛选器,该筛选器查找发送到 roundingEndpoint 地址的消息。 客户端可以向此终结点发送请求,以指示消息应路由到 roundingCalc 服务。 以下配置定义了一个筛选器,该筛选器使用 PrefixEndpointAddressMessageFilter 来确定消息是否到达“舍入/计算器”终结点。
<!--define a filter looking for messages that show up with the address prefix. The corresponds to the rounding calc virtual endpoint--> <filter name="PrefixAddressFilter" filterType="PrefixEndpointAddress" filterData="http://localhost/routingservice/router/rounding/"/>如果在以开头
http://localhost/routingservice/router/rounding/的地址收到消息,则此筛选器的计算结果为 true。 由于此配置使用的基址是http://localhost/routingservice/router,并且为 roundingEndpoint 指定的地址为“rounding/calculator”,因此用于与此终结点通信的完整地址与此http://localhost/routingservice/router/rounding/calculator筛选器匹配。注释
执行匹配时,PrefixEndpointAddress 筛选器不会评估主机名,因为可以使用各种主机名来引用单个主机,这些主机名可能都是从客户端应用程序引用主机的有效方法。 例如,以下所有内容都可能引用同一主机:
- localhost
- 127.0.0.1
www.contoso.com- ContosoWeb01
最终筛选器必须支持在没有自定义标头的情况下,将到达通用端点的消息进行路由。 对于此方案,消息应在 regularCalc 和 roundingCalc 服务之间交替。 为了支持这些消息的“轮询路由”,请使用自定义筛选器,以便每条被处理的消息都能匹配到一个筛选器实例。 下面定义了 RoundRobinMessageFilter 的两个实例,这些实例组合在一起,以指示它们应相互交替。
<!-- Set up the custom message filters. In this example, we'll use the example round robin message filter, which alternates between the references--> <filter name="RoundRobinFilter1" filterType="Custom" customType="CustomFilterAssembly.RoundRobinMessageFilter, CustomFilterAssembly" filterData="group1"/> <filter name="RoundRobinFilter2" filterType="Custom" customType="CustomFilterAssembly.RoundRobinMessageFilter, CustomFilterAssembly" filterData="group1"/>在运行时,该筛选器类型会在同一组内配置为集合的所有定义的筛选器实例之间交替使用。 这会导致此自定义筛选器处理的消息在返回
true时,在RoundRobinFilter1和RoundRobinFilter2之间交替。
定义筛选器表
若要将筛选器与特定的客户端终结点相关联,必须将筛选器放置在筛选器表中。 此示例方案还使用筛选器优先级设置,这是一个可选设置,可用于指示筛选器的处理顺序。 如果未指定筛选器优先级,则会同时评估所有筛选器。
注释
虽然指定筛选器优先级可以控制筛选器的处理顺序,但它可能会对路由服务的性能产生不利影响。 如果可能,请构造筛选器逻辑,以便不需要使用筛选器优先级。
下面定义了筛选器表,并将前面定义的“XPathFilter”添加到优先级为 2 的表。 此条目还指定如果
XPathFilter与消息匹配,则将消息路由到该roundingCalcEndpoint消息。<routing> ... <filters> ... </filters> <filterTables> <table name="filterTable1"> <entries> <!--add the filters to the message filter table--> <!--first look for the custom header, and if we find it, send the message to the rounding calc endpoint--> <add filterName="XPathFilter" endpointName="roundingCalcEndpoint" priority="2"/> </entries> </table> </filterTables> </routing>指定筛选器优先级时,首先评估最高优先级筛选器。 如果特定优先级级别的一个或多个筛选器匹配,则不会评估优先级较低的筛选器。 对于此方案,2 是指定的最高优先级,这是此级别的唯一筛选器条目。
已定义筛选器条目,以便通过检查终结点名称或地址前缀,来确定是否在特定终结点上收到消息。 以下条目将这两个筛选器条目添加到筛选器表,并将其与消息将路由到的目标终结点相关联。 这些筛选器设置为 1 的优先级,以指示仅当以前的 XPath 筛选器与消息不匹配时,它们才应运行。
<!--if the header wasn't there, send the message based on which virtual endpoint it arrived at--> <!--we determine this through the endpoint name, or through the address prefix--> <add filterName="EndpointNameFilter" endpointName="regularCalcEndpoint" priority="1"/> <add filterName="PrefixAddressFilter" endpointName="roundingCalcEndpoint" priority="1"/>由于这些筛选器的筛选器优先级为 1,因此仅当优先级级别为 2 的筛选器与消息不匹配时,才会评估这些筛选器。 此外,由于这两个筛选器具有相同的优先级级别,因此将同时评估它们。 由于这两个筛选器互斥,因此只有一个或另一个筛选器可以匹配消息。
如果消息与前面的任何筛选器不匹配,则消息是通过通用服务终结点接收的,并且不包含指示路由它的标头信息。 这些消息将由自定义筛选器处理,该筛选器在两个计算器服务之间对它们进行负载均衡。 以下示例演示如何将筛选器条目添加到筛选器表;每个筛选器都与两个目标终结点之一相关联。
<!--if none of the other filters have matched, this message showed up on the default router endpoint, with no custom header--> <!--round robin these requests between the two services--> <add filterName="RoundRobinFilter1" endpointName="regularCalcEndpoint" priority="0"/> <add filterName="RoundRobinFilter2" endpointName="roundingCalcEndpoint" priority="0"/>由于这些条目指定优先级为 0,因此仅当没有高优先级的筛选器与消息匹配时,才会评估它们。 此外,由于两者具有相同的优先级,因此会同时评估它们。
如前所述,这些筛选器定义使用的自定义筛选器仅对每个接收到的消息只评估一个或另一个。 由于使用此筛选器仅定义了两个筛选器,并且它们具有相同的组设置,因此路由服务会在 regularCalcEndpoint 和 RoundingCalcEndpoint 之间交替发送。
若要针对筛选器评估消息,筛选器表必须首先与将用于接收消息的服务终结点相关联。 以下示例演示如何使用路由行为将路由表与服务终结点相关联:
<behaviors> <!--default routing service behavior definition--> <serviceBehaviors> <behavior name="routingConfiguration"> <routing filterTableName="filterTable1" /> </behavior> </serviceBehaviors> </behaviors>
Example
下面是配置文件的完整列表。
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright (c) Microsoft Corporation. All rights reserved -->
<configuration>
<system.serviceModel>
<services>
<service behaviorConfiguration="routingConfiguration"
name="System.ServiceModel.Routing.RoutingService">
<host>
<baseAddresses>
<add baseAddress="http://localhost/routingservice/router" />
</baseAddresses>
</host>
<!--Set up the inbound endpoints for the Routing Service-->
<!--first create the general router endpoint-->
<endpoint address="general"
binding="wsHttpBinding"
name="routerEndpoint"
contract="System.ServiceModel.Routing.IRequestReplyRouter" />
<!--create a virtual endpoint for the regular calculator service-->
<endpoint address="regular/calculator"
binding="wsHttpBinding"
name="calculatorEndpoint"
contract="System.ServiceModel.Routing.IRequestReplyRouter" />
<!--now create a virtual endpoint for the rounding calculator-->
<endpoint address="rounding/calculator"
binding="wsHttpBinding"
name="roundingEndpoint"
contract="System.ServiceModel.Routing.IRequestReplyRouter" />
</service>
</services>
<behaviors>
<!--default routing service behavior definition-->
<serviceBehaviors>
<behavior name="routingConfiguration">
<routing filterTableName="filterTable1" />
</behavior>
</serviceBehaviors>
</behaviors>
<client>
<!--set up the destination endpoints-->
<endpoint name="regularCalcEndpoint"
address="net.tcp://localhost:9090/servicemodelsamples/service/"
binding="netTcpBinding"
contract="*" />
<endpoint name="roundingCalcEndpoint"
address="net.tcp://localhost:8080/servicemodelsamples/service/"
binding="netTcpBinding"
contract="*" />
</client>
<routing>
<!-- use the namespace table element to define a prefix for our custom namespace-->
<namespaceTable>
<add prefix="custom" namespace="http://my.custom.namespace/"/>
</namespaceTable>
<filters>
<!--define the different message filters-->
<!--define an xpath message filter to look for the custom header coming from the client-->
<filter name="XPathFilter" filterType="XPath" filterData="/s12:Envelope/s12:Header/custom:RoundingCalculator = 'rounding'"/>
<!--define an endpoint name filter looking for messages that show up on the virtual regular calculator endpoint-->
<filter name="EndpointNameFilter" filterType="EndpointName" filterData="calculatorEndpoint"/>
<!--define a filter looking for messages that show up with the address prefix. The corresponds to the rounding calc virtual endpoint-->
<filter name="PrefixAddressFilter" filterType="PrefixEndpointAddress" filterData="http://localhost/routingservice/router/rounding/"/>
<!--Set up the custom message filters. In this example, we'll use the example round robin message filter, which alternates between the references-->
<filter name="RoundRobinFilter1" filterType="Custom" customType="CustomFilterAssembly.RoundRobinMessageFilter, CustomFilterAssembly" filterData="group1"/>
<filter name="RoundRobinFilter2" filterType="Custom" customType="CustomFilterAssembly.RoundRobinMessageFilter, CustomFilterAssembly" filterData="group1"/>
</filters>
<filterTables>
<table name="filterTable1">
<entries>
<!--add the filters to the message filter table-->
<!--first look for the custom header, and if we find it, send the message to the rounding calc endpoint-->
<add filterName="XPathFilter" endpointName="roundingCalcEndpoint" priority="2"/>
<!--if the header wasn't there, send the message based on which virtual endpoint it arrived at-->
<!--we determine this through the endpoint name, or through the address prefix-->
<add filterName="EndpointNameFilter" endpointName="regularCalcEndpoint" priority="1"/>
<add filterName="PrefixAddressFilter" endpointName="roundingCalcEndpoint" priority="1"/>
<!--if none of the other filters have matched, this message showed up on the default router endpoint, with no custom header-->
<!--round robin these requests between the two services-->
<add filterName="RoundRobinFilter1" endpointName="regularCalcEndpoint" priority="0"/>
<add filterName="RoundRobinFilter2" endpointName="roundingCalcEndpoint" priority="0"/>
</entries>
</table>
</filterTables>
</routing>
</system.serviceModel>
</configuration>