Pokazywanie postów oznaczonych etykietą WCF. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą WCF. Pokaż wszystkie posty

niedziela, 15 września 2013

Hostowanie serwisu REST, przetwarzanie wiadomości, tworzenie klientów

Aby ułatwić hostowanie serwisów opartych o REST została przedstawiona klasa WebServiceHost. Klasa ta zawiera potrzebne bindingi.
Jedyne co musimy podać to typ serwisu oraz adres pod którym będzie dostępny:


Code:
            var webServiceHost = new WebServiceHost(typeof(Service1), new Uri(@"http://localhost/RestService/"));
            webServiceHost.Open();
            Console.WriteLine("Press key to close");
            Console.Read();
            webServiceHost.Close();


Aby napisać klienta serwisu REST można skorzystać z klasy WebChannelFactory<T>. Kod tworzenia jest wręcz identyczny jak w przypadku połączenia z serwisem SOAP:


Code:
            var webChannelFactory = new WebChannelFactory<IService1>(new Uri("http://localhost/RestService/"));
            var channel = webChannelFactory.CreateChannel();
            List<Person> persons = channel.GetPersons();
            foreach (var person in persons)
            {
                Console.WriteLine("{0} {1}", person.Id, person.Name);
            }



Warto jeszcze w tym miejscu wspomnieć o klasie WebOperationContext. Klasa ta umożliwia dostęp do aktualnie przetwarzanego requestu w metodach serwisu. Możemy dzięki temu pobrać następujące dane:
  • URI wywołania
  • nagłówki 
  • ETag
  • format danych
  • długość wiadomości
Przykładowe wywołanie podczas wykonywania metody serwisu:


Czym jest REST i jak go użyć w WCF

Pisząc o WCF nie sposób pominąć REST. REST (Representational state transfer) - jest wzorcem pisania aplikacji rozproszonych udostępnianych po HTTP. REST zapewnia bardzo dużą skalowalność przez to iż nie jest przechowywany stan pomiędzy żądaniami.
Opiera się o słowa kluczowe dobrze znane z protokołu HTTP: GET, POST, PUT, DELETE. Dane mogą być przesyłane w postaci HTML, XML, JSON.
Wykorzystanie REST jest bardzo szerokie. Wystarczy wymienić, że tacy potentaci jak google, facebook czy flickr udostępniają cześć swojej funkcjonalności właśnie za pomocą REST.

WCF pozwala w bardzo prosty sposób udostępnić serwis jako REST.
Najważniejsza jest poprawna konfiguracja serwisu:


Code:
<?xml version="1.0"?>
<configuration>

  <appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5"/>
  </system.web>
  <system.serviceModel>
    <services>
      <service name="RestSample.Service1">
        <endpoint address="" binding="webHttpBinding" contract="RestSample.IService1" behaviorConfiguration="sampleBehavior">
        </endpoint>
      </service>
    </services>

    <behaviors>
      <endpointBehaviors>
        <behavior name="sampleBehavior">
          <webHttp helpEnabled="true"/>
        </behavior>
      </endpointBehaviors>
    </behaviors>
    
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <!--
        To browse web app root directory during debugging, set the value below to true.
        Set to false before deployment to avoid disclosing web app folder information.
      -->
    <directoryBrowse enabled="true"/>
  </system.webServer>

</configuration>


Dwie najważniejsze rzeczy jakie musimy wprowadzić do naszego standardowego configu:
  • typ bindingu - webHttpBinding 
  • do behaviors dodajemy webHttp
Co oznacza webHttpBinding i webHttp w przypadku tej konfiguracji?
WebHttpBinding:
  • tworzy kanał Http
  • wprowadza wsparcie dla plików cookie
  • protokoły i bezpieczeństwo (HTTP, HTTPS)
  • kodowanie wiadomości: XML, JSON
WebHttpBehavior:
  • operacje selekcji danych
  • serializacja danych
  • obsługa wywołań

Metody interfejsu możemy oznaczyć dodatkowymi atrybutami:
WebGet:
  • odpowiada czasownikowi HTTP GET
  • pozwala ustawić adres wywołania operacji (UtlTemplate) oraz parametry przekazywane do operacji
  • format wiadomości wyjściowej (XML, JSON)
 WebInvoke
  • pozostałe czasowniki protokołu HTTP
  • domyślnie wywoływany jest POST
  • pozwala na przekazanie parametrów; ostatni parametr wskazuje gdzie ma zostać zdeserializowana zawartość wiadomości
Warto zauważyć jeszcze jeden aspekt przedstawionej konfiguracji - helpEnabled. Dzięki temu przełącznikowi serwis jest w stanie sam wygenerować dokumentację wraz z przykładowymi zapytaniami. Przykładowa konfiguracja wygląda w następujący sposób:




Jest to bardzo przydatne - nie musimy tworzyć własnej dokumentacji - zostaje ona wygenerowana automatycznie.

70-513 Udostępnianie serwisu WCF

Aby ktoś mógł skorzystać, ze stworzonego przez na serwisu należy go udostępnić.

Adres
Adres wskazuje gdzie znajduje się nasz serwis składa się z 4 składowych: schema, nazwa hosta, portu, ścieżki (przeważnie nazwa serwisu). Przykład: http://www.contoso.com/OrderService/

