Anvisningar: Versionshantering av tjänster
Det här avsnittet beskriver de grundläggande steg som krävs för att skapa en routningskonfiguration som dirigerar meddelanden till olika versioner av samma tjänst. I det här exemplet dirigeras meddelanden till två olika versioner av en kalkylatortjänst, roundingCalc
(v1) och regularCalc
(v2). Båda implementeringarna stöder samma åtgärder. men den äldre tjänsten, roundingCalc
, avrundar alla beräkningar till närmaste heltalsvärde innan de returneras. Ett klientprogram måste kunna ange om den nyare regularCalc
tjänsten ska användas.
Varning
För att kunna dirigera ett meddelande till en viss tjänstversion måste routningstjänsten kunna fastställa meddelandets mål baserat på meddelandeinnehållet. I den metod som visas nedan anger klienten versionen genom att infoga information i ett meddelandehuvud. Det finns metoder för tjänstversionshantering som inte kräver att klienter skickar ytterligare data. Ett meddelande kan till exempel dirigeras till den senaste eller mest kompatibla versionen av en tjänst eller så kan routern använda en del av SOAP-standardkuvertet.
De åtgärder som exponeras av båda tjänsterna är:
- Lägg till
- Subtract
- Multiplicera
- Dividera
Eftersom båda tjänstimplementeringarna hanterar samma åtgärder och i stort sett är identiska förutom de data som de returnerar, är basdata som finns i meddelanden som skickas från klientprogram inte tillräckligt unika för att du ska kunna avgöra hur begäran ska dirigeras. Åtgärdsfilter kan till exempel inte användas eftersom standardåtgärderna för båda tjänsterna är desamma.
Detta kan lösas på flera sätt, till exempel genom att exponera en specifik slutpunkt på routern för varje version av tjänsten eller lägga till ett anpassat rubrikelement i meddelandet för att ange tjänstversion. Med var och en av dessa metoder kan du dirigera inkommande meddelanden unikt till en viss version av tjänsten, men att använda unikt meddelandeinnehåll är den bästa metoden för att skilja mellan begäranden för olika tjänstversioner.
I det här exemplet lägger klientprogrammet till den anpassade huvudrubriken "CalcVer" i begärandemeddelandet. Det här huvudet innehåller ett värde som anger vilken version av tjänsten som meddelandet ska dirigeras till. Värdet "1" anger att meddelandet måste bearbetas av avrundningstjänstenCalc, medan värdet "2" anger regularCalc-tjänsten. På så sätt kan klientprogrammet styra vilken version av tjänsten som ska bearbeta meddelandet direkt. Eftersom det anpassade huvudet är ett värde som finns i meddelandet kan du använda en slutpunkt för att ta emot meddelanden som är avsedda för båda versionerna av tjänsten. Följande kod kan användas i klientprogrammet för att lägga till den här anpassade rubriken i meddelandet:
messageHeadersElement.Add(MessageHeader.CreateHeader("CalcVer", "http://my.custom.namespace/", "2"));
Implementera tjänstversioner
Skapa den grundläggande routningstjänstens konfiguration genom att ange tjänstslutpunkten som exponeras av tjänsten. I följande exempel definieras en enda tjänstslutpunkt som ska användas för att ta emot meddelanden. Den definierar också de klientslutpunkter som ska användas för att skicka meddelanden till
roundingCalc
(v1) ochregularCalc
(v2) tjänster.<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>
Definiera de filter som används för att dirigera meddelanden till målslutpunkterna. I det här exemplet används XPath-filtret för att identifiera värdet för det anpassade huvudet "CalcVer" för att avgöra vilken version meddelandet ska dirigeras till. Ett XPath-filter används också för att identifiera meddelanden som inte innehåller "CalcVer"-huvudet. I följande exempel definieras de filter och namnområdestabeller som krävs.
<!-- 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>
Kommentar
Prefixet s12-namnområde definieras som standard i namnområdestabellen och representerar namnområdet
http://www.w3.org/2003/05/soap-envelope
.Definiera filtertabellen, som associerar varje filter med en klientslutpunkt. Om meddelandet innehåller "CalcVer"-huvudet med värdet 1 skickas det till regularCalc-tjänsten. Om huvudet innehåller värdet 2 skickas det till roundingCalc-tjänsten. Om det inte finns någon rubrik dirigeras meddelandet till regularCalc.
Följande definierar filtertabellen och lägger till filtren som definierades tidigare.
<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>
Om du vill utvärdera inkommande meddelanden mot filtren i filtertabellen måste du associera filtertabellen med tjänstslutpunkterna med hjälp av routningsbeteendet. I följande exempel visas hur du associerar
filterTable1
med tjänstslutpunkterna:<behaviors> <!--default routing service behavior definition--> <serviceBehaviors> <behavior name="routingConfiguration"> <routing filterTableName="filterTable1" /> </behavior> </serviceBehaviors> </behaviors>
Exempel 1
Följande är en fullständig lista över konfigurationsfilen.
<?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>
Exempel 2
Följande är en fullständig lista över klientprogrammet.
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);
}
}
}