Spis treści:

Kategoria:Windows Communication Foundation


Metadane WCF - MEX i WSDL

Co to są metadane

WCF to nie tylko wywoływanie metod interfejsowych. WCF został zaprojektowany tak, aby ta komunikacja była możliwie prosta. Projektanci WCF, a wcześniej Web Service, zadali sobie następujące pytanie: skąd grupa odbiorców ma wiedzeć o istnieniu naszych usług? Więcej, skąd ta sama grupa osób ma wiedzieć, jakie udostępniamy metody? Jak powinna być struktura komunikatu w sensie przekazywanych argumentów i jakiej odpowiedzi mogą oczekiwać odbiorcy? WCF, jeśli zechce, może udostępniać metadane. Dzięki tym metadanym inni mogą nas poznać, a różne narzędzia (m.in. svcutil.exe) potrafią na ich podstawie wygenerować wygodną klasę .NET. Nie zamierzam powielać teorii, bo od tego jest dokumentacja. Zwrócę uwagę na praktyczną część zagadnienia i elementy niezbędne do zrozumienia całego mechanizmu.

Udostępnianie metadanych klientom

Wiemy juz, z poprzednich wpisów, że możemy konfigurować WCF na dwa różne sposoby: poprzez pliki konfiguracyjne XML i bardziej dynamicznie, z poziomu kodu .NET. Najpierw przedstawię to drugie rozwiązanie. Żeby serwer był serwerem, zróbmy sobie prostą usługę z jedną metodą Hello (powtórka z rozwywki). Żeby nie odsyłać do innego artykułu zamieściłem kod interfejsu i implementacji poniżej:

//IHelloWorld.cs
using System.ServiceModel;

[ServiceContract]
interface IHelloWorld
{
    [OperationContract]
    string Hello();
}

//----------------------
//HelloWorld.cs
public class HelloWorldIHelloWorld
{
    public string Hello()
    {
        return "Hello World!";
    }
}

Czas na udostępnienie metadanych. Metadane to nic innego jak kolejna końcówka (ang. endpoint), udostępniająca informacje na temat obsługiwanych interfejsów i ich metod. Jednak to nie wszystko. Aby metadane naprawdę się pojawiły, należy zdefiniować sposób postępowania serwera (ang. behavior) w zakresie publikacji metadanych. Realizowane jest to przy pomocy klasy ServiceMetadataBehavior. Przyjrzyjmy się przykładowej implementacji serwera udostępniającego metadane:

using System;
using System.ServiceModel;
using System.ServiceModel.Description;

class Program
{
    static void Main(string[] args)
    {
        Uri adress = new Uri("http://localhost:2222/Hello");
        ServiceHost host = new ServiceHost(typeof(HelloWorld), adress);
       
        //Procedura udostępniania metadanych
        ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
        host.Description.Behaviors.Add(smb);
        Uri metadataAdress = new Uri(adress+"/mex");
        host.AddServiceEndpoint(typeof(IMetadataExchange),
            MetadataExchangeBindings.CreateMexHttpBinding(), metadataAdress);   

        host.AddServiceEndpoint(typeof(IHelloWorld), new BasicHttpBinding(), adress);
        host.Open();
        Console.WriteLine("Serwer uruchomiony");
        Console.ReadLine();
    }
}

Przeanalizujmy powyższy kod. Najpierw do klasy hosta dodajemy definicję sposobu postępowania dla metadanych (ServiceMetadataBehavior), a następnie, jak wspomniałem na początku, udostępniamy właściwą końcówkę. W naszym przypadku jest to końcówka /mex. Pełny adres metadanych będzie miał zatem następującą postać: http://localhost:2222/Hello/mex.

