路由简介

路由服务提供一个通用的可插入 SOAP 中介,能够基于消息内容路由消息。 使用路由服务,可以创建复杂的路由逻辑,以便实现服务聚合、服务版本控制、优先级路由和多播路由等方案。 路由服务还提供错误处理,使你可以设置备份终结点的列表,如果在发送到主目标终结点时发生故障,则会向这些终结点发送消息。

本主题适用于路由服务的新用户,涵盖路由服务的基本配置和托管。

配置

路由服务作为 WCF 服务实现,该服务公开一个或多个服务终结点,这些终结点接收来自客户端应用程序的消息并将消息路由到一个或多个目标终结点。 该服务提供应用于服务公开的服务终结点的 RoutingBehavior。 此行为用于配置服务运行方式的各个方面。 为方便使用配置文件时配置,在 RoutingBehavior 上指定参数。 在基于代码的方案中,这些参数将指定为对象的一 RoutingConfiguration 部分,然后可以传递给 RoutingBehavior

在启动时,该行为向客户端终结点添加 SoapProcessingBehavior,用于对消息执行 SOAP 处理。 这样,路由服务便可以将消息传输到以下终结点:这些终结点所要求的 MessageVersion 不同于已接收消息的终结点RoutingBehavior 还会注册服务扩展RoutingExtension,该扩展提供一个接入点,用于在运行时修改路由服务配置。

RoutingConfiguration 类提供配置和更新路由服务的一致方式。 它包含充当路由服务的设置的参数,用于在服务启动时配置 RoutingBehavior ,或传递给 RoutingExtension 以在运行时修改路由配置。

用于执行基于内容的消息路由的路由逻辑通过将多个 MessageFilter 对象组合到筛选器表(MessageFilterTable<TFilterData> 对象)中来定义。 传入消息根据筛选器表中包含的消息筛选器进行评估,并针对与消息匹配的每个 MessageFilter 进行评估,将其转发到目标终结点。 应用于路由消息的筛选器表是通过使用配置中的 RoutingBehavior 或通过代码使用 RoutingConfiguration 对象指定的。

定义终结点

虽然似乎应该通过定义要使用的路由逻辑来启动配置,但第一步实际上是要确定将消息路由到的终结点的形状。 路由服务使用协定来定义用于接收和发送消息的通道的形状,因此输入通道的形状必须与输出通道的形状匹配。 例如,如果要路由到使用请求-回复模式的终结点,则必须在入站终结点上使用兼容的协定,例如IRequestReplyRouter

这意味着,如果目标终结点使用包含多种通信模式(例如混合单向和双向操作)的协议,则无法创建一个可以接收消息并将其路由到所有这些通信模式的服务终结点。 必须确定哪些终结点具有兼容的形状,并定义一个或多个服务终结点,这些终结点将用于接收要路由到目标终结点的消息。

注释

当使用指定了多种通信模式(例如,单向和双向混合运行模式)的协定时,一种解决方法是在路由服务中使用双工协定,例如,IDuplexSessionRouter。 这意味着绑定必须支持双工通信,而这并非在所有情况下都能够实现。 在无法实现此目的的情况下,可能需要将通信分解为多个终结点或修改应用程序。

有关路由协定的详细信息,请参阅 路由协定

定义服务终结点后,可以使用 RoutingBehavior 将特定的 RoutingConfiguration 与终结点相关联。 使用配置文件配置路由服务时, RoutingBehavior 用于指定包含用于处理此终结点上收到的消息的路由逻辑的筛选器表。 如果要以编程方式配置路由服务,则可以使用 RoutingConfiguration 指定筛选器表。

以下示例定义了路由服务以编程方式和通过配置文件使用的服务和客户端终结点。

<services>
  <!--ROUTING SERVICE -->
  <service behaviorConfiguration="routingData"
            name="System.ServiceModel.Routing.RoutingService">
    <host>
      <baseAddresses>
        <add baseAddress="http://localhost:8000/routingservice/router"/>
      </baseAddresses>
    </host>
    <!-- Define the service endpoints that are receive messages -->
    <endpoint address=""
              binding="wsHttpBinding"
              name="reqReplyEndpoint"
              contract="System.ServiceModel.Routing.IRequestReplyRouter" />
  </service>