Binding
Binding definiuje sposób, w jaki będzie można się dostać do serwisu. Są to elementy takie jak protokół, SSL. WCF udostępnia domyślnie kilka predefiniowanych możliwości komunikacji:
  • basicHttpBinding - HTTP, HTTPS, MTOM
  • wsHttpBinding - HTTP, HTTPS, MTOM - oferuje niezawodność, tranzakcyjność, bezpieczeństwo
  • wsDualHttpBinding - dwukierunkowa komunikacja 
  • webHttpBinding - przekazuje dane przez HTTP lub HTTPS nie korzystając z formatu protokołu SOAP - przydatny w przypadku gdy klient nie wymaga protokołu SOAP
  • wsFederationHttpBinding - zapewnia bezpieczeństwo pomiędzy różnymi systemami autoryzacji i autentykacji
  • netTcpBinding - przesyłanie danych po TCP
  • netNamedPipeBinding - jeżeli serwis będzie używany na jednym komputerze
  • netMsmqBinding - komunikacja pomiędzy dwoma komputerami
  • netPeerTcpBinding - komunikacja Peer-To-Peer pomiędzy komputerami
  • msmqIntegrationBinding - dla istniejących aplikacji napisanych w C++ używających do komunikacji MSMQ
  • basicHttpContextBinding - wsparcie plików cookie dla protokołu HTTP
  • netTcpContextBinding, netTcpContextBinding, wsHttpContextBinding - umożliwia użycie nagłówka w celu wymiany komunikatów

Contract
Określa operacje udostępniane przez serwis i jest to przeważnie nazwa interfejsu poprzedzona przestrzenią nazw.

Powyższe składowe można zadeklarować zarówno w kodzie jak i konfiguracji XML. XML jest preferowanym sposobem, gdyż w przypadku zmiany np. adresu nie ma konieczności kompilowania całej aplikacji od początku, a jedyna zmiana będzie dotyczyła pliku konfiguracyjnego.

Przykład konfiguracji w XML:

Code:
  <system.serviceModel>
    <services>
      <service name="WcfExposingService.OrderService">
        <endpoint address="http://localhost:8000/OrderService/" binding="basicHttpBinding" 
                  contract="WcfExposingService.IOrderService"/>
      </service>
    </services>
  </system.serviceModel>

Dla jednego serwisu można skonfigurować wiele sposobów komunikacji (endpoint), tak więc do jego serwisu możemy odnosić się poprzez protokół HTTP, TCP czy też inny sposób. W tym przypadku należy pamiętać tylko o tym, aby każdy z tych sposobów posiadał inny adres ekspozycji serwisu.
Aby sobie ułatwić pracę podczas definiowania wielu adresów, można skorzystać z właściwości base address i dodać do listy, bazowe adresy, które następnie zostaną uzupełnione odpowiednią końcówką. Uwaga! w przypadku hostowania na IIS bazowe adresy nie są brane pod uwagę, gdyż są mapowane na ścieżkę wirtualną do katalogu gdzie znajdują się pliki serwisu.
W przypadku adresu bazowego WCF rozpoznaje z jakim bindingiem ma do czynienia a następnie dobiera odpowiedni adres z dostępnej listy.
Przykład takiej konfiguracji:

Code:
    <services>
      <service name="WcfExposingService.OrderService">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:17354/OrderService/"/>
            <add baseAddress="net.tcp://localhost:17354/OrderService/"/>
          </baseAddresses>
        </host>
        <endpoint address="" binding="basicHttpBinding"
                  contract="WcfExposingService.IOrderService"/>
        <endpoint address="secure" binding="wsHttpBinding"
                  contract="WcfExposingService.IOrderService"/>
        <endpoint address="" binding="NetTcpBinding"
          contract="WcfExposingService.IOrderService"/>
      </service>
    </services>

Oraz konfiguracja w kodzie C#:

Code:
            Uri httpAddress = new Uri("http://localhost:8000/OrderService/");
            Uri tcpAddress = new Uri("net.tcp://localhost:8000/OrderService/");
            Uri[] baseAddresses = {httpAddress, tcpAddress};

            var serviceHost = new ServiceHost(typeof (OrderService), baseAddresses);
            var basicHttpBinding = new BasicHttpBinding();
            var wsHttpBinding = new WSHttpBinding();
            var netTcpBinding = new NetTcpBinding();

            serviceHost.AddServiceEndpoint(typeof (IOrderService), basicHttpBinding, "");
            serviceHost.AddServiceEndpoint(typeof(IOrderService), wsHttpBinding, "secure");
            serviceHost.AddServiceEndpoint(typeof(IOrderService), netTcpBinding, "");



Publikowanie metadanych
Metadane publikujemy za pomocą bindowania o typie mexHttpBinding. Metadane możemy pobrać dla serwisu za pomocą składni: nazwa_serwisu.svc?wsdl. Definiowanie w XML:

Code:
  <system.serviceModel>
    <services>
      <service name="WcfExposingService.OrderService">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:17354/OrderService/"/>
            <add baseAddress="net.tcp://localhost:17354/OrderService/"/>
          </baseAddresses>
        </host>
        <endpoint address="" binding="basicHttpBinding"
                  contract="WcfExposingService.IOrderService"/>
        <endpoint address="secure" binding="wsHttpBinding"
                  contract="WcfExposingService.IOrderService"/>
        <endpoint address="" binding="NetTcpBinding"
          contract="WcfExposingService.IOrderService"/>
        <endpoint address="mex" binding="mexHttpBinding"
                  contract="IMetadataExchange" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="MexGet">
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

I to samo w kodzie:

Code:
            ServiceMetadataBehavior serviceMetadataBehavior =
                serviceHost.Description.Behaviors.Find<ServiceMetadataBehavior>();
            if (serviceMetadataBehavior != null)
            {
                serviceMetadataBehavior = new ServiceMetadataBehavior();
                serviceMetadataBehavior.HttpGetEnabled = true;
                serviceHost.Description.Behaviors.Add(serviceMetadataBehavior);
            }
            serviceHost.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName,
                                           MetadataExchangeBindings.CreateMexHttpBinding(), "mex");