Standard WS-MetadataExchange (http://www.w3.org/TR/ws-metadata-exchange) nie definiuje docelowego adresu metadanych i jego względnego położenia. Zwyczajowo przyjęło się, że jest to końcówka /mex. Jest to częściowo spowodowane tym, że Visual Studio, (także svcutil.exe) wyszukując metadane, próbuje do bazowego adresu usługi dodać właśnie przyrostek /mex.

Napisałem wcześniej, że przy udostępnianiu metadanych, oprócz końcówki, wymagane jest przekazanie klasy ServiceMetadataBehavior. Gdy tego nie uczynimy, otrzymamy następujący komunikat:

System.InvalidOperationException: The contract name 'IMetadataExchange' could not be found in the list of contracts implemented by the service XXX. Add a ServiceMetadataBehavior to the configuration file or to the ServiceHost directly to enable support for this contract.

Nie ma zatem obaw, że o czymś zapomnimy. Komunikat jest wystarczająco czytelny. Czas przetestować końcówkę z metadanymi.

Testowanie metadanych

Ważne, aby przed testami uruchomić nasz serwer. To on udostępnia metadane i to z nim trzeba się komunikować. Wiem, że to głupie, ale wiele osób o tym zapomina. Procedura testowa będzie wyglądała natępująco:

  1. Uruchomienie Visual Studio i stworzenie nowego, dowolnego projektu. Może, a nawet - dla niedowiarków - powinien być to oddzielny proces. Nie będzie wtedy wątpliwości, że metadane wyciągane są przez WCF.
  2. W drzewie projektu wybieramy References/Add Service Reference i wpisujemy adres http://localhost:2222/Hello.
  3. Klikamy Go.
  4. Po chwili powinna się pokazać definicja naszej usługi. Warto zwrócić uwagę, że Visual Studio automatycznie zmienił adres na następujący: http://localhost:2222/Hello/mex. To pod tym adresem są metadane. To dlatego przyjąłem końcówkę /mex.

Jako ćwiczenie polecam zmianę końcówki na mex2 i powtórzenie scenariusza. Gdy operacja zakończy się niepowodzeniem, zamiast adresu http://localhost:2222/Hello należy podać http://localhost:2222/Hello/mex2. Metadane powinny się pojawić.

Włączenie metadanych w pliku konfiguracyjnym

Częstą praktyką jest konfigurowanie usług WCF z poziomu pliku App.config. Po przeanalizowaniu sposobu konfiguracji dynamicznej, zrozumienie konfiguracji statycznej będzie już łatwe. Przypomnę: potrzebujemy dodatkowej końcówki mex i definicji zachowania ServiceMetadataBehavior. Popatrzmy na przykład pliku App.config pokazany poniżej:

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

Należy zwrócić uwagę na drugą końcówkę (endpoint) i jej właściwość binding. Jest to odpowiednik linijki MetadataExchangeBindings.CreateMexHttpBinding(). Należy również pamiętać o właściwym protokole - tutaj wszędzie jest HTTP, dla TCP trzeba wpisać mexTcpBinding (lub w kodzie MetadataExchangeBindings.CreateMexTcpBinding()).

Jak już wiemy, do poprawnego działania końcówki metadanych potrzebowaliśmy klasy ServiceMetadataBehavior. Także w pliku konfiguracyjnym mamy tego ślad i jest to sekcja behaviors, wewnątrz której, wewnątrz dwóch innych elementów, znajduje się znacznik serviceMetadata. Pusty znacznik jest odpowiednikiem pustego, bezparametrowego konstruktora klasy ServiceMetadataBehavior.

WSDL i zgodność wstecz

Niektórzy powiadają, że MEX to dziecko WSDL (Web Service Definition Language). W pewnym sensie to prawda, choćby z racji wieku. Należy sobie zdać sprawę, że, pozostając w terminologii rodzinnej, niedaleko pada jabłko od jabłoni. Różnica jest bardzo subtelna - WSDL korzysta z metody GET HTTP, natomiast MEX z komunikatów SOAP (Simple Object Access Protocol). Druga drobna, wręcz niezauważalna dla klienta końcowego różnica polega na tym, że przy GET HTTP może istnieć konieczność kilku odwołań do serwera w celu pobrania dodatkowych schematów XML (pliki XSD). W SOAP wszystko jest połączone w jeden większy komunikat.

Włączenie obsługi GET HTTP

Domyślnie obsługa GET HTTP jest wyłączona, ale to żaden problem. Trzeba ją włączyć! Dla pliku konfiguracyjnego będzie to modyfikacja sekcji behavior i dodanie jednego atrybutu do elementu serviceMetadata:

<behavior>
  <serviceMetadata httpGetEnabled="true"/>
</behavior>

Próba uruchomienia doprowadzi do błędu następującej treści:

The HttpGetEnabled property of ServiceMetadataBehavior is set to true and the HttpGetUrl property is a relative address, but there is no http base address. Either supply an http base address or set HttpGetUrl to an absolute address.

Mamy dwie możliwości - albo ustawimy adres, pod który trzeba wysłać żądanie GET (HttpGetUrl jest domyślnie ustawiony na null, co jest interpretowane jako adres względny), albo ustalimy adres bazowy. Pokażę drugie rozwiązanie, bo pozwala nam nieco uprościć całą konfigurację. Przyjrzyjmy się zmodyfikowanej wersji:

<?xml version="1.0encoding="utf-8?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="HelloWorld">
        <endpoint binding="basicHttpBinding"
                  contract="IHelloWorld"/>
        <endpoint address="/mex"
                  binding="mexHttpBinding"
                  contract="IMetadataExchange"/>
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:2222/Hello"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

W pokazanej konfiguracji pojawiła się nowa sekcja - host. To w niej definiuje się adres bazowy całej usługi. Pociąga to za sobą inne zmiany - już nie trzeba jawnie wskazywać bazwzględnego adresu każdej końcówki. Zniknął atrybut address końcówki z usługą, bo pokrywa się on z adresem bazowym. Atrybut address końcówki z metadanymi uległ skróceniu - teraz jest to adres względny, ustanawiany względem adresu bazowego, do którego dodajemy przyrostek /mex.

Testowanie WSDL

Aby przetestować metadane przy pomocy HTTP GET wystarczy uruchomić dowolną przeglądarkę internetową i wkleić w pasku adresu następujący ciąg znaków:

http://localhost:2222/Hello?wsdl

Nie będę omawiał struktury pliku WSDL, bo nie jest to w tej chwili konieczne. Celem było pokazanie, że prawdziwie - są metadane.

WSDL w konfiguracji dynamicznej

Jak w każdym obszarze konfiguracji WCF zachęcam do wyszukiwania podobieństw między plikiem XML a wersją C#. Także tutaj, czyli we włączaniu HTTP GET jest podobieństwo: atrybut httpGetEnabled zostaje zastąpiony właściwością HttpGetEnabled. Popatrzmy na zmodyfikowany fragment:

//Procedura udostępniania metadanych
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
host.Description.Behaviors.Add(smb);
Uri metadataAdress = new Uri(adress+"/mex");
host.AddServiceEndpoint(typeof(IMetadataExchange),
    MetadataExchangeBindings.CreateMexHttpBinding(), metadataAdress); 

Podsumowanie

Metadane pozwalają innym aplikacjom rozpoznać nasze usługi i przygotować się na komunikację. Otwarty format XML sprawia, że grono odbiorców nie kończy się na aplikacjach .NET. Mogą to być aplikacje w Javie, w C++, może to być również dokument Word ze swoim VBA. Temat dokumentów Word i korzystania z usług WCF postaram się w najbliższym czasie rozwinąć.

Zachęcam do eksperymentowania z interfejsem i obserwowania zmian w dokumencie WSDL. Myślę, że dość szybko można dostrzec wiele zależności i zrozumieć konstrukcję metadanych bez przeciskania się przez dokumentacje.

Kategoria:Windows Communication Foundation

, 2013-12-20

Brak komentarzy - bądź pierwszy

Dodaj komentarz
Wyślij
Ostatnie komentarze
Dzieki za te informacje. były kluczowe
Dobrze wyjaśnione, dzięki !
a z innej strony - co gdybym ciąg znaków chciał mieć rozbity nie na wiersze a na kolumny? Czyli ciąg ABCD: 1. kolumna: A, 2. kolumna: B, 3. kolumna: C, 4 kolumna: D?
Ciekawy artykuł.
Czy można za pomocą EF wysłać swoje zapytanie?
Czy lepiej do tego użyć ADO.net i DataTable?