</services>
<behaviors>
  <serviceBehaviors>
    <behavior name="routingData">
      <serviceMetadata httpGetEnabled="True"/>
      <!-- Add the RoutingBehavior and specify the Routing Table to use -->
      <routing filterTableName="routingTable1" />
    </behavior>
  </serviceBehaviors>
</behaviors>
<client>
<!-- Define the client endpoint(s) to route messages to -->
  <endpoint name="CalculatorService"
            address="http://localhost:8000/servicemodelsamples/service"
            binding="wsHttpBinding" contract="*" />
</client>
//set up some communication defaults
string clientAddress = "http://localhost:8000/servicemodelsamples/service";
string routerAddress = "http://localhost:8000/routingservice/router";
Binding routerBinding = new WSHttpBinding();
Binding clientBinding = new WSHttpBinding();
//add the endpoint the router uses to receive messages
serviceHost.AddServiceEndpoint(
     typeof(IRequestReplyRouter),
     routerBinding,
     routerAddress);
//create the client endpoint the router routes messages to
ContractDescription contract = ContractDescription.GetContract(
     typeof(IRequestReplyRouter));
ServiceEndpoint client = new ServiceEndpoint(
     contract,
     clientBinding,
     new EndpointAddress(clientAddress));
//create a new routing configuration object
RoutingConfiguration rc = new RoutingConfiguration();
….
rc.FilterTable.Add(new MatchAllMessageFilter(), endpointList);
//attach the behavior to the service host
serviceHost.Description.Behaviors.Add(
     new RoutingBehavior(rc));

此示例将路由服务配置为公开具有地址的 http://localhost:8000/routingservice/router单个终结点,该终结点用于接收要路由的消息。 由于消息路由到请求-回复终结点,因此服务终结点使用 IRequestReplyRouter 协定。 此配置还定义了一个单个客户端终结点 http://localhost:8000/servicemodelsample/service,用于路由消息。 名为“routingTable1”的筛选器表(未显示)包含用于路由消息的路由逻辑,并使用 RoutingBehavior (配置文件)或 RoutingConfiguration (用于编程配置)与服务终结点相关联。

路由逻辑

若要定义用于路由消息的路由逻辑,必须确定传入消息中包含的能够被唯一识别和处理的数据。 例如,如果您路由的所有目标终结点共享相同的 SOAP 动作,则消息中包含的动作值不是确定消息应路由到哪个特定终结点的良好指示器。 如果必须唯一地将消息路由到一个特定终结点,则应根据唯一标识消息路由到的目标终结点的数据进行筛选。

路由服务提供多个 MessageFilter 实现,用于检查消息中的特定值,例如地址、作、终结点名称,甚至 XPath 查询。 如果这些实现都不符合你的需求,则可以创建自定义 MessageFilter 实现。 有关消息筛选器和路由服务使用的实现的比较的详细信息,请参阅 消息筛选器选择筛选器

将多个消息筛选器组织到筛选器表中,这些筛选器将每个 MessageFilter 与目标终结点相关联。 (可选)筛选器表还可用于指定路由服务在传输失败时尝试将消息发送到的备份终结点列表。

默认情况下,筛选器表中的所有消息筛选器会被同时评估;但是,您可以通过指定一个Priority来按特定顺序评估消息筛选器。 首先评估优先级最高的所有条目,如果匹配项位于较高优先级级别,则不会评估优先级较低的消息筛选器。 有关筛选器表的详细信息,请参阅 消息筛选器

下面的示例使用 MatchAllMessageFilter,它对所有消息的计算结果均为 true。 此 MessageFilter 将添加到“routingTable1”筛选器表中,该表将 MessageFilter 与名为“CalculatorService”的客户端终结点相关联。 然后 RoutingBehavior 指定此表用于路由由服务终结点处理的消息。

<behaviors>
  <serviceBehaviors>
    <behavior name="routingData">
      <serviceMetadata httpGetEnabled="True"/>
      <!-- Add the RoutingBehavior and specify the Routing Table to use -->
      <routing filterTableName="routingTable1" />
    </behavior>
  </serviceBehaviors>
