Spis treści:

Kategoria:C#Windows Communication Foundation


Konfiguracja WCF za pomocą plików app.config

App.config, czyli plik konfiguracyjny

W poprzednich wpisach pokazałem, w jaki sposób skonfigurować usługi WCF bezpośrednio w kodzie C#. Jest to rozwiązanie dobre, ale ma kilka wad. Najważniejsza z nich jest taka, że każda zmiana w takiej konfiguracji wymaga ponownej kompilacji wszystkich, lub przynajmniej części, źródeł. Zaleta jest taka, że mamy najzwyczajniej w świecie mniej plików.

Wspomniane artykuły można znaleźć tutaj: Tworzenie prostego serwera WCF, Tworzenie prostego klienta WCF.

Konfigurację usług WCF umieszcza się, podobnie jak inne elementy konfiguracyjne, w pliku app.config. Podczas kompilacji i budowy całego projektu środowisko programistyczne zmienia nazwę tego pliku na nazwę pliku wykonywalnego z rozszerzeniem .config i przerzuca do katalogu z plikiem wykonywalnem. Jeżeli zatem aplikacja ma nazwę aplikacja.exe, to plik konfiguracyjny przyjmie nazwę aplikacja.exe.config.

Co można definiować w takim pliku?

W zasadzie to prawie wszystko. Przypomnijmy sobie, że w tym pliku konfiguruje się nie tylko ustawienia WCF, ale także pozostałe ustawienia aplikacji. Jest to miejsce, gdzie zwykle definiuje się łańcuch połączeniowy do bazy danych, sposób logowania błędów, parametry działania programu i wiele innych rzeczy. My zajmiemy się wyłącznie konfiguracją usług WCF. Jak pamiętamy z poprzednich artykułów, aby zdefiniować sposób nasłuchiwania przez serwer musieliśmy określić protokół, adres i sposób komunikacji. Tym razem konfigurację przeniesiemy do pliku app.config.

Zmiany w projekcie serwera

Żeby nie zagmatwać całej sytuacji zmiany projektów będą następowały stopniowo. Na pierwszy ogień pójdzie serwer usług WCF. Zanim przejdę do omówienia struktury pliku .xml przyjrzyjmy się gotowej definicji. Aby tę definicję zawrzeć w projekcie należy dodać plik app.config, a w nim wpisać co następuje:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="HelloWorld">
        <endpoint address="http://localhost:2222/Hello"
                  binding="basicHttpBinding"
                  contract="IHelloWorld"/>
      </service>
    </services>
  </system.serviceModel>
</configuration>

Przyjrzyjmy się teraz dokładniej poszczególnym elementom tego pliku. Pierwsza linijka to określenie wersji i sposobu kodowania znaków, nie ma ona większego znaczenia dla WCF. Dodawana jest ona automatycznie podczas tworzenia nowego pliku konfiguracyjnego z poziomu Visual Studio. Druga linijka to początek seksji konfiguracyjnej. Jest to w zasadzie jedyna możliwość rozpoczęcia pliku .xml, bo jak wiemy plik .xml może mieć tylko jeden element najwyższego poziomu. Cała zabawa z konfiguracją WCF zaczyna się od znacznika system.serviceModel. To właśnie w tym znaczniku znajduje się cała konfiguracja właściwa WCF. Już na tym etapie mamy możliwość konfiguracji wielu rzeczy, ale zostawimy sobie je na później. My zajmiemy się wyłącznie znacznikiem services, czyli tym elementem, w którym definiuje się usługi.

Definiowanie usług WCF

W znaczniku services znajdują się definicje usług. Umieszcza się tutaj wpis dla każdej usługi, czyli inaczej mówiąc klasy imlementującej pewien zbiór udostępnianych funkcji, jeżeli oczywiście chcemy te usługi upublicznić. My na razie mamy tylko jedną klasę, która implementuje jeden interfejs, więc pojawi się tu tylko jeden znacznik service. Atrybut name oznacza tutaj nazwę klasy implementującej interfejs, czyli klasy, w której znajduje się właściwy kod. W naszym przypadku jest to klasa HelloWorld.

