サービスのバージョンを管理する方法
このトピックでは、メッセージを同じサービスの異なるバージョンにルーティングするルーティング構成を作成するために必要な、基本的な手順について説明します。この例では、電卓サービスの 2 つのバージョン roundingCalc
(v1) および regularCalc
(v2) にメッセージがルーティングされます。これらの実装は両方とも同じ操作をサポートしますが、古い方のサービス roundingCalc
では、戻る前にすべての計算を最も近い整数値に丸めます。クライアント アプリケーションは、新しい方の regularCalc
サービスを使用するかどうかを示すことが可能である必要があります。
![]() |
---|
メッセージをサービスの特定のバージョンにルーティングするには、ルーティング サービスで、メッセージの内容に基づいて、そのメッセージの転送先を特定できることが必要です。次に示す手法では、クライアントがメッセージ ヘッダーに情報を挿入することでバージョンを指定します。サービスのバージョンを指定するいくつかの手法では、クライアントが追加データを渡す必要はありません。たとえば、サービスの最新のバージョン、または最も互換性のあるバージョンにメッセージをルーティングしたり、ルーターが標準の SOAP エンベロープの一部を使用したりすることもできます。 |
次の操作が両方のサービスによって公開されます。
[追加]
減算
乗算
除算
両方のサービス実装が同じ操作を処理し、返すデータを除いて基本的には同一であるため、クライアント アプリケーションから送信されるメッセージに含まれる基本データでは、要求をルーティングする方法を決定できません。たとえば、両方のサービスの既定のアクションが同じであるために、アクション フィルターを使用できない場合があります。
この問題は、いくつかの方法で解決できます。たとえば、ルーター上でサービスの各バージョン用に特定のエンドポイントを公開したり、メッセージにカスタム ヘッダー要素を追加してサービスのバージョンを示したりできます。どちらの方法でも、受信メッセージをサービスの特定のバージョンに一意にルーティングできますが、異なるサービス バージョンの要求を区別するには、一意のメッセージ コンテンツを使用することをお勧めします。
この例では、クライアント アプリケーションがカスタム ヘッダー CalcVer を要求メッセージに追加します。このヘッダーには、メッセージのルーティング先となるサービスのバージョンを示す値が含まれています。値が 1 の場合は roundingCalc サービス、値が 2 の場合は regularCalc サービスが、メッセージを処理する必要があります。これにより、クライアント アプリケーションは、サービスのどちらのバージョンでメッセージが処理されるかを直接制御できます。カスタム ヘッダーはメッセージ内に含まれる値であるため、1 つのエンドポイントを使用して、サービスの両方のバージョン宛てのメッセージを受信できます。クライアント アプリケーションで次のコードを使用して、このカスタム ヘッダーをメッセージに追加できます。
messageHeadersElement.Add(MessageHeader.CreateHeader("CalcVer", "http://my.custom.namespace/", "2"));
サービスのバージョン管理の実装
サービスによって公開されるサービス エンドポイントを指定することによって、ルーティング サービスの基本的な構成を作成します。次の例では、メッセージの受信に使用される、単一のサービス エンドポイントを定義します。また、
roundingCalc
(v1) サービスおよびregularCalc
(v2) サービスへのメッセージ送信に使用する、クライアント エンドポイントも定義します。<services> <service behaviorConfiguration="routingConfiguration" name="System.ServiceModel.Routing.RoutingService"> <host> <baseAddresses> <add baseAddress="https://localhost/routingservice/router" /> </baseAddresses> </host> <!--Set up the inbound endpoint for the Routing Service--> <endpoint address="calculator" binding="wsHttpBinding" name="routerEndpoint" contract="System.ServiceModel.Routing.IRequestReplyRouter" /> </service> </services> <client> <!--set up the destination endpoints--> <endpoint name="regularCalcEndpoint" address="net.tcp://localhost:9090/servicemodelsamples/service/" binding="netTcpBinding" contract="*" /> <endpoint name="roundingCalcEndpoint" address="https://localhost:8080/servicemodelsamples/service/" binding="wsHttpBinding" contract="*" /> </client>
送信先エンドポイントにメッセージをルーティングするのに使用するフィルターを定義します。この例では、XPath フィルターを使用してカスタム ヘッダー CalcVer の値を検出し、メッセージのルーティング先となるバージョンを特定します。XPath フィルターは、CalcVer ヘッダーを含まないメッセージの検出にも使用します。次の例では、必要なフィルターおよび名前空間のテーブルを定義します。
<!-- 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 containing a value of 2--> <filter name="XPathFilterRegular" filterType="XPath" filterData="sm:header()/custom:CalcVer = '2'"/> <!--define an xpath message filter to look for the custom header containing a value of 1--> <filter name="XPathFilterRounding" filterType="XPath" filterData="sm:header()/custom:CalcVer = '1'"/> <!--define an xpath message filter to look for messages that do not contain the custom header--> <filter name="XPathFilterNoHeader" filterType="XPath" filterData="count(sm:header()/custom:CalcVer)=0"/> </filters
注 :
s12 の名前空間プレフィックスは、既定では名前空間のテーブルで定義され、名前空間 "http://www.w3.org/2003/05/soap-envelope" を表します。 各フィルターをクライアント エンドポイントと関連付けるフィルター テーブルを定義します。値が 1 の CalcVer ヘッダーがメッセージに含まれる場合は、regularCalc サービスに送信されます。ヘッダーに値 2 が含まれる場合は、roundingCalc サービスに送信されます。ヘッダーがない場合、メッセージは regularCalc にルーティングされます。
次のコードでは、フィルター テーブルを定義し、前に定義されたフィルターを追加します。
<filterTables> <filterTable name="filterTable1"> <!--add the filters to the message filter table--> <!--look for the custom header = 1, and if we find it, send the message to the rounding calc endpoint--> <add filterName="XPathFilterRounding" endpointName="roundingCalcEndpoint"/> <!--look for the custom header = 2, and if we find it, send the message to the rounding calc endpoint--> <add filterName="XPathFilterRegular" endpointName="regularCalcEndpoint"/> <!--look for the absence of the custom header, and if it is not present, assume the v1 endpoint--> <add filterName="XPathFilterNoHeader" endpointName="roundingCalcEndpoint"/> </filterTable> </filterTables>
フィルター テーブルに含まれているフィルターと照合して受信メッセージを評価するには、ルーティング動作を使用して、フィルター テーブルをサービス エンドポイントと関連付ける必要があります。次の例は、filterTable1 をサービス エンドポイントと関連付ける方法を示しています。
<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="https://localhost/routingservice/router" />
</baseAddresses>
</host>
<!--Set up the inbound endpoint for the Routing Service-->
<endpoint address="calculator"
binding="wsHttpBinding"
name="routerEndpoint"
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="https://localhost:8080/servicemodelsamples/service/"
binding="wsHttpBinding"
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 containing a value of 2-->
<filter name="XPathFilterRegular" filterType="XPath"
filterData="sm:header()/custom:CalcVer = '2'"/>
<!--define an xpath message filter to look for the
custom header containing a value of 1-->
<filter name="XPathFilterRounding" filterType="XPath"
filterData="sm:header()/custom:CalcVer = '1'"/>
<!--define an xpath message filter to look for
messages that do not contain the custom header-->
<filter name="XPathFilterNoHeader" filterType="XPath"
filterData="count(sm:header()/custom:CalcVer)=0"/>
</filters>
<filterTables>
<filterTable name="filterTable1">
<!--add the filters to the message filter table-->
<!--look for the custom header = 1, and if we find it,
send the message to the rounding calc endpoint-->
<add filterName="XPathFilterRounding" endpointName="roundingCalcEndpoint"/>
<!--look for the custom header = 2, and if we find it,
send the message to the rounding calc endpoint-->
<add filterName="XPathFilterRegular" endpointName="regularCalcEndpoint"/>
<!--look for the absence of the custom header, and if
it is not present, assume the v1 endpoint-->
<add filterName="XPathFilterNoHeader" endpointName="roundingCalcEndpoint"/>
</filterTable>
</filterTables>
</routing>
</system.serviceModel>
</configuration>
クライアント アプリケーションの全体の一覧を次に示します。
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
namespace Microsoft.Samples.AdvancedFilters
{
//The service contract is defined in generatedClient.cs, generated from the service by the svcutil tool.
//Client implementation code.
class Client
{
static void Main()
{
//Print out the welcome text
Console.WriteLine("This sample routes the Calculator Sample through the new WCF RoutingService");
Console.WriteLine("Wait for all the services to indicate that they've started, then press");
Console.WriteLine("<ENTER> to start the client.");
while (Console.ReadLine() != "quit")
{
//Offer the Address configuration for the client
Console.WriteLine("");
Console.WriteLine("Welcome to the Calculator Client!");
EndpointAddress epa;
//set the default address as the general router endpoint
epa = new EndpointAddress("https://localhost/routingservice/router/calculator");
//set up the CalculatorClient with the EndpointAddress, the WSHttpBinding, and the ICalculator contract
//We use the WSHttpBinding so that the outgoing has a message envelope, which we'll manipulate in a minute
CalculatorClient client = new CalculatorClient(new WSHttpBinding(), epa);
//client.Endpoint.Contract = ContractDescription.GetContract(typeof(ICalculator));
//Ask the customer if they want to add a custom header to the outgoing message.
//The Router will look for this header, and if so ignore the endpoint the message was
//received on, and instead direct the message to the RoundingCalcService.
Console.WriteLine("");
Console.WriteLine("Which calculator service should be used?");
Console.WriteLine("Enter 1 for the rounding calculator, 2 for the regular calculator.");
Console.WriteLine("[1] or [2]?");
string header = Console.ReadLine();
//get the current operationContextScope from the client's inner channel
using (OperationContextScope ocs = new OperationContextScope((client.InnerChannel)))
{
//get the outgoing message headers element (collection) from the context
MessageHeaders messageHeadersElement = OperationContext.Current.OutgoingMessageHeaders;
//if they wanted to create the header, go ahead and add it to the outgoing message
if (header != null && (header=="1" || header=="2"))
{
//create a new header "RoundingCalculator", no specific namespace, and set the value to
//the value of header.
//the Routing Service will look for this header in order to determine if the message
//should be routed to the RoundingCalculator
messageHeadersElement.Add(MessageHeader.CreateHeader("CalcVer", "http://my.custom.namespace/", header));
}
else //incorrect choice, no header added
{
Console.WriteLine("Incorrect value entered, not adding a header");
}
//call the client operations
CallClient(client);
}
//close the client to clean it up
client.Close();
Console.WriteLine();
Console.WriteLine("Press <ENTER> to run the client again or type 'quit' to quit.");
}
}
private static void CallClient(CalculatorClient client)
{
Console.WriteLine("");
Console.WriteLine("Sending!");
// Call the Add service operation.
double value1 = 100.00D;
double value2 = 15.99D;
double result = client.Add(value1, value2);
Console.WriteLine("Add({0},{1}) = {2}", value1, value2, result);
// Call the Subtract service operation.
value1 = 145.00D;
value2 = 76.54D;
result = client.Subtract(value1, value2);
Console.WriteLine("Subtract({0},{1}) = {2}", value1, value2, result);
// Call the Multiply service operation.
value1 = 9.00D;
value2 = 81.25D;
result = client.Multiply(value1, value2);
Console.WriteLine("Multiply({0},{1}) = {2}", value1, value2, result);
// Call the Divide service operation.
value1 = 22.00D;
value2 = 7.00D;
result = client.Divide(value1, value2);
Console.WriteLine("Divide({0},{1}) = {2}", value1, value2, result);
}
}
}