WCF - Pierwsze kroki

Jakiś czas temu otrzymałem maila z prośbą o napisanie krótkiego kursu w jaki sposób zacząć pracę z WCF.

Do czego służy WCF? Przede wszystkim do pisania aplikacji opartych o architekturę SOA - czyli zorientowaną na usługi. W internecie można znaleźć dokładne opisy czym jest WCF i co potrafi. Najważniejsze jest to, iż za pomocą jednego interfejsu możemy obsługiwać wiele kanałów (soap, tcp, msmq itp).

Kiedyś już pisałem na ten temat - za czasów .NET 3.5. Obecnie programowanie w WCF jest prostsze niż kiedyś. Właściwie po utworzeniu projektu, możemy bezpośrednio uruchamiać serwis bez potrzeby tworzenia jakiejkolwiek konfiguracji.

Aby stworzyć projekt WCF wybieramy odpowiedni szablon w oknie nowego projektu WCF:



Stworzony projekt zawiera już przykładową implementację serwisu. Możemy go bezpośrednio uruchomić (F5) i zobaczyć jak łatwo testuje się serwis WCF:



Jeżeli zobaczymy do pliku web.config:






zauważyć można że nie ma zdefiniowanych żadnych endpointów. Zdefiniowane są mapowania protokołów. Mapowania te znajdują się także w machine.config i są globalne dla wszystkich serwisów. Dzięki temu możemy użyć zdefiniowanych endpointów, Wszystkie dostępne możemy podglądnąć w narzędziu konfiguracyjnym WCF:



Narzędzie uruchamiamy klikając prawym przyciskiem na plik web.config i wybieramy Edit WCF Configuration.

W kolejnej części podstawowe pojęcia, które są związane z WCF.

WCF - Podstawowe pojęcia

Pierwszym podstawowym pojęciem w WCF jest Endpoint.
Endpoint to sposób udostępniania przez serwis swojej funkcjonalności. Definiuje on trzy ważne aspekty:



Mówi się czasami o zasadzie ABC, gdyż od tych liter pochodzą składowe Endpointa:

  • Address - definiuje adres pod jakim znajduje się serwis
  • Binidng - kanał komunikacji (np. tcpip, http)
  • Contract - typ wysyłanej wiadomości


Kolejnym ważnym terminem jest Service Behaviours. Poprzez ten mechanizm możemy kontrolować w jaki sposób będzie się zachowywał serwis.  Możemy wyszczególnić dwa aspekty, na które mamy wpływ - sposób tworzenia serwisu oraz możliwość wykonywania operacji wielowątkowo.



Dostępne tryby tworzenia serwisu:
  • PerCall - przy każdym żądaniu klienta tworzona jest nowa instancja serwisu - domyślna wartość
  • Single - wszystkie żądania obsługiwane są przez tę samą instancję serwisu - Singleton 
  • PerSession - nowa instancja serwisu tworzona jest dla każdej nowej sesji


Kontrolować wielowątkowość możemy wybierając odpowiednią opcję z pośród dostępnych:
  • Single - brak wielowątkowości 
  • Multiple - wielowątkowość włączona
  • Reentrant - jest to dosyć ciekawy przypadek. Działa on w ten sposób, jeżeli klient 1 wywołuje operację w serwisie, a operacja ta odnosi się do innego serwisu, to w tym czasie klient 2 może wywołać operację bez czekania na to aż zostanie zwolniony serwis przez pierwszego klienta. 


Wiadomości między klientem a serwisem wymieniane są za pomocą wiadomości. Wiadomość może być przesyłana w trzech dostępnych trybach:
  • One way - klient wysyła wiadomość i nie czeka na odpowiedź od serwisu 
  • Duplex - obustronna komunikacja między klientem i serwisem 
  • Request-Replay - po wysłaniu żądania przez klienta, zostaje odesłana odpowiedź z serwera

Dodatkowo możemy się spotkać z terminem Channel (kanał) - czyli kanał komunikacyjny (tcp, http, itp.).

Dynamiczne wybieranie endpointa

Jeżeli nasz serwis udostępnia endpoint mex-owy, klienci mają możliwość wyboru dynamicznego endpointa. Nie jest wymagane w tym przypadku tworzenie u klientów konfiguracji. Wystarczy podać adres endpointa mex-owego a wszystkie pozostałe endpointy zostaną automatycznie pobrane.

Aby tego dokonać po pierwsze serwis musi udostępnić endpoint mex. Jeżeli już mamy taki endpoint, na kliencie korzystamy z klasy MetadataResolver i metody Resolve. Metoda ta przyjmuje dwa parametry:
  • typ serwisu
  • adres endpointa mex
Po wywołaniu tej metody zostanie zwrócona kolekcja endpointów obsługiwanych przez aplikację. Możemy przejść po tej kolekcji i wybrać dowolny endpoint, który jest nam najwygodniej stosować. 
Przykładowy kod realizujący dynamiczne odnajdowanie endpointów:

 

Code:
            var serviceEndpointCollection = MetadataResolver.Resolve(typeof(IService1), new EndpointAddress("http://localhost/MyService/mex"));
            ChannelFactory<IService1> channelFactory = null;
            foreach (var endpoint in serviceEndpointCollection)
            {
                if (endpoint.Binding.GetType() == typeof(BasicHttpBinding))
                {
                    channelFactory = new ChannelFactory<IService1>(endpoint.Binding, endpoint.Address);
                    var channel = channelFactory.CreateChannel();
                    var data = channel.GetData(10);
                    Console.WriteLine(data);
                    //Close channel
                    ((IClientChannel)channel).Close();
                    //Close factory
                    channelFactory.Close();
                }
            }


