BETA
Aby się zalogować, najpiew wybierz portal.
Aby się zarejestrować, najpiew wybierz portal.
Podaj słowa kluczowe
Słowa kluczowe muszą mieć co najmniej 3 sąsiadujące znaki alfanumeryczne
Pole zawiera niedozwolone znaki

Baza wiedzy











WCF w praktyce. Część 5 - kilka słów o konfiguracji

10-11-2010 23:30 | Maciej Grabek
W poprzednich artykułach przeszliśmy przez proces tworzenia usługi, hosta i klienta. Przyjrzeliśmy się również jednemu z narzędzi do pracy z Windows Communication Foundation jakim jest WCF Test Client. Przyszedł jendnak czas aby przyjrzeć się esencji WCF, czyli jego konfiguracji. Zapraszam do lektury.

W poprzednich artykułach przeszliśmy przez proces tworzenia usługi, hosta i klienta. Przyjrzeliśmy się również jednemu z narzędzi do pracy z Windows Communication Foundation jakim jest WCF Test Client. Przyszedł jednak czas aby przejść do esencji WCF, czyli do jego konfiguracji. Oczywiście nie można zamknąć opisu całej konfiguracji WCF w jednym, a nawet w kilku artykułach, dlatego informacje które tu przedstawię stanowią zbiór najważniejszych rzeczy, na które trzeba zwrócić uwagę przy pracy z WCF. W tej części podejmiemy następujące tematy:
1. Składniki konfiguracji WCF
2. Punkty końcowe
3. Manipulowanie punktami końcowymi
4. Podmiana implementacji serwisu

Pierwsze co musimy zrobić, to pobrać projekt, który był dołączony do trzeciej części serii. Jest on dostępny tutaj. Będzie to nasz punkt wyjściowy do analizy i wszelkich zmian.

Na początek przyjrzymy się zawartości pliku app.config po stronie Hosta, na przykład z projektu SampleServiceSelfHost. Interesująca dla nas jest sekcja <system.serviceModel>, której zawartość znajduje się na listingu 1.

Listing1 Sekcja system.ServiceModel pliku konfiguracyjnego hosta.

[Kod]

<system.serviceModel>
<services>
<service name="SampleServiceLibrary.GreetingService">
<endpoint address="" binding="wsHttpBinding" contract="SampleServiceLibrary.IGreetingService" />
<endpoint address="basic" binding="basicHttpBinding" contract="SampleServiceLibrary.IGreetingService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8732/Design_Time_Addresses/SampleServiceLibrary/Service1/" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>

To co widzimy to przede wszystkim definicja usługi, którą stanowi sekcja . Określa ona podstawowe informacje o usłudze. Pierwsza z tych informacji to definicja punktów końcowych, przy których zatrzymamy się na chwilę. Aby skorzystać z usługi musimy odwołać się do jej punktu końcowego. Punkt taki jest charakteryzowany przez trzy parametry: adres, wiązanie i kontrakt. Adres - w naszym przypadku (self hosting) korzystamy z adresu bazowego () i w punktach końcowych określamy już tylko jego postfiksy (np. /basic lub /mex).
Wiązanie (binding) - na ten temat przygotowuję oddzielny artykuł, natomiast w tym miejscu należy zaznaczyć, że określa ono dodatkowe informacje na temat połączenia, np. użyte zabezpieczenia, ograniczenia wielkości wiadomości itp. W naszym przypadku korzystamy z domyślnych ustawień wiązań wsHttpBinding, basicHttpBinding oraz mexHttpBinding.
Jeżeli chodzi o mexHttpBinding, to służy on do pobrania metadanych dotyczących serwisu przez klienta podczas dodawania referencji i generowania proxy. Po tej operacji możemy go usunąć.
Kontrakt - określa jaki kontrakt usługi jest realizowany przez punkt końcowy. W naszym przypadku jest to SampleServiceLibrary.IGreetingService.

Jak widać nasza usługa udostępnia dwa punkty końcowe realizujące nasz kontrakt oraz jeden specjalny służący do wymiany metadanych. Ich adresy to odpowiednio:
http://localhost:8732/Design_Time_Addresses/SampleServiceLibrary/Service1/
http://localhost:8732/Design_Time_Addresses/SampleServiceLibrary/Service1/basic
http://localhost:8732/Design_Time_Addresses/SampleServiceLibrary/Service1/mex

Informacja o tym, która implementacja kontraktu będzie używana przez ten serwis znajduje się w atrybucie name węzła <service>.

Wróćmy zatem do pliku app.config hosta. Mamy tam jeszcze węzeł <behaviors>. Określamy w nim dodatkowe informacje na temat "zachowań" serwisu. W tym przypadku udostępniamy metadane po protokole http (<serviceMetadata>) oraz uniemożliwiamy zwracanie przez nasz serwis błędów wewnętrznych (<serviceDebug>). Na serwerze produkcyjnym obie opcje powinny mieć wartość false!