</behaviors>
<!--ROUTING SECTION -->
<routing>
  <filters>
    <filter name="MatchAllFilter1" filterType="MatchAll" />
  </filters>
  <filterTables>
    <table name="routingTable1">
      <filters>
        <add filterName="MatchAllFilter1" endpointName="CalculatorService" />
      </filters>
    </table>
  </filterTables>
</routing>
//create a new routing configuration object
RoutingConfiguration rc = new RoutingConfiguration();
//create the endpoint list that contains the endpoints to route to
//in this case we have only one
List<ServiceEndpoint> endpointList = new List<ServiceEndpoint>();
endpointList.Add(client);
//add a MatchAll filter to the Router's filter table
//map it to the endpoint list defined earlier
//when a message matches this filter, it is sent to the endpoint contained in the list
rc.FilterTable.Add(new MatchAllMessageFilter(), endpointList);

注释

默认情况下,路由服务仅评估消息的标头。 若要允许筛选器访问消息正文,必须设置为 RouteOnHeadersOnlyfalse

多播

尽管许多路由服务配置使用仅将消息路由到一个特定终结点的独占筛选器逻辑,但可能需要将给定消息路由到多个目标终结点。 若要将消息多播到多个目标,必须满足以下条件:

  • 通道形状不能为请求-答复(但可以为单向或双工),因为客户端应用程序在响应请求时只能接收一个答复。

  • 多个筛选器在评估消息时必须返回 true

如果满足这些条件,则会将消息路由到所有评估结果为 true的所有筛选器的所有终结点。 以下示例定义路由配置,如果消息中的终结点地址为 http://localhost:8000/routingservice/router/rounding,则会导致消息路由到两个终结点。

<!--ROUTING SECTION -->
<routing>
  <filters>
    <filter name="MatchAllFilter1" filterType="MatchAll" />
    <filter name="RoundingFilter1" filterType="EndpointAddress"
            filterData="http://localhost:8000/routingservice/router/rounding" />
  </filters>
  <filterTables>
    <table name="routingTable1">
      <filters>
        <add filterName="MatchAllFilter1" endpointName="CalculatorService" />
        <add filterName="RoundingFilter1" endpointName="RoundingCalcService" />
      </filters>
    </table>
  </filterTables>
</routing>
rc.FilterTable.Add(new MatchAllMessageFilter(), calculatorEndpointList);
rc.FilterTable.Add(new EndpointAddressMessageFilter(new EndpointAddress(
    "http://localhost:8000/routingservice/router/rounding")),
    roundingCalcEndpointList);

SOAP 处理

为了支持在不同协议之间路由消息,RoutingBehavior 会默认将 SoapProcessingBehavior 添加到消息路由的所有客户端端点。 在将消息路由到终结点之前,此行为会自动创建新的 MessageVersion ,并为任何响应文档创建兼容的 MessageVersion ,然后再将其返回到请求客户端应用程序。

为出站消息创建新的 MessageVersion 的步骤如下:

请求处理

  • 获取出站绑定/通道的 MessageVersion

  • 获取原始消息的正文读取器。

  • 使用相同操作、正文读取器和新的 MessageVersion 创建新的消息

  • 如果 Addressing != Addressing.None,请将 To、From、FaultTo 和 RelatesTo 标头复制到新消息。

  • 将所有消息属性复制到新邮件。

  • 存储处理响应时要使用的原始请求消息。

  • 返回新请求消息。

响应处理

  • 获取原始请求消息的 MessageVersion

  • 获取接收的响应消息的正文读取器。

  • 使用与原始请求消息相同的操作、正文读取器和MessageVersion来创建新的响应消息。

  • 如果 Addressing != Addressing.None,请将 To、From、FaultTo 和 RelatesTo 标头复制到新消息。

  • 将消息属性复制到新邮件。

  • 返回新的响应消息。

默认情况下, SoapProcessingBehavior 会在服务启动时自动添加到客户端终结点 RoutingBehavior ;但是,可以使用该属性控制 SOAP 处理是否添加到所有客户端终结点 SoapProcessingEnabled 。 如果需要更精细地控制 SOAP 处理,还可以将行为直接添加到特定终结点,并在终结点级别启用或禁用此行为。

