Udostępnij za pośrednictwem


Instrukcje: Przechowywanie wersji usługi

W tym temacie opisano podstawowe kroki wymagane do utworzenia konfiguracji routingu, która kieruje komunikaty do różnych wersji tej samej usługi. W tym przykładzie komunikaty są kierowane do dwóch różnych wersji usługi kalkulatora ( roundingCalc wersja 1) i regularCalc (wersja 2). Obie implementacje obsługują te same operacje; jednak starsza usługa, roundingCalc, zaokrągla wszystkie obliczenia do najbliższej wartości całkowitej przed zwróceniem. Aplikacja kliencka musi mieć możliwość wskazania, czy używać nowszej regularCalc usługi.

Ostrzeżenie

Aby skierować komunikat do określonej wersji usługi, usługa routingu musi mieć możliwość określenia miejsca docelowego komunikatu na podstawie zawartości komunikatu. W metodzie przedstawionej poniżej klient określi wersję, wstawiając informacje do nagłówka komunikatu. Istnieją metody przechowywania wersji usługi, które nie wymagają od klientów przekazywania dodatkowych danych. Na przykład komunikat może być kierowany do najnowszej lub najbardziej zgodnej wersji usługi lub router może używać części standardowej koperty protokołu SOAP.

Operacje uwidocznione przez obie usługi to:

  • Dodaj
  • Subtract
  • Razy
  • Podziel

Ponieważ obie implementacje usług obsługują te same operacje i są zasadniczo identyczne niż zwracane dane, podstawowe dane zawarte w komunikatach wysyłanych z aplikacji klienckich nie są wystarczająco unikatowe, aby umożliwić określenie sposobu kierowania żądania. Na przykład nie można używać filtrów akcji, ponieważ akcje domyślne dla obu usług są takie same.

Można to rozwiązać na kilka sposobów, takich jak uwidacznianie określonego punktu końcowego na routerze dla każdej wersji usługi lub dodanie niestandardowego elementu nagłówka do komunikatu w celu wskazania wersji usługi. Każde z tych podejść umożliwia unikatowe kierowanie komunikatów przychodzących do określonej wersji usługi, ale użycie unikatowej zawartości komunikatu jest preferowaną metodą różnicowania między żądaniami dla różnych wersji usług.

W tym przykładzie aplikacja kliencka dodaje niestandardowy nagłówek "CalcVer" do komunikatu żądania. Ten nagłówek będzie zawierać wartość wskazującą wersję usługi, z którą powinien być kierowany komunikat. Wartość "1" wskazuje, że komunikat musi zostać przetworzony przez usługę roundingCalc, a wartość "2" wskazuje usługę regularCalc. Dzięki temu aplikacja kliencka może bezpośrednio kontrolować, która wersja usługi będzie przetwarzać komunikat. Ponieważ nagłówek niestandardowy jest wartością zawartą w komunikacie, można użyć jednego punktu końcowego do odbierania komunikatów przeznaczonych dla obu wersji usługi. Do dodania tego nagłówka niestandardowego do komunikatu można użyć następującego kodu w aplikacji klienckiej:

messageHeadersElement.Add(MessageHeader.CreateHeader("CalcVer", "http://my.custom.namespace/", "2"));

Implementowanie obsługi wersji usługi

  1. Utwórz podstawową konfigurację usługi routingu, określając punkt końcowy usługi uwidoczniony przez usługę. W poniższym przykładzie zdefiniowano pojedynczy punkt końcowy usługi, który będzie używany do odbierania komunikatów. Definiuje również punkty końcowe klienta, które będą używane do wysyłania komunikatów do roundingCalc usług (v1) i regularCalc (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>
    
  2. Zdefiniuj filtry używane do kierowania komunikatów do docelowych punktów końcowych. W tym przykładzie filtr XPath służy do wykrywania wartości nagłówka niestandardowego "CalcVer" w celu określenia wersji, do której ma być kierowany komunikat. Filtr XPath służy również do wykrywania komunikatów, które nie zawierają nagłówka "CalcVer". W poniższym przykładzie zdefiniowano wymagane filtry i tabelę przestrzeni nazw.

    <!-- 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>
    

    Uwaga

    Prefiks przestrzeni nazw s12 jest definiowany domyślnie w tabeli przestrzeni nazw i reprezentuje przestrzeń http://www.w3.org/2003/05/soap-envelopenazw .

  3. Zdefiniuj tabelę filtrów, która kojarzy każdy filtr z punktem końcowym klienta. Jeśli komunikat zawiera nagłówek "CalcVer" o wartości 1, zostanie wysłany do usługi regularCalc. Jeśli nagłówek zawiera wartość 2, zostanie wysłany do usługi roundingCalc. Jeśli nagłówek nie jest obecny, komunikat zostanie przekierowany do zwykłego obliczenia.

    Poniżej zdefiniowano tabelę filtrów i dodano zdefiniowane wcześniej filtry.

    <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>
    
  4. Aby ocenić komunikaty przychodzące względem filtrów zawartych w tabeli filtrów, należy skojarzyć tabelę filtrów z punktami końcowymi usługi przy użyciu zachowania routingu. W poniższym przykładzie pokazano skojarzenie filterTable1 z punktami końcowymi usługi:

    <behaviors>
      <!--default routing service behavior definition-->
      <serviceBehaviors>
        <behavior name="routingConfiguration">
          <routing filterTableName="filterTable1" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    

Przykład 1

Poniżej znajduje się pełna lista pliku konfiguracji.

<?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>

Przykład 2

Poniżej znajduje się pełna lista aplikacji klienckiej.

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);

        }
    }
}

Zobacz też