W przykładzie pokazanym powyżej iterujemy po kolekcji zwróconych endpointów. W przypadku gdy odnajdziemy BasicHttpBinding tworzymy kanał a następnie wykonujemy operację.

 Zaletą tego rozwiązania jest przede wszystkim brak potrzeby trzymania dodatkowej konfiguracji endpointów w aplikacji. Wystarczy tylko adres endpointa mex, a pozostałe informacje zostaną pobrane w czasie działania programu. 

Konsumowanie serwisu

Aby można było korzystać z serwisu można zastosować jedną z kilku dostępnych metod. W zależności od naszych preferencji możemy skorzystać z następujących narzędzi:
  • bezpośrednio z klasy ChannelFactory<T> kiedy mamy dostęp do interfejsu servisu
  • dodanie referencji do serwisu z poziomu Visual Studio
  • narzędzie svcutil.exe
Pierwszy sposób jest bardzo wygodny. Wymaga jednak współdzielenia biblioteki zawierającej interfejs serwisu co nie zawsze jest możliwe do osiągnięcia.
Użycie klasy jest niezwykle proste:


Code:
            var channelFactory = new ChannelFactory<IService1>(new BasicHttpBinding(),
                new EndpointAddress("http://localhost/MyService/"));
            var channel = channelFactory.CreateChannel();
            var data = channel.GetData(10);
            Console.WriteLine(data);
            //Close channel
            ((IClientChannel)channel).Close();
            //Close factory
            channelFactory.Close();

Należy przy tym pamiętać, że kanał należy zamknąć w celu odzyskania zasobów. Drugą ważną informacją jest to, iż gdy wystąpi jakiś błąd, nie możemy użyć metody Close. W takim przypadku należy skorzystać z metody Abort.
W pokazanym przypadku powyżej, dane konfiguracyjne zostały podane w kodzie. Klasa ChannelFactory<T> posiada klika różnych konstruktorów. Jeden z nich przyjmuje nazwę konfiguracji z app/web config. W takim przypadku możemy konfigurację zapisać w pliku i zmienić ją w czasie działania programu bez potrzeby jej ponownej kompilacji. 

Dodanie referencji z poziomu Visual Studio jest bardzo proste. Klikamy prawym klawiszem myszy na projekt i dodajemy Service Reference:






W oknie które się pojawi możemy wpisać adres serwisu lub, jeżeli serwis jest uruchomiony na tej samej maszynie wystarczy kliknąć przycisk Discover. Spowoduje to odnalezienie wszystkich serwisów udostepnionych na tej maszynie:


Po kliknięciu w przycisk OK zostaną wygenerowane klasy typów oraz klasa Proxy zawierająca metody interfejsu. 

Kolejną możliwością jest użycie narzędzia svcutil.exe (ServiceModel Metadata Utility Tool). Jest to proste narzędzie konsolowe. Znaleźć je możemy tam gdzie zainstalowane jest Microsoft SDKs. W moim przypadku jest to ścieżka: C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools.
Narzędzie jest bardzo proste w obsłudze. Przykład:


Narzędzie udostępnia wiele różnych przełączników umożliwiających m.in. wybranie serializatora, podanie danych uwierzytelniania itp.

Po zakończeniu działania programu w wybranym katalogu zostaną wygenerowane dwa pliki: .cs z kodem dla klienta oraz plik konfiguracyjny (dane dotyczące endpointa):



Na temat narzędzia svcutil można bardzo dużo informacji znaleźć w internecie jak i na stronach msdn.

Który sposób jest najlepszy? Jeżeli mamy bezpośredni dostęp do interfejsu - najwygodniejszym narzędziem będzie ChannelFactory<T>. W przypadku gdy nie mamy takiego dostępu możemy skorzystać z Visuala lub svcutil.exe. To drugie narzędzie jest szczególnie przydatne jeżeli chcemy dostosować wygenerowany kod do naszych potrzeb, a w przypadku aktualizacji chcemy mieć nad tym pełną kontrolę.

Obsługa wyjątków w WCF

WCF pozwala na przekazywanie wyjątków do klienta. Oczywiście wyjątki, które znamy z .NET nie możemy wprost przesyłać do klienta - należy pamiętać, że nasz serwis może być obsługiwany przez klientów innych platform.
Wyjątki którymi się posługujemy w WCF są typu FaultException oraz generyczna odmiana FaultException<T>. Aby zaznaczyć, że metoda może zwrócić wyjątek należy oznaczyć ją atrybutem FaultContract(typeof(ExceptionType)):


Code:
        [OperationContract]
        [FaultContract(typeof(string))]
        string GetData(int value);

Metodę następnie możemy zaimplementować w następujący sposób:


Code:
        public string GetData(int value)
        {
            if (value <= 0)
            {
                throw new FaultException<string>("Value must be greater than 0", "Incoret value");
            }
            return string.Format("You entered: {0}", value);
        }

Ogólnie przyjęte jest, że nie należy wysyłać do klienta oryginalnego wyjątku. Zobaczmy na poniższy diagram:



Z diagramu wynika, że oryginalny wyjątek przed wysłaniem do klienta musi zostać przetworzony. Nie przekazujemy oryginalnego wyjątku klientowi, aby nie ujawniać mu wewnętrznej struktury naszego serwisu. Ogólnie przyjmuje się, że po stronie serwisu:
  • przechwytujemy zawartość wyjątku, kto i kiedy go spowodował oraz oryginalne żądanie
  • logujemy zawartość Exception do bazy lub pliku
  • tworzymy identyfikator zgłoszenia - ułatwi to rozmowę pomocy technicznej z danym klientem