Przyjrzyjmy się teraz jak wygląda plik web.config dla hostowania przy pomocy IIS. Zawartość sekcji <system.serviceModel> jest widoczna na listingu 2.

Listing 2. Zawartość sekcji <system.serviceModel> w pliku web.config

[Kod]

<system.serviceModel>
<services>
<service name="SampleServiceLibrary.AnotherGreetingService">
<endpoint address="" binding="wsHttpBinding" contract="SampleServiceLibrary.IGreetingService" bindingConfiguration="BasicHttpBinding_IGreetingServiceA" />
<endpoint address="basic" binding="basicHttpBinding" contract="SampleServiceLibrary.IGreetingService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>

Jak widać, nie ma w nim informacji o adresie bazowym, z racji na to, że jest nim adres pod którym jest dostępny plik GreetingService.svc.

Jeżeli mamy kilka klas implementujących kontrakt wówczas możemy w wygodny sposób zmienić implementację serwisu na dowolną z nich i tym samym zmieni się logika naszej usługi. Aby to zaprezentować w projekcie SampleServiceLibrary dodamy plik AnotherGreetingService.cs o treści widocznej na listingu 3.

Listing 3. Inna implementacja kontraktu

[Kod]

using System;
using SampleServiceLibrary.Messages;

namespace SampleServiceLibrary
{
public class AnotherGreetingService : IGreetingService
{
public GreetingResponse SayHi(GreetingRequest request)
{
GreetingResponse response = new GreetingResponse();
response.Message = String.Format("Hello {0}! This is AnotherGreetingService!", request.Name);
return response;
}
}
}

Teraz w pliku GreetingService.svc zmieniamy atrybut Service na SampleServiceLibrary.AnotherGreetingService. Jego ostateczna zawartość widoczna jest na listingu 4.

Listing 4. Zmieniony plik GreetingService.svc

[Kod]

<%@ ServiceHost Language="C#" Debug="true" Service="SampleServiceLibrary.AnotherGreetingService"%>

Dodatkowo wprowadzając w pliku web.config zmianę atrybutu name węzła <service> będziemy mieć dokładnie taką samą konfigurację jak wcześniej, a nie domyślną (brak konfiguracji dla serwisu w pliku web.config oznacza konfigurację domyślną). Biorąc pod uwagę, że nasz klient łączy się do punktu końcowego z końcówką /basic wprowadzamy opisane przeze mnie zmiany. W węźle <services> mamy zatem wpis widoczny na listingu 5.

Listing 5. Zmodyfikowana sekcja <services> w pliku web.config

[Kod]

<service name="SampleServiceLibrary.AnotherGreetingService">
<endpoint address="" binding="wsHttpBinding" contract="SampleServiceLibrary.IGreetingService" />
<endpoint address="basic" binding="basicHttpBinding" contract="SampleServiceLibrary.IGreetingService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>

Uruchamiamy usługę, a następnie uruchamiamy klienta nie wprowadzając w nim żadnych zmian. W wyniku otrzymujemy odpowiedź widoczną na rysunku 1. Dla porównania na rysunku 2 jest prezentowana wersja sprzed zmian.

Rysunek 1. Odpowiedź z serwisu po zmienionej wybranej implementacji.

Rysunek 2. Dla porównania odpowiedź z serwisu przed zmianą wybranej implementacji.

Jak widać zmiana ta nie jest trudna, a przede wszystkim jest niewidoczna dla klienta usługi! Sprowadza się ona wręcz do kosmetyki.
Analogicznej zmiany możemy dokonać w przypadku self hosting, jednakże będzie tu konieczność przekompilowania kodu, gdyż jest w nim informacja o tym który kontrakt będzie udostępniony przez hosta.

Czas przyjrzeć się konfiguracji po stronie klienta, gdyż ona również jest niezmiernie istotna. Zawartość pliku app.config klienta widoczna jest na listingu 6.

Listing 6. Konfiguracja klienta.

[Kod]

<system.serviceModel>
<client>
<endpoint address="http://localhost:5000/GreetingService.svc/basic"
binding="basicHttpBinding"
contract="SampleServiceReference.IGreetingService" name="BasicHttpBinding_IGreetingService" />
<endpoint address="http://localhost:5000/GreetingService.svc/basic"
binding="basicHttpBinding"
contract="AsyncSampleServiceReference.IGreetingService" name="BasicHttpBinding_IGreetingServiceAsync" />
</client>
</system.serviceModel>

Jak widać tu również mamy informacje o punktach końcowych hosta, gdyż proxy, które pozwala na połączenie się do serwisu musi wiedzieć gdzie i jak się do niego podłączyć. Jeżeli okaże się, że usługa zmieniła adres wystarczy zmienić go w odpowiednim punkcie końcowym po stronie klienta i gotowe - dalej możemy korzystać z usługi.

Co jeżeli chcielibyśmy okaże się, że musimy zmienić na przykład protokół z HTTP na TCP. W tym przypadku również wszystko może odbyć się bez większych zmian po stronie klienta. W pliku app.config projektu SampleServiceSelfHost dodajmy punkt końcowy widoczny na listingu 7.

