方法: フィルターの使用
ここでは、複数のフィルターを使用したルーティング構成を作成するために必要な基本手順について説明します。 この例では、メッセージが、電卓サービスの 2 つの実装である regularCalc および roundingCalc にルーティングされます。 これらの実装は両方とも同じ操作をサポートしますが、片方のサービスでは、値を返す前にすべての計算を最も近い整数値に丸めます。 クライアント アプリケーションは、丸めバージョンのサービスを使用するかどうかを示すことができる必要があります。優先するサービスが示されていない場合は、2 つのサービス間でメッセージが負荷分散されます。 次の操作が両方のサービスによって公開されます。
追加
減算
乗算
除算
どちらのサービスも同じ操作を実装するため、アクション フィルターを使用することはできません。メッセージで指定されるアクションが一意にならないためです。 代わりに、メッセージが適切なエンドポイントに必ずルーティングされるための追加作業が必要になります。
一意なデータを特定する
両方のサービス実装が同じ操作を処理し、返すデータを除いて基本的に同一であるため、クライアント アプリケーションから送信されるメッセージに含まれる基本データでは、要求をルーティングする方法を特定できません。 ただし、クライアント アプリケーションでメッセージに一意のヘッダー値を追加すると、この値を使用して、メッセージのルーティング方法を決定できます。
この例では、クライアント アプリケーションで丸め処理を行う電卓を使ってメッセージを処理する必要がある場合に、次のコードを使用してカスタム ヘッダーを追加します。
messageHeadersElement.Add(MessageHeader.CreateHeader("RoundingCalculator", "http://my.custom.namespace/", "rounding"));
これで、XPath フィルターを使用して、メッセージにこのヘッダーが含まれるかどうかを確認し、このヘッダーを持つメッセージを roundCalc サービスにルーティングできるようになります。
さらに、ルーティング サービスが、EndpointName、EndpointAddress、または PrefixEndpointAddress の各フィルターと共に使用できる仮想サービス エンドポイントを 2 つ公開し、クライアント アプリケーションの要求の送信先エンドポイントに基づいて、受信メッセージを特定の電卓の実装に一意にルーティングします。
エンドポイントを定義する
ルーティング サービスで使用するエンドポイントを定義する場合は、最初にクライアントおよびサービスが使用するチャンネルの形状を決める必要があります。 このシナリオでは、両方の送信先サービスで要求/応答パターンが使用されるため、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>
この構成では、ルーティング サービスは 3 つの別個のエンドポイントを公開します。 実行時の選択に応じて、クライアント アプリケーションは、これらのアドレスの 1 つにメッセージを送ります。 "仮想" サービス エンドポイントの 1 つ ("rounding/calculator" または "regular/calculator") に到着したメッセージは、対応する電卓の実装に転送されます。 クライアント アプリケーションが特定のエンドポイントに要求を送信しない場合、メッセージの送信先は標準のエンドポイントになります。 選択されたエンドポイントにかかわらず、クライアント アプリケーションでは、メッセージにカスタム ヘッダーを含めるように選択することで、丸め処理を行う電卓の実装にメッセージを転送するように指定することもできます。
次の例では、ルーティング サービスによるメッセージのルーティング先であるクライアント (送信先) エンドポイントを定義します。
<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 では、"rounding" という値を含むメッセージに RoundingCalculator ヘッダーがあるかどうかが確認されます。 このヘッダーは、メッセージを roundingCalc サービスにルーティングする必要があることを示すために、クライアント側で設定されたものです。
Note
s12 の名前空間プレフィックスは、既定で名前空間のテーブルで定義され、名前空間
http://www.w3.org/2003/05/soap-envelope
を表します。また、2 つの仮想エンドポイントで受信したメッセージがあるかどうかを検索するフィルターも定義する必要があります。 1 つ目の仮想エンドポイントは、"regular/calculator" エンドポイントです。 クライアントは、メッセージを 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 を使用するフィルターを定義して、"rounding/calculator" エンドポイントでメッセージが受信されたかどうかを確認します。
<!--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
となり、このフィルターと一致します。Note
PrefixEndpointAddress フィルターは、一致するメッセージの確認を行う際にホスト名を評価しません。これは、1 つのホストへの参照を表す際に使用できるホスト名にはさまざまな種類があり、そのすべてが、クライアント アプリケーションからホストを参照するための正しい方法であるためです。 たとえば、次の例はすべて、同じホストを参照します。
- localhost
- 127.0.0.1
www.contoso.com
- ContosoWeb01
最後のフィルターでは、標準のエンドポイントで受信する、カスタム ヘッダーのないメッセージのルーティングがサポートされている必要があります。 このシナリオでは、regularCalc サービスと roundingCalc サービスで交互にメッセージが処理されます。 これらのメッセージの "ラウンド ロビン" ルーティングをサポートするには、フィルター インスタンスと処理されるメッセージを 1 対 1 で対応させるカスタム フィルターを使用します。 次のコードでは、RoundRobinMessageFilter のインスタンスを 2 つ定義します。これらは、交互に使用されることを示すために、グループ化されています。
<!-- 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"/>
実行中に、このフィルターの種類は、同じグループで 1 つのコレクションとして構成されている、この種類の定義済みフィルター インスタンスすべてを相次いで使用します。 これにより、このカスタム フィルターで処理されるメッセージでは、
RoundRobinFilter1
およびRoundRobinFilter2
に交互にtrue
が返されます。
フィルター テーブルを定義する
フィルターを特定のクライアント エンドポイントと関連付けるには、それぞれをフィルター テーブル内で指定する必要があります。 このサンプルのシナリオでは、フィルターの優先順位設定も使用しています。この設定は省略可能で、フィルターを処理する順序を指定できます。 優先順位を指定しない場合は、すべてのフィルターが同時に評価されます。
Note
フィルターの優先順位を指定すると、フィルターが処理される順序を制御できますが、ルーティング サービスのパフォーマンスに影響を与える場合があります。 可能な場合は、フィルターの優先順位設定が不要になるようにフィルター ロジックを構築します。
次のコードでは、フィルター テーブルを定義し、以前優先順位 2 でテーブルに定義された "XPathFilter" を追加します。 また、このエントリでは、
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>
フィルターの優先順位を指定すると、優先順位の高いフィルターから評価されます。 指定された優先順位と一致するフィルターが 1 つまたは複数ある場合は、指定した優先順位より低いレベルのフィルターは評価されません。 ここでは、2 が、指定されている優先順位で最高であり、このレベルの唯一のフィルター エントリです。
フィルター エントリは、メッセージが特定のエンドポイントで受信されているかどうかを、エンドポイント名またはアドレスのプレフィックスを調べることによって確認するために定義されています。 次のように入力することで、これらのフィルター エントリの両方をフィルター テーブルに追加し、メッセージがルーティングされる送信先エンドポイントに関連付けます。 前の XPath フィルターがメッセージと一致しない場合にのみ、これらのフィルターが実行されるようにするため、これらのフィルターの優先順位は 1 に設定されています。
<!--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 に設定されているフィルターがメッセージと一致しない場合にのみ評価されます。 また、両方のフィルターに同じ優先順位が指定されているため、これらは同時に評価されます。 これらのフィルターは同時に指定できないため、いずれか一方だけがメッセージと一致する可能性があります。
それまでのどのフィルターとも一致しないメッセージは、一般的なサービス エンドポイントを介して受信されており、ルーティング先を示すヘッダー情報が含まれていないメッセージです。 このようなメッセージはカスタム フィルターによって処理され、2 つの電卓サービス間で負荷分散されます。 次の例では、フィルター テーブルにフィルター エントリを追加する方法を示しています。各フィルターは、2 つの送信先エンドポイントのうちの 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"/>
これらのエントリの優先順位は 0 に設定されているため、これらのフィルターは、優先順位が高いフィルターがメッセージと一致しない場合にのみ評価されます。 また、両方のフィルターに同じ優先順位が指定されているため、これらは同時に評価されます。
既に説明したように、これらのフィルター定義で使用されるカスタム フィルターは、受信するメッセージごとにどちらかのフィルターだけを
true
と評価します。 このフィルターを使用して定義されたフィルターは、同じグループ設定を持つ 2 つのフィルターのみであるため、ルーティング サービスが、regularCalcEndpoint と roundingCalcEndpoint に交互に送信するという効果をもたらします。メッセージをフィルターと照合して評価するには、最初に、フィルター テーブルをメッセージの受信に使用するサービス エンドポイントに関連付ける必要があります。 次の例は、ルーティング動作を使用して、ルーティング テーブルをサービス エンドポイントに関連付ける方法を示しています。
<behaviors> <!--default routing service behavior definition--> <serviceBehaviors> <behavior name="routingConfiguration"> <routing filterTableName="filterTable1" /> </behavior> </serviceBehaviors> </behaviors>
例
構成ファイル全体の一覧を次に示します。
<?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>