Klientowi przekazujemy następujące informacje:
  • wiadomość typu: "W serwisie wystąpił błąd..."
  • kod błędu (np HTTP 5xx)
  • dodajemy także informacje kontaktowe z pomocą techniczną oraz identyfikator problemu
Powyższe działania określane są często mianem Sanitizing Response - tłumacząc na polski usuwamy z odpowiedzi to co nie jest potrzebne klientowi i potencjalnie szkodliwe dla nas.


Aplikacja kliencka następnie może przechwycić wyjątek, tak jak każdy inny. Przykładowy kod klienta:


Code:
            var channelFactory = new ChannelFactory<IService1>(new BasicHttpBinding(),
                new EndpointAddress("http://localhost.:30000/MyService/"));
            var channel = channelFactory.CreateChannel();
            try
            {
                var data = channel.GetData(-5);
                Console.WriteLine(data);
            }
            catch (FaultException<string> ex)
            {
                Console.WriteLine(ex.Reason);
                Console.WriteLine(ex.Detail);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            ((IClientChannel)channel).Close();

WSDL vs MEX

Aby zewnętrzni producenci oprogramowania mogli skorzystać z naszego serwisu muszą w jakiś sposób pobrać informacje o formacie przesyłanych wiadomości.

Do udostępniania opisu serwisu możemy wykorzystać WSDL (Web Services Description Language) lub MEX (Metadata Exchange Endpoint). Jedno jak i drugie rozwiązanie tworzy opis serwisu - ten sam. Różnica polega na tym, iż w przypadku MEX tworzony jest specjalny endpoint, który zapytany o opis serwisu zwraca opis jako jedna wiadomość SOAP.

Podsumujmy
WSDL:
  • dokument WSDL dostępny jest poprzez wysłanie zapytania HTTP GET - wyświetlić możemy go w przeglądarce
  • dokument WSDL może zawierać odnośniki do innych dokumentów (np. schemy) - podział na wiele dokumentów

MEX:
  • tworzony jest specjalny endpoint udostępniający informacje w formacie SOAP
  • jako odpowiedź otrzymujemy jeden dokument zawierający wszystkie dane o serwisie - brak podziału na wiele dokumentów jak w przypadku WSDL
  • pozwala pobrać klientowi informacje o udostępnianych endpointach co pozwala uniknąć przechowywania konfiguracji ich w konfiguracji aplikacji 
  • możliwość pobrania za pomocą tcp, http, https, named pipes

Kiedy użyć MEX a kiedy WSDL?
WSDL generalnie używamy kiedy nasz serwis korzysta z komunikacji za pomocą HTTP. W przypadku użycia tcp, named pipes czy też innego protokołu - udostępniamy opis serwisu za pomocą  MEX.
Większość napisanych już serwisów korzysta z WSDL - standard ten istnieje na rynku już od dłuższego czasu.


Znając różnice zobaczmy w jaki sposób aktywować udostępnianie WSDL oraz MEX.
WSDL:
W przypadku tworzenia projektu na starcie obsługa WSDL jest włączona domyślnie - nie jest wymagana żadna dodatkowa konfiguracja. Wpis który mówi o aktywności WSDL jest następujący:

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


Brak potrzeby definiowania konfiguracji zawdzięczamy domyślnym endpointom, które zostały wprowadzone w wersji 4.0 frameworka.


MEX
Podobnie jak powyższy także nie wymaga żadnej dodatkowej konfiguracji - dostępny jest domyślnie. Jeżeli chcielibyśmy hostować aplikację z udostępnianiem MEX w aplikacji konsolowej, moglibyśmy to zrobić w następujący sposób:


Code:
        static void Main(string[] args)
        {
            var serviceHost = new ServiceHost(typeof(Service1), new Uri("http://localhost/MyService"));
            var smb = serviceHost.Description.Behaviors.Find<ServiceMetadataBehavior>() ?? new ServiceMetadataBehavior();
            smb.HttpGetEnabled = true;
            smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
            serviceHost.Description.Behaviors.Add(smb);
            serviceHost.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName,
                MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
            serviceHost.AddServiceEndpoint(typeof(IService1), new BasicHttpBinding(), "");
            serviceHost.Open();
            foreach (var endpoint in serviceHost.Description.Endpoints)
            {
                Console.WriteLine(endpoint.Address);
            }
            Console.WriteLine("Press key to exit");
            Console.ReadLine();
            serviceHost.Close();
        }


Kod został zaczerpnięty z dokumentacji MSDN.

Podsumowując: WCF umożliwia udostępnianie metadanych na dwa sposoby:


WCF własna konfiguracja endpointów

WCF od wersji 4.0 ułatwia konfigurację standardowo wykorzystywanych endpointów. Jeżeli jednak chcemy sami zdefiniować endpoint, będzie trzeba taką konfigurację napisać ręcznie lub skorzystać z gotowego narzędzia.

Aby skorzystać z narzędzia konfiguracyjnego, klikamy na pliku web.config i z menu kontekstowego wybieramy opcję Edit WCF Configuration:





Zostanie otwarty edytor




klikamy na Create a New Service... - zostanie otwarty kreator, który pozwoli skonfigurować endpoint w kilku krokach:

1. Wprowadzamy typ serwisu, dla którego tworzymy endpoint (możemy go też wybrać za pomocą klawisza Browse...)



 2. Kolejny krok pozwala nam wybrać kontrakt:


3. Następnie wybieramy czy chcemy korzystać z domyślnej konfiguracji bindingu czy też chcemy stworzyć nową:


4. W kolejnym kroku definiujemy sposób komunikacji z naszym serwisem:


5. Następny krok pozwala nam zadecydować czy korzystamy z rozszerzeń WS czy też z podstawowej wersji bindingu:


6. Przedostatni krok pozwala wprowadzić adres serwisu:


7. Ostatni ekran wyświetla podsumowanie tworzenia serwisu:



Po kliknięciu na Finish konfiguracja serwisu zostanie zapamiętana. Dodatkowo możemy jeszcze skonfigurować binding:






Konfigurować możemy takie aspekty jak timeout, wielkość przesyłanej wiadomości, sposób przesyłania wiadomości, wielkości przesyłanych tablic itd.

Konfiguracja za pomocą wbudowanego w Visual Studio narzędzia jest prosta i intuicyjna. W przypadku jeżeli chcemy cały serwis skonfigurować ręcznie w web.configu otrzymamy pomoc ze strony IntelliSense.

WCF - Budowa serwisu

Znając podstawową terminologię WCF, możemy przystąpić do budowy serwisu.

W edytorze usuwamy domyślnie stworzone pliki i dodajemy nowy serwis. Nazywamy go CustomerService. Jak nazwa mówi, będzie umożliwiał pobieranie danych o klientach.
Po dodaniu serwisu do projektu zostaną stworzone dwa pliki: CustomerService.svc oraz ICustomerService.cs.
Pierwszy plik implementuje metody znajdujące się w interfejsie zdefiniowanym w drugim pliku. Możemy usunąć domyślnie stworzoną metodę DoWork().

Serwis będzie posiadać następujący interfejs:


Code:
using System.Collections.Generic;
using System.ServiceModel;

namespace Wcf_SimpleService
{
    [ServiceContract]
    public interface ICustomerService
    {
        [OperationContract]
        List<Customer> GetCustomers();

        [OperationContract]
        Customer GetCustomerById(int customerId);

        [OperationContract]
        void AddCustomer(Customer customer);

        [OperationContract]
        void RemoveCustomerById(int customerId);

        [OperationContract]
        int GetCustomerCount();
    }
}


Widać tutaj użycie dwóch ważnych atrybutów:
  • ServiceContract - definiuje dany interfejs jako kontrakt dla serwisu WCF
  • OperationContract - definiuje składową, z której będzie mógł korzystać klient (znajdzie się ona w WSDL)
Każdy z tych atrybutów pozwala dodatkowo ustawić specyficzne dla siebie atrybuty. ServiceContract pozwala ustawić m.in. przestrzeń nazw,interfejs używany w przypadku komunikacji dwustronnej, czy też poziom ochorny. OperationContract pozwala na zmianę nazwy metody w generowanym WSDLu,wybranie czy metoda ma być typu one-way czy request-respond itp.

Kolejnym krokiem w tworzeniu serwisu jest implementacja metod:


Code:
using System.Collections.Generic;
using System.ServiceModel;
using System.Linq;

namespace Wcf_SimpleService
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class CustomerService : ICustomerService
    {
        private List<Customer> customers = new List<Customer>
            {
                new Customer {Id = 1, FirstName = "Jan", LastName = "Kowalski"},
                new Customer {Id = 2, FirstName = "Marek", LastName = "Wierzbicki"}
            };

        public List<Customer> GetCustomers()
        {
            return customers;
        }

        public Customer GetCustomerById(int customerId)
        {
            return customers.Single(c => c.Id == customerId);
        }

        public void AddCustomer(Customer customer)
        {
            customers.Add(customer);
        }

        public void RemoveCustomerById(int customerId)
        {
            customers.Remove(customers.Single(x => x.Id == customerId));
        }

        public int GetCustomerCount()
        {
            return customers.Count;
        }
    }
}