W naszym przypadku klasa znajduje się w domyślnej, "beznazwowej" przestrzeni nazw. Jeżeli nasza klasa znajduje się w nazwanej przestrzeni nazw, czyli otoczona jest sekcją namespace, to tę przestrzeń nazw także trzeba tu umieścić. Jeżeli użyliśmy namespace AAA, a nasza klasa jest w środku, to napisalibyśmy <service name="AAA.HelloWorld">

Atrybut address określa adres, pod którym nasza usługa WCF będzie dostępna. Jest to ten sam adres, którego użyliśmy bezpośrednio w kodzie programu. Atrybut binding określa sposób komunikacji. W kodzie C# przekazywaliśmy obiekt klasy BasicHttpBinding jako jeden z parametrów podczas tworzenia wątku nasłuchującego, tym razem definicja znajduje się w pliku.

Uważny i wnikliwy obserwator mógł w tej chwili dostrzeć pewną nieścisłość w nazewnictwie. Definiując klasę w kodzie używaliśmy wielkiej litery i pisaliśmy BasicHttpBinding. W pliku konfiguracyjnym piszemy basicHttpBinding, czyli z małej litery. Dlaczego tak jest? Prawdę mówiąc nie wiadomo. Napisanie w kodzie basicHttpBinding będzie błedem, bo nazwą klasy jest BasicHttpBinding. Podobnie w pliku .xml napisanie BasicHttpBinding będzie błędem, bo w definicji schematu użyto nazwy basicHttpBinding. Schemat pliku konfiguracyjnego umieszczony jest w pliku DotNetConfig.xsd, w katalogu Visual Studio.

Ostatnim atrybutem jest contract, czyli definicja kontraktu, lub przekładając na język programistyczny, interfejsu. Tak ja w przypadku nazwy klasy musimy tutaj podać pełną ścieżkę, włącznie z przestrzeniami nazw. W naszym przykładzie przestrzenie nazw zostały usunięte, więc wystacza sama nazwa interfejsu. Należy jeszcze tylko zadbać o poprawne zamknięcie znaczników, bo cała konfiguracja serwera na tym etapie się kończy.

Zmiany w kodzie C# serwera

Aby w pełni cieszyć się konfigurowalnym serwerem należy wprowadzić drobne zmiany w samym kodzie. Jak można się domyślać, kod będzie krótszy i tak w rzeczywistości jest. Główna metoda serwera zaprezentowana jest poniżej:

static void Main(string[] args)
{
  ServiceHost host = new ServiceHost(typeof(HelloWorld));
  host.Open();
  Console.WriteLine("Serwer uruchomiony");
  Console.ReadLine();
}

Cała operacja sprowadza się do utworzenia klasy ServiceHost i przekazaniu jej typu tworzonej klasy. To ten sam typ, który wskazany jest atrybutem name w pliku konfiguracyjnym. Po tym właśnie kluczu WCF odszukuje odpowiedni wpis i z niego bierze pozostałe parametry, czyli adres, protokół i sposób komunikacji. Druga linijka to najprostsze w świecie wywołanie metody Open. reszta pozostaje bez zmian.

Zadanie konfiguracji serwera zostało wykonane. Można się o tym przekonać uruchamiając serwer, oraz klienta stworzonego wcześniej. Klienta, którego konfiguracja znajduje się jeszcze w kodzie. Jeżeli uda się uruchomić serwer, można przejść do utworzenia pliku konfiguracyjnego klienta.

Przenoszenie konfiguracji klienta do pliku app.config

Przeniesienie konfiguracji do pliku aplikacji klienckiej nie jest wcale trudniejsze od przeniesienia konfiguracji do pliku xml na serwerze. Pierszym etapem jest utworzenie pliku app.config i umieszczenie w nim następujących wpisów:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <client>
      <endpoint address="http://localhost:2222/Hello"
                binding="basicHttpBinding"
                contract="IHelloWorld">
      </endpoint>
    </client>
  </system.serviceModel>
</configuration>

