Procédure : Contrôle des versions du service
Cette rubrique présente les étapes de base nécessaires pour créer une configuration de routage qui route les messages vers des versions différentes du même service. Dans cet exemple, les messages sont routés vers deux versions différentes d'un service de calculatrice, roundingCalc
(v1) et regularCalc
(v2). Les deux implémentations prennent en charge les mêmes opérations ; toutefois, le service le plus ancien, roundingCalc
, arrondit tous les calculs à la valeur entière la plus proche avant de les retourner. Une application cliente doit être en mesure d'indiquer s'il faut utiliser le service regularCalc
plus récent.
Avertissement
Pour router un message vers une version spécifique du service, le service de routage doit être en mesure de déterminer la destination du message en fonction du contenu de message. Dans la méthode présentée ci-dessous, le client spécifie la version en insérant des informations dans un en-tête de message. Il existe des méthodes de contrôle des versions de service qui ne requièrent pas que les clients transmettent des données supplémentaires. Par exemple, un message peut être routé vers la version d'un service la plus récente ou la plus compatible ou le routeur peut utiliser une partie de l'enveloppe SOAP standard.
Les opérations exposées par les deux services sont :
- Ajouter
- Soustraire
- Multiplier
- Diviser
Comme les deux implémentations de service gèrent les mêmes opérations et sont pour l'essentiel identiques en dehors des données qu'elles retournent, les données de base contenues dans les messages envoyés à partir d'applications clientes ne sont pas assez uniques pour vous permettre de déterminer comment router la demande. Par exemple, les filtres Action ne peuvent pas être utilisés, parce que les actions par défaut des deux services sont identiques.
Il existe plusieurs solutions, par exemple exposer un point de terminaison spécifique sur le routeur pour chaque version du service ou ajouter au message un élément d'en-tête personnalisé pour indiquer la version du service. Chacune de ces approches vous permet de router de manière unique des messages entrants vers une version spécifique du service, cependant la méthode recommandée pour différencier les demandes destinées à différentes versions de service est d'utiliser un contenu de message unique.
Dans cet exemple, l'application cliente ajoute l'en-tête personnalisé « CalcVer » au message de demande. Cet en-tête contiendra une valeur qui indique la version du service vers lequel le message doit être routé. La valeur « 1 » indique que le message doit être traité par le service roundingCalc, tandis que la valeur « 2 » indique le service regularCalc. Cela permet à l'application cliente de contrôler directement quelle version du service traitera le message. Puisque l'en-tête personnalisé est une valeur contenue dans le message, vous pouvez utiliser un seul point de terminaison pour recevoir les messages destinés aux deux versions du service. Le code suivant peut être utilisé dans l'application cliente pour ajouter cet en-tête personnalisé au message :
messageHeadersElement.Add(MessageHeader.CreateHeader("CalcVer", "http://my.custom.namespace/", "2"));
Implémenter le contrôle des versions du service
Créez la configuration du service de routage de base, en spécifiant le point de terminaison de service exposé par le service. L'exemple suivant définit un point de terminaison de service unique, qui sera utilisé pour recevoir des messages. Il définit également les points de terminaison clients qui seront utilisés pour envoyer des messages aux services
roundingCalc
(v1) etregularCalc
(v2).<services> <service behaviorConfiguration="routingConfiguration" name="System.ServiceModel.Routing.RoutingService"> <host> <baseAddresses> <add baseAddress="http://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="http://localhost:8080/servicemodelsamples/service/" binding="wsHttpBinding" contract="*" /> </client>
Définissez les filtres utilisés pour router les messages vers les points de terminaison de destination. Dans cet exemple, le filtre XPath est utilisé pour détecter la valeur de l’en-tête personnalisé « CalcVer », afin de déterminer la version vers laquelle le message doit être routé. Un filtre XPath est également utilisé pour détecter les messages ne contenant pas d’en-tête « CalcVer ». L'exemple suivant définit les filtres et la table d'espace de noms nécessaires.
<!-- 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>
Notes
Le préfixe d’espace de noms s12 est défini par défaut dans la table d’espace de noms et représente l’espace de noms
http://www.w3.org/2003/05/soap-envelope
.Définissez la table de filtres, qui associe chaque filtre à un point de terminaison client. Si le message contient l’en-tête « CalcVer » avec la valeur 1, il sera envoyé au service regularCalc. Si l'en-tête contient la valeur 2, il sera envoyé au service roundingCalc. Si aucun en-tête n'est présent, le message sera routé vers regularCalc.
Les éléments suivants définissent la table de filtres et ajoutent les filtres définis précédemment.
<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>
Pour évaluer les messages entrants en fonction des filtres contenus dans la table de filtres, vous devez associer la table de filtres aux points de terminaison de service à l'aide du comportement de routage. L’exemple suivant montre l’association de
filterTable1
aux points de terminaison de service :<behaviors> <!--default routing service behavior definition--> <serviceBehaviors> <behavior name="routingConfiguration"> <routing filterTableName="filterTable1" /> </behavior> </serviceBehaviors> </behaviors>
Exemple 1
L'intégralité du fichier de configuration est présentée ci-dessous.
<?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 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="http://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>
Exemple 2
L'intégralité de l'application cliente est présentée ci-dessous.
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("http://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.
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);
}
}
}