Na klasie CustomerService został nałożony atrybut ServiceBehaviour. Ktoś może zapytać: dlaczego ten atrybut nie został nałożony na interfejsie? Odpowiedź jest bardzo prosta: ponieważ dotyczy on bezpośrednio serwisu, nie jego interfejsu. Serwis będzie udostępniał listę klientów, którą będziemy modyfikowali za pomocą poszczególnych operacji, dlatego też został oznaczony flagą InstanceContextMode.Singe.
Implementacje metod nie są niczym specjalnym, dlatego nie będę ich szczegółowo omawiać.

Ostatnim elementem jest klasa modelu - Customer:


Code:
using System.Runtime.Serialization;

namespace Wcf_SimpleService
{
    [DataContract]
    public class Customer
    {
        [DataMember]
        public int Id { get; set; }

        [DataMember]
        public string FirstName { get; set; }

        [DataMember]
        public string LastName { get; set; }
    }
}

Dwa kolejne atrybuty które znajdą zastosowanie w naszych serwisach to:
  • DataContract - atrybut ten nakładamy na klasę która ma podlegać serializacji 
  • DataMember - atrybut ten determinuje pola które mają podlegać serializacji
Jeżeli w klasie nie zostanie na pole nałożony atrybut DataMemeter wartość takiego pola jak i samo pole nie będzie brało udziału w serializacji. 

Kolejnym ważnym aspektem jest hostowanie serwisu WCF. Tym razem przedstawię jeden z prostszych czyli self hosting - serwis zostanie hosotowany w zwyczajnej aplikacji konsolowej (o hostowaniu więcej w kolejnym poście).

Tworzymy nowy projekt typu Console Application i dodajemy następujący kod:


Code:
using System;
using System.Linq;
using System.ServiceModel;
using Wcf_SimpleService;