注释

如果对需要与原始请求消息不同的 MessageVersion 的终结点禁用 SOAP 处理,则必须提供自定义机制,以便在将消息发送到目标终结点之前执行所需的任何 SOAP 修改。

在以下示例中, soapProcessingEnabled 属性用于防止 SoapProcessingBehavior 自动添加到所有客户端终结点。

<behaviors>
  <!--default routing service behavior definition-->
  <serviceBehaviors>
    <behavior name="routingConfiguration">
      <routing filterTableName="filterTable1" soapProcessingEnabled="false"/>
    </behavior>
  </serviceBehaviors>
</behaviors>
//create the default RoutingConfiguration
RoutingConfiguration rc = new RoutingConfiguration();
rc.SoapProcessingEnabled = false;

动态配置

添加其他客户端终结点或需要修改用于路由消息的筛选器时,必须有一种方法在运行时动态更新配置,以防止将服务中断到当前通过路由服务接收消息的终结点。 修改配置文件或主机应用程序的代码并不总是足够,因为任一方法都需要回收应用程序,这将导致当前传输中的任何消息丢失,以及等待服务重启时可能会停机。

只能以编程方式修改 RoutingConfiguration 。 虽然最初可以使用配置文件来配置服务,但只能在运行时修改配置。您需要构造一个新的 RoutingConfiguration,并将其作为参数传递给由服务扩展提供的 ApplyConfiguration 方法。 当前传输中的任何消息将继续使用以前的配置进行路由,而调用 ApplyConfiguration 后收到的消息将使用新配置。 以下示例演示如何创建路由服务的实例,然后修改配置。

RoutingConfiguration routingConfig = new RoutingConfiguration();
routingConfig.RouteOnHeadersOnly = true;
routingConfig.FilterTable.Add(new MatchAllMessageFilter(), endpointList);
RoutingBehavior routing = new RoutingBehavior(routingConfig);
routerHost.Description.Behaviors.Add(routing);
routerHost.Open();
// Construct a new RoutingConfiguration
RoutingConfiguration rc2 = new RoutingConfiguration();
ServiceEndpoint clientEndpoint = new ServiceEndpoint();
ServiceEndpoint clientEndpoint2 = new ServiceEndpoint();
// Add filters to the FilterTable in the new configuration
rc2.FilterTable.add(new MatchAllMessageFilter(),
       new List<ServiceEndpoint>() { clientEndpoint });
rc2.FilterTable.add(new MatchAllMessageFilter(),
       new List<ServiceEndpoint>() { clientEndpoint2 });
rc2.RouteOnHeadersOnly = false;
// Apply the new configuration to the Routing Service hosted in
routerHost.routerHost.Extensions.Find<RoutingExtension>().ApplyConfiguration(rc2);

注释

以这种方式更新路由服务时,只能传递新配置。 无法仅修改当前配置的选择元素或将新条目追加到当前配置;必须创建并传递替换现有配置的新配置。

注释

使用上一配置打开的任何会话将继续使用以前的配置。 新配置仅供新会话使用。

错误处理

如果在尝试发送消息时遇到任何 CommunicationException,则会进行错误处理。 这些异常通常表示尝试与定义的客户端终结点(例如EndpointNotFoundExceptionServerTooBusyException,或CommunicationObjectFaultedException)通信时遇到问题。 错误处理代码还会捕获异常,并在出现 TimeoutException 时尝试重试发送,这是另一种不从 CommunicationException 派生的常见异常。

当发生上述异常之一时,路由服务将切换到备份终结点列表。 如果所有备份终结点因通信失败而失败,或者终结点返回指示目标服务中的故障的异常,则路由服务会将错误返回到客户端应用程序。

注释

错误处理功能捕获并处理尝试发送消息时以及尝试关闭通道时发生的异常。 错误处理代码不打算检测或处理与之通信的应用程序终结点创建的异常;由服务引发的异常会在路由服务中显示为 FaultException ,并反向传递到客户端。