Listing 7. Nowy punkt końcowy.

[Kod]

<endpoint address="net.tcp://localhost:5005/SampleServiceLibrary/Service1/" binding="netTcpBinding" contract="SampleServiceLibrary.IGreetingService" />

Oznacza on tyle, że nasza usługa będzie dostępna na protokole tcp pod adresem net.tcp://localhost:5005/SampleServiceLibrary/Service1/
Z racji na to, że w kodzie klienta odwołujemy się do punktu końcowego po nazwie nie będziemy dodawać nowego wpisu, lecz zmodyfikujemy istniejący. Fakt, że nazywa się on BasicHttpBinding_IGreetingService i jeżeli będzie nasłuchiwał na net.tcp będzie to niezmiernie mylące, jednakże moim celem jest pokazanie elastyczności WCF. W rzeczywistym rozwiązaniu można by w trakcie tworzenia klienta nazwać ten punkt końcowy nie BasicHttpBinding_IGreetingService, tylko np GreetingServiceDefaultEndpoint i wtedy nie było by takich nieporozumień. Ja jednak nie chcę tego robić, by pokazać, że wszystko można zamknąć w modyfikacji plików konfiguracyjnych. W związku z tym modyfikujemy punkt końcowy do postaci widniejącej na listingu 8.

Listing 8 Zmieniony punkt końcowy po stronie klienta

[Kod]

<endpoint address="net.tcp://localhost:5005/SampleServiceLibrary/Service1/"
binding="netTcpBinding"
contract="SampleServiceReference.IGreetingService" name="BasicHttpBinding_IGreetingService" />

Tak oto w prosty sposób zamiast używać do komunikacji protokołu http, wykorzystujemy TCP. Po uruchomieniu projektu SampleServiceSelfHost a następnie SampleServiceClient widzimy, że wszystko działa jak należy.

Pliki konfiguracyjne, którymi się teraz zajmujemy korzystają z domyślnych ustawień komunikacji. Możemy je zmodyfikować dodając do <system.serviceModel> sekcję <bindings> a w niej np. konfigurację połączenia wykorzystującego basicHttpBinding dla referencji z metodami asynchronicznymi, którą dodaliśmy w trzeciej części. Na listingu 9 widzimy domyślną konfigurację takiego wiązania.

Listing 9. Domyślne ustawienia dla wiązania basicHttpBinding

[Kod]

<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IGreetingServiceA" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>

Aby wykorzystać tą konfigurację w punkcie końcowym musimy dodać informację o jej wykorzystaniu dodając atrybut bindingConfiguration. Przybiera on wówczas postać widoczną na listingu 10.

Listing 10. Punkt końcowy ze zdefiniowaną konfiguracją wiązania.

[Kod]

<endpoint address="http://localhost:5000/GreetingService.svc/basic"
binding="basicHttpBinding"
contract="AsyncSampleServiceReference.IGreetingService"
bindingConfiguration="BasicHttpBinding_IGreetingServiceA"
name="BasicHttpBinding_IGreetingServiceAsync" />

Należy jednak pamiętać o tym, że po stronie serwisu należy dodać analogiczną konfigurację, zatem w piliku web.config (asynchroniczne operacje wykonywane są na adresie http://localhost:5000/GreetingService.svc/basic, który udostępnia SampleServiceWebHost) dodajemy analogiczne wpisy.
Wiązaniom poświęcę oddzielny artykuł, zatem nie wnikam w tym miejscu w ich zawartość, żeby nie przeładować tego "odcinka".

Tak oto doszliśmy do końca analizy i modyfikacji stworzonego wcześniej projektu. Wiemy już jak możemy zmieniać punkty końcowe usług, ich konfigurację (wiązania) oraz protokoły używane do komunikacji. Wiemy również jak łatwo możemy zmienić używaną przez usługę implementację kontraktu. Poprzez ten artykuł chciałem pokazać to co jest sednem Windows Communication Foundation, a mianowicie wygoda, elastyczność i oderwanie od warstwy transportu poprzez przejrzystą i wygodną konfigurację.
To oczywiście nie jest wszystko na temat konfiguracji. Zachęcam do dalszej lektury tego obszernego tematu.

Zapraszam również na swojego bloga gdzie w poście dotyczącym tego artykułu macie możliwość wypowiedzenia się, który z tematów przedstawić w kolejnym artykule. Znajdziecie tam również wpisy dotyczące WCF.

Kod po zmianach można pobrać z tego miejsca.

Maciej Grabek
Maciej Grabek
Pasjonat technologii oferowanych przez Microsoft , współzałożyciel i lider Toruńskiej Grupy Deweloperów .NET. Na codzień programista .NET w jednej z największych firm e-commerce w Polsce.

Podobne artykuły

Komentarze 0

pkt.

Zaloguj się lub Zarejestruj się aby wykonać tę czynność.