namespace HostClientApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var host = new ServiceHost(typeof(CustomerService), new Uri("net.tcp://localhost/netTCP"), new Uri("http://localhost/CustomerService")))
            {
                host.Open();
                host.Description.Endpoints.ToList().ForEach(endPoint => Console.WriteLine(endPoint.ListenUri));
                Console.WriteLine("Press Enter to Exit");
                Console.Read();
            }
        }
    }
}

Hosotwanie odbywa się przy użyciu klasy ServiceHost, której jako parametry przekazujemy typ hosotwanego serwisu oraz adresy pod którymi będzie dostępny dla klientów.

Ostatnim elementem jest stworzenie aplikacji klienckiej za pomocą której możemy korzystać z serwisu. Aplikacja to program typu konsolowego, gdzie za pomocą poleceń wprowadzanych z klawiatury można wykonywać operacje:


Code:
using System;
using System.ServiceModel;
using Wcf_SimpleService;

namespace ClientApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            var channelFactory = new ChannelFactory<ICustomerService>(new BasicHttpBinding());
            ICustomerService channel = channelFactory.CreateChannel(new EndpointAddress("http://localhost/CustomerService"));
            
            Console.WriteLine("Select operation:");
            Console.WriteLine("1 Print all customers");
            Console.WriteLine("2 Print customer by id");
            Console.WriteLine("3 Add new customer");
            Console.WriteLine("4 Remove customer by id");
            Console.WriteLine("5 Get customers count");
            Console.WriteLine("0 quit");
            var ok = true;
            while (ok)
            {
                Console.Write("Podaj operacje: ");
                int option = int.Parse(Console.ReadLine());
                switch (option)
                {
                    case 0:
                        ok = false;
                        break;
                    case 1:
                        PrintCustomers(channel);
                        break;
                    case 2:
                        Console.Write("Podaj id klienta: ");
                        PrintCustomerById(channel, ReadInt());
                        break;
                    case 3:
                        Console.Write("Podaj imię: ");
                        string firstName = Console.ReadLine();
                        Console.Write("Podaj nazwisko: ");
                        string lastName = Console.ReadLine();
                        Console.Write("Podaj id: ");
                        int id = ReadInt();
                        var customer = new Customer {FirstName = firstName, LastName = lastName, Id = id};
                        channel.AddCustomer(customer);
                        break;
                    case 4:
                        Console.WriteLine("Podaj id klienta, ktorego chcesz usunac: ");
                        channel.RemoveCustomerById(ReadInt());
                        break;
                    case 5:
                        Console.WriteLine(channel.GetCustomerCount());
                        break;
                }
                Console.WriteLine();
            }
        }

        private static void PrintCustomers(ICustomerService channel)
        {
            var customers = channel.GetCustomers();
            customers.ForEach(x => Console.WriteLine("{0} {1} {2}", x.Id, x.FirstName, x.LastName));
        }

        private static void PrintCustomerById(ICustomerService channel, int id)
        {
            var customer = channel.GetCustomerById(id);
            Console.WriteLine("{0} {1} {2}", customer.Id, customer.FirstName, customer.LastName);
        }

        private static int ReadInt()
        {
            return int.Parse(Console.ReadLine());
        }
    }
}


Powyższy kod nie jest szablonowym przykładem w jaki sposób tworzyć kod aplikacji, służy tylko jako przykład wywoływania operacji na serwisie.

Kod aplikacji testowej można pobrać stąd: http://sdrv.ms/11gW6nS

sobota, 14 września 2013

WCF - Hostowanie serwisu

W poprzednim poście został opisany sposób w jaki stworzyć prosty serwis.
Ten post omówi możliwości i sposoby hostowania serwisu.

Dowiedzieliśmy się już że serwis można hostować w innej aplikacji .NET za pomocą klasy ServiceHost. Klasa ta pozwala na hostowanie serwisu WCF w dowolnej aplikacji .NET.

Prawidłowa obsługa tworzenia instancji klasy ServiceHost przebiega w następujący sposób:


Code:
            var host = new ServiceHost(typeof(CustomerService), new Uri("net.tcp://localhost/netTCP"),
                    new Uri("http://localhost/CustomerService"));
            try
            {
                host.Open();
                Console.WriteLine("Press Enter to Exit");
                Console.Read();
                host.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                host.Abort();
            }

Drugą możliwością prawidłowej obsługi tworzenia klasy ServiceHost jest użycie bloku using, pokazany we wcześniejszym poście:


Code:
            using (var host = new ServiceHost(typeof(CustomerService), new Uri("net.tcp://localhost/netTCP"), new Uri("http://localhost/CustomerService")))
            {
                host.Open();
                host.Description.Endpoints.ToList().ForEach(endPoint => Console.WriteLine(endPoint.ListenUri));
                Console.WriteLine("Press Enter to Exit");
                Console.Read();
            }


Czym różni się Close od Abort:
  • Close - standardowy sposób zamykania kanału komunikacji w wyniku którego:
    • operacje które trwają nie zostaną przerwane
    • możemy tę operację przeprowadzić asynchronicznie
    • zwalnia zasoby
    • może rzucić wyjątkiem CommunicationException lub TimeoutException
  • Abort - wymuszone zamknięcie kanału komunikacji
    • zamyka kanał bez oczekiwania na zakończenie operacji, które są w toku
Abort stosujemy kiedy na kanale komunikacyjnym wystąpił wyjątek. W przypadku, gdy na kanale który rzucił wyjątkiem zastosujemy operację Close, kanał rzuci kolejnym wyjątkiem, którego możemy zapomnieć obsłużyć.