如果在路由服务尝试中继消息时出现错误,则可能会在客户端收到 FaultException,而不是在缺少路由服务时通常收到的 EndpointNotFoundException。 因此,路由服务可能会屏蔽异常,除非检查嵌套异常,否则不会提供完全透明度。

跟踪异常

将消息发送到列表中的终结点失败时,路由服务将跟踪生成的异常数据,并将异常详细信息附加为名为 Exceptions 的消息属性。 这会保留异常数据,并允许用户通过消息检查器以编程方式访问。 异常数据按每条消息存储在字典中,该字典将终结点名称映射到在尝试向其发送消息时遇到的异常详细信息。

备份终结点

筛选器表中的各个筛选器条目可以选择指定一个备份终结点列表,如果在向主终结点发送消息时出现传输故障,则会使用这些备份终结点。 如果发生此类故障,路由服务将尝试将消息传输到备份终结点列表中的第一个条目。 如果此发送尝试也遇到传输失败,则会尝试备份列表中的下一终结点。 路由服务会继续将消息传送到列表中的每个终结点,直到消息被成功接收、所有终结点返回传输失败,或某个终结点返回一个非传输失败。

以下示例将路由服务配置为使用备份列表。

<routing>
  <filters>
    <!-- Create a MatchAll filter that catches all messages -->
    <filter name="MatchAllFilter1" filterType="MatchAll" />
  </filters>
  <filterTables>
    <!-- Set up the Routing Service's Message Filter Table -->
    <filterTable name="filterTable1">
        <!-- Add an entry that maps the MatchAllMessageFilter to the dead destination -->
        <!-- If that endpoint is down, tell the Routing Service to try the endpoints -->
        <!-- Listed in the backupEndpointList -->
        <add filterName="MatchAllFilter1" endpointName="deadDestination" backupList="backupEndpointList"/>
    </filterTable>
  </filterTables>
  <!-- Create the backup endpoint list -->
  <backupLists>
    <!-- Add an endpoint list that contains the backup destinations -->
    <backupList name="backupEndpointList">
      <add endpointName="realDestination" />
      <add endpointName="backupDestination" />
    </backupList>
  </backupLists>
</routing>
//create the endpoint list that contains the service endpoints we want to route to
List<ServiceEndpoint> backupList = new List<ServiceEndpoint>();
//add the endpoints in the order that the Routing Service should contact them
//first add the endpoint that we know is down
//clearly, normally you wouldn't know that this endpoint was down by default
backupList.Add(fakeDestination);
//then add the real Destination endpoint
//the Routing Service attempts to send to this endpoint only if it
//encounters a TimeOutException or CommunicationException when sending
//to the previous endpoint in the list.
backupList.Add(realDestination);
//add the backupDestination endpoint
//the Routing Service attempts to send to this endpoint only if it
//encounters a TimeOutException or CommunicationsException when sending
//to the previous endpoints in the list
backupList.Add(backupDestination);
//create the default RoutingConfiguration option
RoutingConfiguration rc = new RoutingConfiguration();
//add a MatchAll filter to the Routing Configuration's filter table
//map it to the list of endpoints defined above
//when a message matches this filter, it is sent to the endpoints in the list in order
//if an endpoint is down or does not respond (which the first endpoint won't
//since the client does not exist), the Routing Service automatically moves the message
//to the next endpoint in the list and try again.
rc.FilterTable.Add(new MatchAllMessageFilter(), backupList);

支持的错误模式

下表描述了与使用备份终结点列表兼容的模式,以及描述特定模式的错误处理详细信息的说明。

图案 会话 交易 接收上下文 支持的备份列表 注释
单向 是的 尝试在备份终结点上重新发送消息。 如果正在多播此消息,则仅将故障通道上的消息移至其备份目标。
单向 ✔️ 引发异常,并回滚事务。
单向 ✔️ 是的 尝试在备份终结点上重新发送消息。 成功接收消息后,完成所有接收上下文。 如果任何终结点未成功接收消息,则不会完成接收上下文。