Podobnie jak w przypadku serwera, tak i tutaj definiujemy zaledwie kilka pól. Jak wspomniałem, konfiguracja WCF znajduje się zawsze w sekcji system.serviceModel. Różnica jest na kolejnym poziomie, bo tym razem otwieramy sekcję client, nie services jak wcześniej. Łatwo zapamiętać: services to usługi, a usługi świadczy serwer, client to po prostu klient. W znacznikach client znajdują się definicje końcówek, czyli znacznik endpoint. Tam definiujemy zaledwie try pola: adres wraz z protokołem, binding, czyli sposób wymiany danych, oraz obsługiwany kontrakt. Kontrakt klienta jest oczywiście taki sam jak kontrakt serwera.

Po wprowadzeniu tych danych wystarczy tylko zadbać o to, aby struktura pliku konfiguracyjnego była zgodna z zasadami XML, czyli między innymi pozamykać wszystkie otwarte wcześniej znaczniki. Jeżeli w katalogu aplikacji klienckiej mamy już plik app.config, możemy przystąpić do zmian w kodzie C#.

Zmiany w kodzie klienta

Kod klienta nie był szczególnie skomplikowany, dlatego łatwo będzie można wyłuskać różnice. Z pewnością zginie definicja adresu oraz definicja metody komunikacji w ramach protokołu, czyli tzw. bindingu. Te wartości znalazły się teraz w pliku konfiguracyjnym. Aby nie przedłużać, przyjrzyjmy się całej metodzie Main klienta.

static void Main(string[] args)
{
  using (var c = new ChannelFactory<IHelloWorld>(""))
  {
    var s = c.CreateChannel();
    Console.WriteLine(s.Hello());
    Console.ReadLine();
  }
}

Na tę chwilę wystarczy proste użycie klasy ChannelFactory, z jednym parametrem w postaci pustego łańcucha znaków.

Wśród różnych konstruktorów klasy ChannelFactory<T> istnieje także konstruktor bezparametrowy, ale z dziwnych względów nie udaje się za jego pomocą uruchomić usług WCF. Dokumentacja tego konstruktora też jest dziwnie uboga i nie wyjaśnia całej kwestii w sposób wystarczający.

Pozostała część kodu klienta nie ulega zmianie.

Na tym etapie możemy zakończyć. Klient powinien się kompilować, a po uruchomieniu połączyć z serwerem i wyświetlić prosty komunikat. Oczywiście pod warunkiem, że serwer działa sobie gdzieś w tle.

Kategoria:C#Windows Communication Foundation

, 2013-12-20

Komentarze:

nameless (2013-05-03 21:43:21)
Należy używać konstruktora ChannelFactory<T> z parametrem w postaci pustego łańcucha znaków ponieważ endpoint nie ma atrybutu name, który w takim przypadku przyjmuje pusty łańcuch znaków. Gdyby konfiguracja wyglądała:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<client>
<endpoint name="HelloWorldEndPointConfig"
address="http://localhost:2222/Hello"
binding="basicHttpBinding"
contract="IHelloWorld">
</endpoint>
</client>
</system.serviceModel>
</configuration>

należałoby użyć:

new ChannelFactory<IHelloWorld>("HelloWorldEndPointConfig")
fejo (2014-09-26 20:24:05)
Hej,

Klasa ServiceHost nie posiada jednoparametrowego konstruktora.

Visual Studio kompiluje taki kod, ale program już nie działa i wyrzuca wyjątek: Usługa „ConsoleApplication2.Skladki” ma zerowe punkty końcowe aplikacji. Może to być spowodowane tym, że nie znaleziono pliku konfiguracji dla aplikacji, nie znaleziono elementu usługi pasującego do nazwy usługi w pliku konfiguracji lub nie zdefiniowano punktów końcowych w elemencie usługi.
Dodaj komentarz
Wyślij
Ostatnie komentarze
Dzieki za rozjasnienie zagadnienia upsert. wlasnie sie ucze programowania :).
Co się stanie gdy spróbuję wyszukać:
SELECT * FROM NV_Airport WHERE Code='SVO'
SELECT * FROM V_Airport WHERE Code=N'SVO'
(odwrotnie są te N-ki)
Będzie konwersja czy nie znajdzie żadnego rekordu?