W przypadku wyboru Self hostingu najlepszym typem aplikacji do hostowania serwisu WCF jest Windows Service. Powody, dla których warto korzystać z Windows Service w celu hostowania WCF:
  • Zapewnione wznawianie usługi w przypadku wystąpienia błędów
  • Konfigurowalne zachowanie uruchamiania aplikacji
  • Konfiguracja zabezpieczeń
Dlaczego możemy chcieć hostować aplikację w ten sposób?
  • kiedy chcemy aby nasz serwis działał w aplikacji klienckiej 
  • kiedy serwis ma działać InProc czyli w tym samym procesie co aplikacja główna
  • kiedy nie możemy użyć IIS
  • kiedy chcemy mieć nieograniczone możliwości konfiguracji i rozszerzania serwisu 
W przypadku gdy skorzystamy ze zwyczajnej aplikacji (np. konsolowej) powyższe rzeczy będą musiały zostać zaimplementowane osobno.


Inną metodą hostowania aplikacji WCF jest wykorzystanie serwera IIS. Jest to jeden z popularniejszych sposobów hostowania aplikacji.
Hostowanie aplikacji na IIS ma szereg zalet:
  • hostowanie aplikacji WCF przebiega w ten sam sposób jak aplikacji webowych
  • udostępnione są narzędzia do monitorowania, zarządzania i kontroli hostowanej aplikacji
  • możliwość wykorzystania współdzielenia tego samego procesu do obsługi wielu aplikacji w celu zmniejszenia obciążenia na serwerze przez dużą ilość serwisów 

Jeżeli chcemy mieć pełną kontrolę nad cyklem życia aplikacji WCF najlepiej skorzystać z hostowania w Windows Service. W innym przypadku warto wybrać IIS.

W jaki sposób hostować aplikację na IIS? Bardzo dobrze opisane jest to na stronie MSDN: http://msdn.microsoft.com/pl-pl/library/ms733766.aspx

ASP.NET AJAX endpoint

Aplikacje korzystające z JSONa i Ajaxa na dobre zagościły w internecie. Strony, które nie posiadają ułamka interaktywności znanego z aplikacji desktopowych - przechodzą do lamusa. W ich miejsce wchodzą w pełni interaktywne witryny korzystające z HTML 5 oraz mnóstwa JavaScript-u.
Aby serwis WCF mógł obsługiwać zapytania od klientów AJAX-owych należy dodać do endpointa WebScriptEnablingBehavior. Klasa ta niesie za sobą następujące konsekwencje:
  • JSON jako domyślny format wysyłania wiadomości
  • możliwość wywoływania zapytań Ajaxowych na operacjach serwisu
  • operacje nie trzeba dekorować atrybutami WebGet lub WebInvoke
Aby ułatwić sobie pracę można skonfigurować plik svc i dodać odwołanie do WebScriptServiceHostFactory. Dzięki temu odpowiedni endpoint zostanie dodany do serwisu.
Przykład:

Code:
<%@ ServiceHost Language="C#" Debug="true" Service="JsonService.Service1" CodeBehind="Service1.svc.cs" Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory"%>

Powyższy trik zadziała w przypadku hostowania aplikacji na IIS.
Niestety udostępnianie serwisu w powyższej postaci ma też swoje wady. W przypadku wystąpienia błędu zostanie zawsze zwrócony błąd HTTP 400 - można to oczywiście pominąć i odpowiedni opakować zwracany błąd. Ponadto w przypadku przekazywania parametrów - UriTemplate nie pozwala na przekazanie tablic.

Pomimo ograniczeń zastosowanie powyższego udogodnienia pozwala na szybkie postawienie serwisu mogącego być konsumowanym przez klientów ASP.NET Ajax.

środa, 11 września 2013

Syndication programming model

Tytuł postu po angielsku - pod taką nazwą łatwiej znaleźć dodatkowe materiały w sieci, a tłumaczenie jej na polski nie przysłużyłoby mi czytelników :)

O co więc chodzi? Syndication programming model to nic innego jak udostępnianie danych w niezależnym formacie. Model ten jest wykorzystywany szczególnie w przypadku blogów. Na pewno każdy spotkał się z terminem kanału RSS. Udostępnienie kanału RSS w przypadku .NET jest niezwykle proste. Framework oferuje dwie gotowe klasy Atom10FeedFormatter oraz Rss20FeedFormatter.

Implementację rozpoczynamy od dodania dwóch atrybutów na interfejs serwisu:


Code:
    [ServiceKnownType(typeof(Atom10FeedFormatter))]
    [ServiceKnownType(typeof(Rss20FeedFormatter))]
    [ServiceContract]
    public interface IService1

Do interfejsu dodajemy metodę odpowiedzialną za generowanie zawartości kanału:


Code:
        [WebGet(UriTemplate = "personsFeed")]
        SyndicationFeedFormatter GetPersonFeed();

Ostatni etap to implementacja metody:

Code:
        public SyndicationFeedFormatter GetPersonFeed()
        {
            var persons = new List<Person>
            {
                new Person {Id = 1, Name = "Patryk"},
                new Person {Id = 2, Name = "Marek"},
                new Person {Id = 3, Name = "Sebastian"},
                new Person {Id = 4, Name = "Ania"}
            };

            var feed = new SyndicationFeed(persons.Select(x => new SyndicationItem(x.Id.ToString(), x.Name, null)));
            string format = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters["format"];
            if (!string.IsNullOrWhiteSpace(format) && format.Equals("atom"))
            {
                return new Atom10FeedFormatter(feed);
            }
            return new Rss20FeedFormatter(feed);
        }


Serwis możemy podglądnąć w przeglądarce:






Przykładowy wynik jest bardzo prostym zastosowaniem kanału RSS. Więcej o możliwościach i architekturze RSS można poczytać na stronach msdn.