当此消息是多播时,仅当至少一个终结点(主或备份)成功接收消息时,接收上下文才会完成。 如果任何多播路径中的任何终结点都未成功接收消息,请不要完成接收上下文。
单向 ✔️ ✔️ 是的 中止上一个事务,创建新事务,并重新发送所有消息。 遇到错误的消息将传输到备份目标。

在创建已成功完成其中的所有传输的事务之后,完成接收上下文并提交该事务。
单向 ✔️ 是的 尝试在备份终结点上重新发送消息。 在多播情况下,仅向备份目标重新发送已遇到错误或其会话关闭失败的消息。
单向 ✔️ ✔️ 引发异常,并回滚事务。
单向 ✔️ ✔️ 是的 尝试在备份终结点上重新发送消息。 在所有消息发送完成且无错误后,会话指示不再发送任何消息,路由服务会成功关闭所有出站会话通道(s),所有接收上下文都已完成,并且入站会话通道已关闭。
单向 ✔️ ✔️ ✔️ 是的 中止当前事务并创建新的。 重新发送会话中的所有以前的消息。 在创建已成功发送其中的所有消息的事务,并且会话指示没有任何其他消息之后,将关闭所有出站会话通道,使用事务完成所有接收上下文,关闭入站会话通道,并提交该事务。

当会话进行多播时,没有错误的消息将发送到原目标,而有错误的消息则被发送到备份目标。
双向 是的 发送到备份目标。 通道返回响应消息后,将响应返回到原始客户端。
双向 ✔️ 是的 将通道上的所有消息发送到备份目标。 通道返回响应消息后,将响应返回到原始客户端。
双向 ✔️ 引发异常,并回滚事务。
双向 ✔️ ✔️ 引发异常,并回滚事务。
双工 当前不支持非会话双工通信。
双工 ✔️ 是的 发送到备份目标。

托管

由于路由服务作为 WCF 服务实现,因此它必须自承载在应用程序内,或者由 IIS 或 WAS 托管。 建议将路由服务托管在 IIS、WAS 或 Windows 服务应用程序中,以利用这些托管环境中提供的自动启动和生命周期管理功能。

以下示例演示如何在应用程序中托管路由服务。

using (ServiceHost serviceHost =
                new ServiceHost(typeof(RoutingService)))

若要在 IIS 或 WAS 中托管路由服务,必须创建服务文件(.svc)或使用基于配置的服务激活。 使用服务文件时,必须使用 Service 参数指定 RoutingService 。 以下示例包含一个示例服务文件,该文件可用于使用 IIS 或 WAS 托管路由服务。

<%@ ServiceHost Language="C#" Debug="true" Service="System.ServiceModel.Routing.RoutingService,
     System.ServiceModel.Routing, version=4.0.0.0, Culture=neutral,
     PublicKeyToken=31bf3856ad364e35" %>

路由服务和模拟

WCF 路由服务可用于身份模拟,以发送和接收消息。 模拟的所有常用 Windows 约束将适用。 如果在开发自己的服务时需要设置服务或帐户权限以进行身份模拟,那么在使用路由服务进行身份模拟时,您需要执行相同的步骤。 有关详细信息,请参阅委派和模拟

将模拟用于路由服务要求在 ASP.NET 兼容模式下使用 ASP.NET 模拟,或使用已配置为允许模拟的 Windows 凭据。 有关 ASP.NET 兼容模式的详细信息,请参阅 WCF 服务和 ASP.NET

警告

WCF 路由服务不支持通过基本身份验证进行伪装。

若要对路由服务使用 ASP.NET 模拟,请对服务托管环境启用 ASP.NET 兼容性模式。 路由服务已被标记为允许 ASP.NET 兼容模式,并且模拟会自动启用。 在 ASP.NET 与路由服务的集成中,模拟是唯一支持的用途。

若要将 Windows 凭据模拟用于路由服务,需要同时配置凭据和服务。 客户端凭据对象(可从WindowsClientCredential访问ChannelFactory)定义了一个AllowedImpersonationLevel属性,该属性必须设置为允许模拟。 最后,您需要在服务上配置 ServiceAuthorizationBehavior 行为,以便将 ImpersonateCallerForAllOperations 设置为 true。 路由服务使用此标志来确定是否创建客户端以转发启用身份模拟的消息。

另请参阅