Ten artykuł pochodzi z serii przygotowań do egzaminu 70-503: Windows Communication Foundation.
Jak zapewne zauważyliście z wcześniejszych lekcji, siła WCFa pochodzi między nimi z możliwości zmiany parametrów “transmisji” bez zmiany ani jednej linijki kodu, rekompilacji, … Od tego mamy przecież plik konfiguracyjny. Możemy sobie napisać jedną aplikację, którą będziemy używać w wielu instancjach jednocześnie i wcale nie będą sobie przeszkadzać. O tym było w poprzednich lekcjach.
Dziś pokażę jak stworzyć sobie serwis całkowicie po stronie kodu C#. Może nam się to przydać np. w sytuacji gdy chcemy użytkownikowi ograniczyć/uprościć możliwość konfiguracji serwisu.
Klasy których użyjemy nazywają się odpowiednio do opcji które były ustawiane w plikach konfiguracyjnych. Zaczniemy od utworzenia obiektu hostującego serwis:
1: ServiceHost host = new ServiceHost(typeof(Calculator));
2: host.AddServiceEndpoint(typeof(ICalculator),
3: new NetTcpBinding(), "net.tcp://localhost:8000/Calculator");
Od razu w drugiej linii mamy pokazane jak zdefiniować ServiceEndpoint. Jako parametry podajemy interfejs definiujący kontrakt, binding i adres czyli znane nam już ABC. :) O kontraktach było pisane wcześniej więc przejdźmy od razu do adresów.
Budowanie adresu
Adres może być bezwzględny (pełny) czyli zawierający pełną ścieżkę wraz z domeną, lub względny (relatywny) gdzie domena i port są określane podczas tworzenia hosta, a przy dodawaniu ServiceEndpoint’a podajemy tylko ścieżkę. Ogólnie adres przyjmuje postać:
protocol://servername[:portNumber]/pathToEndpoint
Przykłady adresów (jak widać dla msmq format adresu jest zupełnie inny):
- http://localhost/SubDirectory/UpdateService
- net.tcp://localhost:8080/UpdateService
- net.pipe://localhost/Directory/UpdateService
- msmq.formatname:DIRECT=OS:.\private$\updateQueue
Adresem może być string (jak we wcześniejszym przykładzie) lub obiekt Uri:
1: ServiceHost host = new ServiceHost(typeof(Calculator));
2: Uri address = new Uri("net.tcp://localhost:8000/Calculator");
3: host.AddServiceEndpoint(typeof(ICalculator),
4: new NetTcpBinding(), address);
Poniżej mamy kod robiący dokładnie to samo ale z użyciem adresowania relatywnego:
1: ServiceHost host = new ServiceHost(typeof(Calculator),
2: new Uri[] { new Uri("net.tcp://localhost:8000") });
3: host.AddServiceEndpoint(typeof(ICalculator),
4: new NetTcpBinding(), "Calculator");
Przydaje się ten sposób zwłaszcza gdy mamy dużo ServiceEndpoint’ów (o tym w kolejnym akapicie) i bez sensu byłoby do każdego doklejanie aktualnego adresu serwera.
Wiele “Punktów Końcowych”
Mamy możliwość utworzenia wielu punktów końcowych, z których każdy będzie odpowiadał innemu kontraktowi (oczywiście nasza klasa Calculator obsługująca serwis musi implementować oba interfejsy):
1: ServiceHost host = new ServiceHost(typeof(Calculator));
2: NetTcpBinding binding = new NetTcpBinding();
3: host.AddServiceEndpoint(typeof(ICalculator),
4: binding, "net.tcp://localhost:8000/Calculator");
5: host.AddServiceEndpoint(typeof(IMaszynkaLicząca),
6: binding, "net.tcp://localhost:8000/Calculator");
Należy tutaj zauważyć ze ta sama instancja obiektu NetTcpBinding została podana w obu wywołaniach AddServiceEndpoint. Jeśli podalibyśmy oddzielne instancje, za drugim razem dostalibyśmy wyjątek informujący o tym, że już jakaś instancja bindingu została przypisana do podanego adresu.
Możemy też utworzyć kilka punktów końcowych do tego samego kontraktu (różniące się bindingiem):
1: ServiceHost host = new ServiceHost(typeof(Calculator));
2: host.AddServiceEndpoint(typeof(ICalculator),
3: new NetTcpBinding(), "net.tcp://localhost:8000/Calculator");
4: host.AddServiceEndpoint(typeof(ICalculator),
5: new WSHttpBinding(), http://localhost/Calculator);
Czytający Training Kit’a zauważą zapewne błąd w podanym tam przykładzie dla C#. W obu przypadkach powinien być użyty kontrakt IUpdateService. Dla VB jej poprawnie.
Metoda AddServiceEndpoint umożliwia podanie także URI listenera jako czwarty parametr wywołania. Dzięki temu część adresu będzie wspólna dla obu bindowań:
1: ServiceHost host = new ServiceHost(typeof(Calculator));
2: Uri commonUri = new Uri("net.tcp://localhost:8888/common");
3: NetTcpBinding binding = new NetTcpBinding();
4: host.AddServiceEndpoint(typeof(ICalculator),
5: binding, "/Calculator1", commonUri);
6: host.AddServiceEndpoint(typeof(ICalculator),
7: binding, "/Calculator2", commonUri);
Po stronie klienta podłączenie do tego serwisu powinno wyglądać mniej więcej tak:
1: EndpointAddress endpoint = new
2: EndpointAddress("net.tcp://localhost:8000/Calculator2");
3: NetTcpBinding binding = new NetTcpBinding();
4: Uri commonUri = new Uri("net.tcp://localhost:8888/common");
5: CalculatorProxy proxy = new CalculatorProxy(binding, endpoint);
6: proxy.Endpoint.Behaviors.Add(newViaUriBehavior(commonUri));
Binding
Właściwości bindowań zostały podzielone na tzw. Binding Elements, które są wspólne dla wielu rodzajów bindowań. Jeśli zrozumiemy działanie każdego “elementu” to będziemy wiedzieli jak działa binding, który zawiera dany element.
basicHttpBinding
basicHttpBinding ma 4 tryby zabezpieczeń (właściwość Security.Mode) . W zależności od trybu będą użyte inne elementy:
- brak zabezpieczeń:
TextMessageEncodingBindingElement, HttpTransportBindingElement
- tryb Transport:
TextMessageEncodingBindingElement, HttpsTransportBindingElement
- tryb Message:
TextMessageEncodingBindingElement, HttpTransportBindingElement, AsymmetricSecurityBindingElement
- tryb TransportWithMessageCredentials:
TextMessageEncodingBindingElement, HttpsTransportBindingElement, TransportSecurityBindingElement
Dodatkowo jeśli ustawimy MessageEncoding na Message Transmission Optimization Mechanism (MTOM) to zamiast TextMessageEncodingBindingElement zostanie użyty MtomMessageEncodingBindingElement.
Nazwy same w sobie powinny świadczyć o tym czym zajmuje się dany element i w jaki sposób to robi.
netTcpBinding
Jak sama nazwa wskazuje (czemu te nazwy są wszędzie takie intuicyjne? :) ) ten binding używa TCP jako podstawę. Używane są następujące elementy BinaryMessageEncodingBindingElement (konwertowanie wiadomości do postaci binarnej), TransactionFlowBindingElement (obsługa transakcji między klientem i serwisem), oraz TcpTransportBindingElement (obsługa transmisji).
netNamedPipeBinding
Składa się z trzech elementów: TransactionFlowBindingElement, BinaryMessageEncodingBindingElement, NamedPipeTransportBindingElement.
netMsmqBinding
Ten binding nie obsługuje transakcji, składa się tylko z dwóch elementów: BinaryMessageEncodingBindingElement, MsmqTransportBindingElement.
netPeerTcpBinding
Składa się z trzech elementów: BinaryMessageEncodingBindingElement, PnrpPeerResolverBindingElement (obsługuje Peer Name Resolution Protocol (PNRP) – protokół potrzebny do nawiązania komunikacji Peer-to-Peer), PeerTransportBindingElement.
wsDualHttpBinding
Oprócz przedstawionych wcześniej TransactionFlowBindingElement, TextMessageEncodingBindingElement i HttpTransportBindingElement korzysta też z elementów CompositeDuplexBindingElement (obsługa wywołania zwrotnego ang. callback) oraz ReliableSessionBindingElement (obsługa sesji czyli zachowanie kolejności komunikatów itp.).
Pozostały jeszcze:
basicHttpContextBinding
TextMessageEncodingBindingElement, HttpTransportBindingElement
msmqIntegrationBinding
BinaryMessageEncodingBindingElement,MsmqTransportBindingElement
netTcpContextBinding
BinaryMessageEncodingBindingElement,TransactionFlowBindingElement, TcpTransportBindingElement
webHttpBinding
TextMessageEncodingBindingElement,HttpTransportBindingElement
wsFederationHttpBinding
TransactionFlowBindingElement, TextMessageEncodingBindingElement, HttpTransportBindingElement
wsHttpContextBinding
TransactionFlowBindingElement, TextMessageEncodingBindingElement, HttpTransportBindingElement
ws2007FederationhttpBinding
TransactionFlowBindingElement, TextMessageEncodingBindingElement, HttpTransportBindingElement
ws2007HttpBinding
TransactionFlowBindingElement, TextMessageEncodingBindingElement, HttpTransportBindingElement
W kolejnych kilku lekcjach zajmiemy się śledzeniem serwisów.