70-503: Customizing and Extending Bindings

Ten artykuł pochodzi z serii przygotowań do egzaminu 70-503: Windows Communication Foundation.

Wiemy już jak wystawić światu naszą usługę. Najczęściej skorzystamy z jakiegoś standardu i to nam wystarczy. Czasem jednak potrzeba coś dopasować do naszych potrzeb.

Dostosowywanie standardowych bindowań

Każdy ze standardowych sposobów komunikacji (ang. bindings) ma kilka właściwości, które możemy dowolnie zmieniać. Np. wsHttpBinding ma następujące właściwości:

  • AllowCookies – czy klient akceptuje i tworzy ciasteczka (ang. Cookies), domyślnie False,
  • BypassProxyOnLocal – czy używać proxy dla lokalnego adresu, domyślnie False,
  • CloseTimeout – ilość czasu jaką należy odczekać przy zamykaniu połączenia zanim zostanie wyrzucony wyjątek, domyślnie 1 minuta
  • HostNameComparisonMode – w jaki  sposób nazwa hosta jest używana do dostępu do serwisu, domyślnie StrongWildcard,
  • MaxBufferPoolSize – maksymalny zakres pamięci dla buffer manager’a, domyślnie 65536 bajtów,
  • MaxRecievedMessageSize – maksymalny rozmiar odbieranej wiadomości, domyślnie 65536 bajtów,
  • MessageEncoding – kodowanie wiadomości MTOM lub Text/XML, domyślnie Text,
  • Name – nazwa bindowania, domyślnie Null,
  • Namespace – przestrzeń nazw XML, domyślnie http://tempuri.org/,
  • OpenTimeout – ilość czasu na otwarcie połączenia, domyślnie 1 minuta,
  • ProxyAddress – adres proxy, ignorowany gdy UseDefaultWebProxy jest ustawione na True, domyślnie Null,
  • ReaderQuotas – ograniczenia dla zawartości wiadomości, domyślnie None,
  • ReceiveTimeout – jak długo połączenie może być nieaktywne, domyślnie 10 minut,
  • SendTimeout – ilość czasu na wysyłanie wiadomości, domyślnie 1 minuta,
  • TextEncoding – kodowanie tekstu wiadomości, domyślnie UTF8Encoding,
  • TransactionFlow – czy wspierać transakcje, domyślnie False,
  • UseDefaultWebProxy – czy używać domyślnego proxy HTTP, domyślnie True.

Informacje o parametrach pozostałych bindowań znajdziemy w dokumentacji MSDN.

Teraz trochę kodu. Ustawianie parametrów w pliku App.config wygląda następująco (w linii 13 jest ustawiona właściwość CloseTimeout na 3 minuty):

   1: <system.serviceModel>
   2:     <services>
   3:         <service name="OrderService">
   4:             <endpoint address=""
   5:                 contract="MyNamespace.IOrderService"
   6:                 binding="WsHttpBinding"
   7:                 bindingConfiguration="CloseTimeout">
   8:             </endpoint>
   9:         </service>
  10:     </services>
  11:     <bindings>
  12:         <wsHttpBinding>
  13:             <binding name="CloseTimeout" closeTimeout="00:03:00">
  14:             </binding>
  15:         </wsHttpBinding>
  16:     </bindings>
  17: </system.serviceModel>

To samo możemy zrobić z poziomu C#:

   1: ServiceHost host = new ServiceHost(typeof(OrderService));
   2: WSHttpBinding wsBinding = new WSHttpBinding();
   3: TimeSpan ts = new TimeSpan(00, 03, 00);
   4: wsBinding.CloseTimeout = ts;
   5: host.AddServiceEndpoint(
   6:     typeof(MyNamespace.IOrderService),
   7:     wsBinding,
   8:     "http://localhost:8000/OrderService/");

Własne bindowania

 

Jeśli nie wystarczy nam zmodyfikowanie właściwości któregoś ze standardowych bindowań, zawsze możemy utworzyć własną klasę obsługującą komunikację (musi one dziedziczyć po CustomBinding). Binding składa się z elementów kontrolujących poszczególne elementy: protokół, kodowanie, itp. Najważniejsze jest by poszczególne elementy były dodawane w odpowiedniej kolejności:

  • Transaction flow – obsługa transakcji,
  • Reliability – obsługa kanałów transmisji, umożliwia zachowanie kolejności wiadomości, ten element dostępny jest dla netTcpBinding, wsHttpBinding i wsDualHttpBinding,
  • Security – obsługa autoryzacji, autentykacji, zabezpieczeń itp.
  • Transport – specyfikacja transportu: właśna lub któraś z następujących: TCP, NamedPipes, HTTP, HTTPS, MSMQ lub Peer-to-Peer,
  • Encoding – obsługa kodowania wiadomości jako tekst, binarnie lub MTOM,

Ostatnie dwa elementy (Transport, Encoding) są wymagane, a pozostałe są opcjonalne. Tak jak wcześniej, możemy definiować bindowania w pliku App.config lub  w kodzie C#:

   1: <system.serviceModel>
   2:     <services>
   3:         <service name="OrderService">
   4:             <endpoint address="http://localhost:8000/OrderService/"
   5:                 contract="MyNamespace.IOrderService"
   6:                 binding="customBinding"
   7:                 bindingConfiguration="NewBinding">
   8:             </endpoint>
   9:         </service>
  10:     </services>
  11:     <bindings>
  12:         <customBinding>
  13:             <binding name="NewBinding">
  14:                 <reliableSession />
  15:                 <security>
  16:                     <localServiceSettings inactivityTimeout="00:10:10"/>
  17:                 </security>
  18:                 <httpTransport/>
  19:                 <textMessageEncoding />
  20:             </binding>
  21:         </customBinding>
  22:     </bindings>
  23: </system.serviceModel>

 

   1: ServiceHost host = new ServiceHost(typeof(MyNamespace.OrderService));
   2: BindingElementCollection bec = new BindingElementCollection();
   3:  
   4: SymmetricSecurityBindingElement ssbe = new
   5:     SymmetricSecurityBindingElement();
   6: ssbe.LocalServiceSettings.InactivityTimeout = new TimeSpan(0, 10, 0);
   7: bec.Add(ssbe);
   8: bec.Add(new TextMessageEncodingBindingElement());
   9: bec.Add(new HttpsTransportBindingElement());
  10:  
  11: CustomBinding customBinding = new CustomBinding(bec);
  12:  
  13: host.AddServiceEndpoint(
  14:     typeof(MyNamespace.IOrderService),
  15:     customBinding,
  16:     "http://localhost:8000/OrderService/");

Na dzisiaj tyle, z następnych lekcji dowiemy się jak uruchomić usługę na serwerze IIS lub w aplikacji konsolowej.

Tagi: , , , ,

70-562: Exploring Common Server Controls

Artykuł pochodzi w serii przygotowań do egzaminu 70-562 ASP.NET.

W ASP .NET mamy do dyspozycji wiele gotowych kontrolek serwerowych. W dzisiejszym artykule opiszemy 5:

  • Label
  • TextBox
  • Button
  • CheckBox
  • RadioButton

Etykieta

Etykieta (ang. Label), wyświetla tekst w określonym miejscu na stronie. Etykiety używamy, kiedy w pewnym momencie chcemy zmienić jakiś tekst na stronie (który jest wyświetlany właśnie w postaci etykiety). Nie powinno używać się etykiety, kiedy wiemy że nasz tekst nie będzie się zmieniał.

Kontrolki do formatki możemy dodawać na dwa sposoby. Albo przeciągamy je z tool boxa albo piszemy w źródle ręcznie. Aby dodać ręcznie etykietę należy napisać:

   1: <asp:Label ID="Label1" runat="server" style="color: Blue" Text="Some Text"></asp:Label>

Teraz aby zmienić właściwość text musimy wejść do Code behind i napisać:

   1: Label1.Text="Inny tekst"

Pole tekstowe

Pole tekstowe (ang. text box) jest to kontrolka, dzięki której użytkownik może komunikować się z aplikacją za pomocą wpisywanych danych (np. może wpisać login, hasło itp). Pole tekstowe ma bardzo dużo fajnych właściwości. Np. zmieniając wartość właściwości TextMode z SingleLine na MultiLine, otrzymujemy duże pole tekstowe z możliwością wpisania kilku wierszy. Innym przykładem właściwości jest MaxLength, która służy do określania maksymalnej ilości znaków wpisanych do pola.

image

Przycisk

Kontrolka przycisku (ang. button control), wyświetla na stronie przycisk, który po naciśnięciu wysyła Post Back do serwera. Przycisk może być stworzony jako submit button lub command button. Submit button wykonuje prosty Post Back do serwera. Możemy go obsłużyć w zdarzeniu Click.
Przycisk użyty jako command button, jest jednym z zestawu przycisków, które pracują razem jako grupa takich jak toolbar. Aby zdefiniować ze nas przycisk ma być command, należy przypisać wartość do właściowści CommandName. W poniższym przykładzie zdefiniujemy sobie Command Button’y, które symulują odtwarzacz. Kiedy użytkownik kliknie na jeden z nich, zdarzenie Command wywoła się na serwerze. To zdarzenie przyjmuje instancje CommandEventArgs jako parametr:

image

   1: protected void Playback_Command(object sender, CommandEventArgs e)
   2: {
   3:  switch (e.CommandName)
   4:  {
   5:     case "Back":
   6:       Response.Write("back");
   7:       break;
   8:     case "Pause":
   9:       Response.Write("pause");
  10:       break;
  11:     case "Play":
  12:       Response.Write("play");
  13:       break;
  14:     case "Forward":
  15:       Response.Write("forward");
  16:       break;
  17:  }
  18: }

Pole wyboru

Pole wyboru (ang. check box), zwraca true jeśli zaznaczone i false jeśli odznaczone. Ciekawym zdarzeniem w check boxie jest CheckedChanged, który zostaje wywołany w momencie zmiany stanu pola. Wtedy należy też zmienić właściwość AutoPostBack na  true, żeby po zmianie stanu postback się wykonał.

Przycisk opcji

Podobny do check boxa jednak można go grupować. Dzięki grupowaniu można zaznaczyć tylko jedną opcję. Aby pogrupować opcję należy ustawić we właściwości GroupName wszędzie tę samą nazwę.

Tagi: , ,

70-503: Service Endpoint Basics

Ten artykuł pochodzi z serii przygotowań do egzaminu 70-503: Windows Communication Foundation.

Zakładamy, że stworzyliśmy już swój serwis. Mamy zdefiniowany kontrakt serwisu, oraz określone metody, które chcemy upublicznić. Teraz chcemy wystawić go (ang. expose) światu. Do tego służy właśnie punkt wejściowy (ang. endpoint), przez który klient może się z nim komunikować.

Endpoint pozwala nam określić gdzie nasz serwis będzie się znajdował (ang. address), w jaki sposób będziemy się z nim komunikować (ang. binding), oraz co serwis robi (ang. contract). Te trzy elementy to tzw. “ABCs of Endpoints”. ABC to trzy pierwsze litery nazw elementów.

Szybki start

W Visual Studio tworzymy przykładowy projekt typu WCF Service Library. Nazwijmy go przykładowo MyNamespace.Service.

image

Wśród wygenerowanych plików interesuje na App.config. Warto go otworzyć.

Omówienie kodu

Wygenerowany plik to XML, który zawiera deklaratywny opis serwisu. Oto wytłumaczenie kluczowych jego fragmentów.

   1: <host>
   2:   <baseAddresses>
   3:     <add baseAddress = "http://localhost:8732/Design_Time_Addresses/MyNamespace.Service/Service1/" />
   4:   </baseAddresses>
   5: </host>

Zdefiniowany został tu adres bazowy (ang. base address), czyli część wspólna dla wszystkich adresów serwisu. Wprowadźmy tu prostszy zapis - http://localhost:8732/Service1.

Uwaga: Zamiana adresu wymaga uruchomienia Visual Studio z uprawnieniami administratora. W przeciwnym wypadku otrzymamy błąd.

image

Please try changing the HTTP port to 8732 or running as Administrator.
System.ServiceModel.AddressAccessDeniedException: HTTP could not register URL http://+:8732/Service1/. Your process does not have access rights to this namespace (see http://go.microsoft.com/fwlink/?LinkId=70353 for details). ---> System.Net.HttpListenerException: Access is denied
   at System.Net.HttpListener.AddAllPrefixes()
   at System.Net.HttpListener.Start()
   at System.ServiceModel.Channels.SharedHttpTransportManager.OnOpen()
   --- End of inner exception stack trace ---

W ramach znacznika endpoint zostały określone kluczowe parametry każdego punktu wejściowego.

1: <endpoint address ="" binding="wsHttpBinding" contract="MyNamespace.Service.IService1">

  • AddressURL, który identyfikuje położenie (adres) serwisu; w naszym wypadku pusty, gdyż zostanie wzięty z adresu bazowego,
  • Binding – wiązanie, określenie jaki sposób możemy otrzymać dostęp do serwisu; w naszym wypadku wykorzystany zostanie SOAP przez HTTP lub HTTPS.
    Inne możliwości to chociażby basicHttpBinding, wsDualHttpBinding, webHttpBinding, wsFederationHttpBinding, netTcpBinding, netNamedPipeBinding, netMsmqBinding, … 
    To, który binding wybierzemy zależy od kilku czynników specyficznych dla sieci oraz środowiska pracy. Przykładowo jeśli serwis jest uruchomiony na pojedynczej maszynie netNamedPipeBinding będzie najbardziej wydajny. Jeśli komunikacja ma następować pomiędzy dwoma komputerami wydajne okazać się wykorzystanie netTcpBinding lub netPeerTcpBinding. Jeśli współdziałanie pomiędzy różnymi środowiskami jest kluczową sprawą, a komunikujemy się pomiędzy komputerami bez zainstalowanego WCF’a – warto rozważyć zastosowanie basicHttpBinding lub wsHttpBinding.
  • Contract – kontrakt, który określa operacje upublicznione przez serwis; zwykle jest to nazwa interfejsu poprzedzona przestrzenią nazw (ang. namespace) projektu.

Znaczników endpoint może być dodanych wiele!

Metadane

Pod adresem http://localhost:8732/Service1/?wsdl (zakładając, że adres bazowy został zmieniony) dostępny opis serwisu w postaci pliku WSDL. Klient może uzyskać dostęp do tych danych za pomocą zwykłego zapytania HTTP-GET. Odpowiedzialne są za to dwa wiersze kodu:

   1: <!-- Wiersz 32. -->
   2: <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
   3: <!-- Wiersz 40. -->
   4: <serviceMetadata httpGetEnabled="True"/>

Zdefiniowany został tu “metadata exchange endpoint”punkt wymiany “serwujący” metadane. Ten punkt dodaje końcówkę mex do adresu HTTP wykorzystywanego przez serwis. Wykorzystuje on także IMetadataExchange jako kontrakt, oraz mexHttpBinding jako binding.

Gdybyśmy zechcieli wyłączyć udostępnianie danych opisujących serwis należy usunąć wymieniony znacznik endpoint oraz przypisać wartość False atrybutowi httpGetEnabled.

Opis za pomocą kodu

Wygenerowany XML to deklaratywny opis serwisu. To samo można zrobić z poziomu kodu. W tym celu utwórzmy nowy projekt typu Console Application, nazwijmy go MyNamespace.Host. Dodajmy w nim referencję do projektu z serwisem, oraz do komponentu System.ServiceModel. Plik App.config usuwamy. Nowy projekt ustawiamy jako domyślny.

Nowa zawartość pliku Program.cs:

   1: using System;
   2: using System.ServiceModel;
   3: using System.ServiceModel.Description;
   4:  
   5: namespace MyNamespace.Host
   6: {
   7:     class Program
   8:     {
   9:         static void Main(string[] args)
  10:         {
  11:             Uri baseAddress = new Uri("http://localhost:8732/Service1/");
  12:             ServiceHost host = new ServiceHost(typeof(MyNamespace.Service.Service1), baseAddress);
  13:  
  14:             BasicHttpBinding basicBinding = new BasicHttpBinding();
  15:             host.AddServiceEndpoint(typeof(MyNamespace.Service.IService1), basicBinding, "");
  16:  
  17:             // mex
  18:             ServiceMetadataBehavior mb;
  19:             mb = host.Description.Behaviors.Find<ServiceMetadataBehavior>();
  20:             if (mb == null)
  21:             {
  22:                 mb = new ServiceMetadataBehavior();
  23:                 mb.HttpGetEnabled = true;
  24:                 host.Description.Behaviors.Add(mb);
  25:             }
  26:             host.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
  27:  
  28:             host.Open();
  29:             Console.WriteLine("Check WSDL at {0}?wsdl", baseAddress);
  30:             Console.Write("[CR] to break...");
  31:             Console.ReadLine();
  32:             host.Close();
  33:         }
  34:     }
  35: }

Uruchamiamy nasz program (F5) i cieszymy się :)

image

W następnej lekcji poznamy bardziej zaawansowane opcje związane z endpointami.

Tagi: , , , ,

70-562: Understanding and Using Server Controls

Artykuł pochodzi w serii przygotowań do egzaminu 70-562 ASP.NET.

ASP.NET udostępnia programiście dwa typu kontrolek serwerowych, które może wykorzystać w swoich aplikacjach webowych: HTML Controls oraz Web Controls. Ale zanim opiszę różnice między nimi, warto zapoznać się z cyklem życia strony ASP.NET.

View State

ASP.NET poszczególne żądania do strony traktuje jako osobne i nie przechowuje żadnych informacji pomiędzy kolejnymi odsłonami (są różne mechanizmy zapisywania stanu, które zostaną opisane w późniejszych artykułach). ASP.NET wykorzystuje mechanizm View State, który umożliwia zapisywanie statu kontrolek pomiędzy kolejnymi żądaniami do strony. View State nie jest przechowywany po stronie serwera, tylko jest przekazywany w treści strony. Dokładny opis mechanizmu View State zostanie opisany później, tutaj został tylko wspomniany, ponieważ jest ważny przy omawianiu cyklu życia strony ASP.NET.

Cykl życia strony ASP.NET

Gdy użytkownik wysyła żądanie wyświetlenia strony ASP.NET, serwer odbiera to żądanie i następnie uruchamia cały mechanizm ASP.NET, który składa się ogólnie z następujących kroków:

  1. Kompilacja strony (o ile jest potrzebna)
  2. Ustawienie Request oraz Response
  3. Inicjalizacja kontrolek na stronie
  4. W przypadku PostBack ustawianie właściwości kontrolek na podstawie View State
  5. Walidacja strony oraz kontrolek
  6. Wywoływania zdarzeń kontrolek (w przypadku PostBack)
  7. Zapisywanie stanu kontrolek w View State
  8. Zwolnienie zasobów i wysłanie gotowej strony do klienta

Aby móc kontrolować cykl życia strony, ASP.NET udostępnia kilkanaście zdarzeń, pod które programista może się podpiąć i wykonać swój kod w danym momencie cyklu życia (np. programowo dodać do strony kontrolki). Poniższa tabela przedstawia najpopularniejsze zdarzenia:

Zdarzenie Opis
PreInit Jest pierwszy zdarzeniem, pod jakie może podpiąć się programista. W nim może ustawić programowo takie rzeczy jak master page, theme lub dodać kontrolki do strony.
Init Wywoływane zaraz po inicjalizacji każdej kontrolki. Zdarzenie te może użyć do zmiany zainicjalizowanych właściwości kontrolki.
InitComplete Wywoływane raz po inicjalizacji wszystkich kontrolek oraz strony.
PreLoad Zdarzenie wywoływane po inicjalizacji strony ale przed odczytaniem View State w przypadku PostBacku.
Load Wywoływane po inicjalizacji strony oraz odczytaniu View State. Zdarzenie Load jest wywoływane pierw dla strony, później dla poszczególnych kontrolek. Warto o tym pamiętać tworząc swoją własną kontrolkę.
Control (PostBack) events Zdarzenia poszczególnych kontrolek np. zdarzenie Click dla przycisku.
LoadComplete W tym momencie każda z kontrolek jest załadowana i obsłużone są ich wszystkie zdarzenia.
PreRender Zdarzenie wywoływane przed zapisaniem stanu kontrolek w View State
SaveStateComplete Wywoływane po zapisaniu View State, wszelkie zmiany kontrolek w tym momencie są już ignorowane.
UnLoad Zdarzenie użyteczne do zwolnienia różnych zasobów wykorzystywanych przez stronę.

Znajomość cyklu życia strony oraz poszczególnych zdarzeń jest ważna, aby móc w pełni zapanować na procesem tworzenie strony.

HTML Server Controls

Kontrolki serwerowe HTML powinny być używane w jednym z poniższych przypadków:

  • Podczas migracji strony z ASP do ASP.NET
  • Kontrolka wymaga zwykłego Java Script podpiętego pod zdarzenia kontrolki
  • Gdy strona zawiera dużą ilość Java Scriptu

Kontrolki serwerowe HTML są bardzo podobne do zwykłych kontrolek HTML, z tą różnicą, że mają dodany atrybut runat=”server” oraz muszą mieć ustawiony atrybut Id, który jest wykorzystywany do dostępu do kontrolki z poziomu kodu behind.

Web Server Controls

Kontrolki serwerowe, są bardziej rozbudowane niż kontrolki serwerowe HTML oraz dają programiście większe możliwości budowania bogatych aplikacji webowych. Często jedna kontrolka serwerowa jest zamieniana później w docelowym HTMLu na kilkanaście znaczników HTML oraz dołączany może być do strony kod Java Script (w przeciwieństwie do kontrolek serwerowych HTML, gdzie jedna kontrolka jest zamieniana na jeden znacznik HTMLa). Wszystkie kontrolki serwerowe dziedziczą po klasie WebControl, która udostępnia sporą ilość atrybutów, którymi programista może później zmieniać zachowanie oraz wygląd kontrolek.

W następnych dwóch artykułach zostaną opisane najbardziej typowe kontrolki serwerowe.

Tagi: , , , ,

70-562: Working with Web configuration files

Artykuł pochodzi w serii przygotowań do egzaminu 70-562 ASP.NET.

Możemy użyć plików konfiguracyjnych do określenie ustawień naszej strony. Ustawienia te są przechowywane w odrębnym pliku, oddzielonym od kodu aplikacji i zapisane w XML. Zazwyczaj storna internetowa zawiera jeden plik web.config który jest w głównym katalogu aplikacji. Jednak nic nie stoi na przeszkodzie aby umieścić ich wiele i będą zarządzały ustawieniami aplikacji na różnych poziomach.

Zrozumieć hierarchie pliku konfiguracyjnego

Plik konfiguracyjny czyli tak naprawdę plik XML zawiera np. takie elementy jak informacje dotyczące bezpieczeństwa, connection string itd. Sama strona może być w danym miejscu skonfigurowana przy pomocy ustawień zawartych w dwóch “web configach” dlatego warto znać i zrozumieć jego budowę.

Sam plik web.config jest zbudowany na zasadzie hierarchii. W praktyce oznacza to, że jest gdzieś plik konfiguracyjny, ogólny dla danej maszyny. Machine.config bo tak się nazywa, należy szukać w katalogu: %SystemRoot\Microsoft.NET\Framework\<versionNumber>\CONFIG\

Plik Machine.config zawiera ustawienia dla wszystkich typów aplikacji .NET tj. np. Windows, Konsola, ClassLibrary czy storna www. Ustawienia te są globalne dla komputera. Niektóre ustawienia niższe w hierarchii możemy przysłonić w naszym web.config natomiast reszta ustawień ma charakter globalny stąd są chronione.

Taką przykładową hierarchie plików konfiguracyjnych przedstawia poniższy rysunek.

f1-12

Widzimy tutaj, że mamy pliki globalne z ustawieniami ogólnymi dla danej maszyny oraz mogą być pliki konfiguracyjne w poszczególnych katalogach aplikacji które mają w sobie bardziej szczegółowe dane.

Przetwarzanie pliku konfiguracyjnego

  1. Pobierane są ustawienia machine.config
  2. Ustawienia z głównego web.config są dodawane do pamięci podręcznej i przysłonięte jeżeli występują jakieś konflikty z machine.config
  3. Jeśli w naszej aplikacji istnieje główny web.config, również jest on dodawany do pamięci podręcznej i zostają ewentualnie przysłonięte ustawienia
  4. Jeżeli istnieje pkt. 3  to wczytywane są ustawienia i zastępowane istniejące.
  5. Jeśli w naszej aplikacji istnieją katalogi i te katalogi mają własne pliki konfiguracyjne to najpierw wykonują się kroki 1-4 a na koniec są wczytywane web.config z naszych podkatalogów i przysłanianie ewentualne ustawienia.

To tyle na dzisiaj, kolejny artykuł już w poniedziałek. Przed nami weekend, czas odpoczynku…mam nadzieje :D

Tagi: , , ,

70-503: Defining Structural Contracts

Ten artykuł pochodzi z serii przygotowań do egzaminu 70-503: Windows Communication Foundation.

Z poprzedniej lekcji wiemy jak utworzyć usługę i jak zdefiniować jej część “behawioralną”, czyli poszczególne metody serwisu. Dzisiaj dowiemy się jak zdefiniować własne typy danych i przekazywać je przez serwis.

Kontrakt danych

Kontrakt danych definiuje nam format, strukturę i sposób serializacji przesyłanych danych. Rozbudujmy nasz kalkulator o możliwość wykonywania dowolnych działań dwuargumentowych. Najpierw w pliku ICalculator.cs zdefiniujemy rodzaje działań, później obiekt zawierający wszystkie informacje potrzebne do wykonania działania.

   1: [DataContract(Namespace = "http://schemas.eastgroup.pl/2010/02/26/calculator/")]
   2: public enum CalculatorOperationType : int
   3: {
   4:     [EnumMember]
   5:     Add,
   6:     [EnumMember]
   7:     Substract,
   8:     [EnumMember]
   9:     Multiply,
  10:     [EnumMember]
  11:     Divide,
  12: }
  13:  
  14: [DataContract(Namespace = "http://schemas.eastgroup.pl/2010/02/26/calculator/")]
  15: public class CalculatorOperation
  16: {
  17:     [DataMember(IsRequired = true)] 
  18:     public CalculatorOperationType operation;
  19:  
  20:     [DataMember(IsRequired = true)] 
  21:     public double a;
  22:  
  23:     [DataMember(IsRequired = true)] 
  24:     public double b;
  25: }

DataContractAttribute jest zdefiniowany w System.Runtime.Serialization i może być użyty tylko do wyliczeń (enum), struktur i klas. Nie jest dziedziczony, więc każda klasa potomna, jeśli ma być typem danych przekazywanym przez serwis, powinna zawierać ten atrybut. Ma tylko dwa parametry:

  • Name – nazwa typu, np. jeśli chcemy nadać mu inną nazwę niż jest w definicji,
  • Namespace – przestrzeń nazw dla typu.

DataMemberAttribute jest też zdefiniowany w System.Runtime.Serialization i służy do oznaczenia pól lub właściwości, które mają być serializowane. Jako parametry możemy użyć:

  • Name – nazwa typu,
  • IsRequired – czy wartość dla pola jest wymagana czy nie,
  • Order – kolejność pól, domyślnie pola będą pokazane w kolejności alfabetycznej,
  • EmitDefaultValue – czy domyślne wartości mają być serializowane, jeśli true – wszystkie pola będą serializowane, jeśli false – serializowane będą tylko te pola, których wartość jest inna niż domyślna.

Ustawienie EmitDefaultValue na false może powodować problemy jeśli IsRequired jest ustawione na true, ponieważ serializer przekaże wartość domyślną, chociaż właściwie powinien w tym miejscu wystąpić wyjątek.

Jeszcze kilka dodatkowych uwag. Jeśli po klasie opatrzonej atrybutem DataContract coś dziedziczy, to pola i właściwości z klasy bazowej zawsze będą przed polami i właściwościami z klasy potomnej. Kolejne będą pola i właściwości klasy potomnej które nie posiadają parametru Order, a następnie te posiadające parametr Order.

EnumMemberAttribute – jak nazwa wskazuje, służy do deklaracji typów wyliczeniowych, jedynym parametrem jest:

  • Value – wartość, która ma być serializowana.

Teraz do kalkulatora możemy przekazywać paczkę z informacjami potrzebnymi do obliczeń:

   1: [ServiceContract(Name = "Calculator",
   2:     Namespace = "http://schemas.eastgroup.pl/2010/02/26/calculator/")]
   3: public interface ICalculator
   4: {
   5:     //(...)
   6:  
   7:     [OperationContract]
   8:     double BinaryOperation(CalculatorOperation calculatorOperation);
   9: }

Jeśli ktoś z Was używał XmlSerializer, to pewnie wie, że przy użyciu tej klasy serializowane są wszystkie pola oprócz tych oznaczonych NonSerializedAttribute. W przypadku DataContractSerializer’a jest odwrotnie: serializowane będą tylko pola opatrzone odpowiednim atrybutem (a pominięte te nieoznaczone żadnym atrybutem). Poniższy kod oddaje to o czym właśnie napisałem (pola PhoneNumber i EmailAddress będą serializowane, a HomeAddress nie):

   1: [Serializable()]
   2: public class ContactInfo
   3: {
   4:     public string PhoneNumber;
   5:  
   6:     public string EmailAddress;
   7:  
   8:     [NonSerialized()]
   9:     public string HomeAddress;
  10: }
  11:  
  12: [DataContract()]
  13: public class ContactInfo
  14: {
  15:     [DataMember()]
  16:     public string PhoneNumber;
  17:  
  18:     [DataMember()]
  19:     public string EmailAddress;
  20:     
  21:     public string HomeAddress;
  22: }

Kolekcje

W WCF’ie możemy używać dowolnych kolekcji (czyli wszystkiego implementującego IEnumerable lub IEnumerable<T>). Nasze dane będą reprezentowane jako tablica, informacje specyficzne dla danej kolekcji zostaną utracone. Np.:

   1: [ServiceContract()]
   2: interface ITaskManager
   3: {
   4:     [OperationContract()]
   5:     List<Task> GetTasksByAssignedName( string name);
   6: }

 

Po drugiej stronie serwisu będzie widoczny jako:

   1: [ServiceContract()]
   2: interface ITaskManager
   3: {
   4:     [OperationContract()]
   5:     Task[] GetTasksByAssignedName( string name);
   6: }

Będzie to obsłużone automatycznie, ale tylko wtedy gdy jest to konkretny typ kolekcji (nie interfejs) i jest on serializowalny (SerializableAttribute)

Co jeśli koniecznie chcemy by przekazywany obiekt pozostał kolekcją? Musimy użyć atrybutu CollectionDataContractAttribute:

   1: [CollectionDataContract(Name = "MyCollectionOf{0}")]
   2: public class MyCollection<T> : IEnumerable<T>
   3: {
   4:     public void Add(T item) { // Etc...
   5:     }
   6:  
   7:     IEnumerator<T> IEnumerable<T>.GetEnumerator() { // Etc...
   8:     }
   9:  
  10:     public IEnumerator GetEnumerator() { // Etc...
  11:     }
  12:  
  13:     // Etc...
  14: }
  15:  
  16:  
  17: [ServiceContract()]
  18: interface ITaskManager
  19: {
  20:     [OperationContract()]
  21:     MyCollection<Task> GetTasksByAssignedName(string name);
  22: }

Nasza kolekcja po drugiej stronie będzie listą:

   1: [CollectionDataContract]
   2: public class MyCollectionOfTask : List<Task>
   3: {}

Jeśli chodzi o typy danych to pozostał jeszcze jeden atrybut do omówienia: KnownTypeAttribute. Używamy go gdy chcemy skorzystać z polimorfizmu. Aby nie utracić informacji z klasy dziedziczącej musimy poinformować o tym, że po typie Task dziedziczy LoanApprovalTask i może on być przekazywany przez nasz serwis.

   1: [DataContract()]
   2: [KnownType(typeof(LoanApprovalTask))]
   3: class Task
   4: {
   5: }
   6:  
   7: [DataContract()]
   8: class LoanApprovalTask : Task
   9: {
  10: }

Kontrakt wiadomości

 

 

 

Mamy już opisane jakimi danymi się posługujemy. Teraz możemy przejść krok dalej i przejąć kontrolę nad całością przekazu. Jeśli chcemy mieć ustaloną przez nas strukturę wiadomości SOAP lub własne nagłówki (np. do przekazywania numeru licencji) to atrybuty MessageContractAttribute, MessageHeaderAttribute i MessageBodyMemberAttribute będą nam do tego potrzebne.

MessageContractAttribute jest używany do oznaczania klas definiujących strukturę komunikatu. Ma kilka parametrów:

  • IsWrapped – jeśli true to wszystko to co miało się znaleźć w ciele komunikatu będzie opakowane w element o nazwie klasy kontraktu lub nazwie podanej w parametrze WrapperName
  • ProtectionLevel – rodzaj zabezpieczeń (podpis, szyfrowanie, …), będzie omówione w kolejnych lekcjach
  • WrapperName – nazwa elementu opakowującego zawartość ciała komunikatu
  • WrapperNamespacenamespace dla elementu opakowującego ciało komunikatu

MessageHeaderAttribute służy do oznaczenia elementów, które mają wejść w skład nagłówka. Ma 6 parametrów:

  • Name – nazwa serializowanego elementu nagłówka
  • Namespace – przestrzeń nazw elementu
  • ProtectionLevel – rodzaj zabezpieczeń
  • Actor – URI wskazujące aktora – cel nagłówka, domyślnie usługa odbierająca
  • MustUnderstand – czy aktor musi zrozumieć nagłówek (jeśli nie zrozumie a ten parametr jest ustawiony na true to aktor musi zgłosić błąd)
  • Relay – czy nagłówek powinien być przekazany do kolejnego odbiorcy jeśli wiadomość nie została przetworzona przez aktora

A atrybutem MessageBodyMemberAttribute oznaczamy elementy, które mają się znaleźć w ciele wiadomości. Parametry:

  • Name – nazwa serializowanego elementu ciała wiadomości
  • Namespace – przestrzeń nazw elementu
  • ProtectionLevel – rodzaj zabezpieczeń
  • Order – kolejność elementów (podobnie jak przy DataMemberAttribute)

Poniżej przykład kodu (myślę że nie wymaga wyjaśniania):

   1: [DataContract()]
   2: public class ContactInfo
   3: {
   4:     [DataMember()]
   5:     public string PhoneNumber;
   6:  
   7:     [DataMember()]
   8:     public string EmailAddress;
   9: }
  10:  
  11: [MessageContract(IsWrapped = false)]
  12: public class ContactInfoRequestMessage
  13: {
  14:     [MessageHeader()]
  15:     public string LicenseKey;
  16: }
  17:  
  18: [MessageContract(IsWrapped = false)]
  19: public class ContactInfoResponseMessage
  20: {
  21:     [MessageBodyMember()]
  22:     public ContactInfo ProviderContactInfo;
  23: }
  24:  
  25: [ServiceContract()]
  26: public interface ISomeService
  27: {
  28:     [OperationContract()]
  29:     [FaultContract(typeof(string))]
  30:     ContactInfoResponseMessage GetProviderContactInfo(
  31:         ContactInfoRequestMessage reqMsg);
  32: }
  33:  
  34: public class SomeService : ISomeService
  35: {
  36:     public ContactInfoResponseMessage GetProviderContactInfo(
  37:         ContactInfoRequestMessage reqMsg)
  38:     {
  39:         if (reqMsg.LicenseKey != ValidLicenseKey)
  40:         {
  41:             const string msg = "Invalid license key.";
  42:             throw new FaultException<string>(msg);
  43:         }
  44:         
  45:         ContactInfoResponseMessage respMsg =
  46:             new ContactInfoResponseMessage();
  47:         respMsg.ProviderContactInfo = new ContactInfo();
  48:         respMsg.ProviderContactInfo.EmailAddress = "sam@fabrikam.com";
  49:         respMsg.ProviderContactInfo.PhoneNumber = "123-456-7890";
  50:         return respMsg;
  51:     }
  52:     
  53:     private const string ValidLicenseKey = "abc-1234-alpha";
  54: }

Wersjonowanie

Jedną z głównych zalet udostępniania usług w postaci serwisów jest odseparowanie klienta od naszej usługi. Ale co zrobimy gdy będzie coś zmienić…

Przy konieczności dodania czegoś do wiadomości DataContractSerializer jest na tyle uprzejmy że jeśli czegoś się nie spodziewa w odebranej paczce to po prostu to ignoruje.

Przy braku oczekiwanego elementu jeśli nie był on wymagany (IsRequired = false) serializer przyjmie wartość null dla typów referencyjnych lub wartości zerowe dla pozostałych. Jeśli element był wymagany, nastąpi wyjątek.

Co w przypadku gdy serwis został rozszerzony a użytkownik ma jeszcze starego klienta i musi przynajmniej zapamiętać dane które dostał z serwisu? A jeśli te dane ma odesłać bo to np. dane autoryzacyjne? Tutaj przychodzi z pomocą IExtensibleDataObject, który umożliwi nam obsługę “nieznanych” danych.

   1: [DataContract(Namespace =
   2:     "http://schemas.fabrikam.com/2008/04/tasks/")]
   3: public class Task : IExtensibleDataObject
   4: {
   5:     [DataMember(IsRequired = true, Order = 1)]
   6:     public string Description;
   7:     // Etc...
   8:  
   9:     public ExtensionDataObject ExtensionData
  10:     {
  11:         get
  12:         {
  13:             return _extensionData;
  14:         }
  15:  
  16:         set
  17:         {
  18:             _extensionData = value;
  19:         }
  20:     }
  21:  
  22:     private ExtensionDataObject _extensionData;
  23: }

W obiekcie _extensionData będą przechowywane wszystkie dane, które klient nie potrafił obsłużyć. Możemy je sobie gdzieś zachować i przy aktualizacji aplikacji z nich skorzystać.

Kontrola serializacji

 

Na koniec jeszcze krótko o samej serializacji. Do dyspozycji mamy 2 serializery: DataContractSerializer i XmlSerializer i 3 enkodery: enkoder tekstowy dla standardowego XML’a, enkoder MTOM oraz binarny WCF-to-WCF (będą opisane w kolejnych lekcjach).

WCF udostępnia dwa atrybuty mówiące o tym który serializer ma być użyty: XmlSerializerFormatAttribute i DataContractFormatAttribute. Oba mają podobne parametry:

  • StyleRpc (remote procedure call) lub Document – styl formatowania SOAP,
  • UseLiteral lub Encoded – czy wstawiać dane “dosłownie” czy jako zakodowane.
   1: [ServiceContract()]
   2: [XmlSerializerFormat(Style=OperationFormatStyle.Rpc,
   3:     Use=OperationFormatUse.Encoded)]
   4: interface ISomeLegacyService
   5: {
   6:     [OperationContract()]
   7:     string SomeOp1( string name);
   8: }
   9:  
  10: [ServiceContract()]
  11: [DataContractFormat(Style=OperationFormatStyle.Rpc)]
  12: interface ISomeRpcService2
  13: {
  14:     [OperationContract()]
  15:     string SomeOp2( string name);
  16: }

Na dzisiaj tyle, w kolejnym artykule pokażemy jak udostępnić nasz serwis światu.

Tagi: , , , , , ,

70-562: Creating a Web Site and Adding New Web Pages

Artykuł pochodzi w serii przygotowań do egzaminu 70-562 ASP.NET.

Witajcie w moim pierwszym artykule, w nowej serii wpisów. Dziś troszkę podstaw. Powiemy o tworzeniu nowego projektu w Visual Studio, dodawaniu nowych stron itp.

Tworzenie strony internetowej

Visual Studio 2008 pozwala nam stworzyć projekt typu Web Site, z rozróżnieniem tego w jaki sposób chcemy mieć dostęp do zawartości strony. Możemy stworzyć projekt Web podłączony do systemu plików na naszym komputerze, na serwerze IIS lub FTP. Wybór odpowiedniej opcji zależy od tego w jaki sposób chcemy uruchomić, zarządzać i wdrażać nasz projekt WWW. Poniżej krótki opis tych opcji:

File System – pliki naszego projektu Web trzymamy w wybranym przez nas katalogu. Web Site używa wtedy lekkiego serwera ASP.NET, który dostarczany jest razem z Visual Studio. Ta opcja dobra jest wtedy, kiedy chcemy uruchomić i debugować nasz projekt Web lokalnie.

FTP – ta opcja przydatna jest wtedy, gdy chcemy łączyć się do naszej strony za pośrednictwem FTP w celu zarządzania plikami, na zdalnym serwerze.

HTTP – używany, kiedy pracujemy ze stroną umieszoną wewnątrz IIS (lokalnie bądź na zdalnym serwerze).

Tworzenie projektu Web Site

Poniżej opis tworzenia nowego projektu typu Web Site w Visual Studio:

1. W Visual Studio wchodzimy w File->New->Website. Spowoduje to otwarcie okienka jak na obrazku poniżej:

image

2. Wybieramy typ Web Site, ścieżkę w której chcemy zapisać oraz zostawiamy domyślny język.
3. Możemy wybrać również wersję frameworka, w której będziemy pisali naszą aplikację. Do wyboru mamy wersję 2.0, 3.0 oraz 3.5.
4. Naciskamy ok i cieszymy się nowo powstałym projektem :)

Jak pisałem wcześniej, podczas tworzenia nowego projektu możemy wybrać 3 opcję, o których pisałem wyżej: File System, FTP oraz HTTP. W zależności od tego, którą opcję wybierzemy, możemy być poproszeni o dodatkowe informację np. login i hasło do serwera.

Tworzenie projektu Web Application

Poza projektem typu Web Site, Visual studio dostarcza nam jeszcze jeden typ projektu, który stworzy nam stronę internetową. Ten typ to ASP .NET Web Application. Projekt tego typu jest użyteczny w momencie, gdy chcemy dodać aplikację do już istniejącego solution, które zawiera już inne projekty, bądź wtedy gdy chcemy nasz projekt traktować mniej jak typowa strona a bardziej jak standardowy projekt visual studio.

Plik solution w projekcie typu Web Site

W momencie utworzenia projektu Web site, Visual Studio tworzy “solution file” (.sln) oraz ukryty plik “solution user options” (.suo). Plik solution jest tekstowym plikiem, który zawiera informacje o:

  • liście projektów, które będą załadowane w Visual Studio i stworzą całe Solution
  • wersji frameworka, w której solution jest stworzone
  • domyślnym języku dla solution
  • liście zależnych projektów
  • kontroli źródła takiej jak Microsoft Visual SourceSafe
  • liście dodatków, które są dostepne

Plik .suo jest plikiem binarnym, który zawiera różne ustawienia użytkownika takich jak:

  • zadania użytkownika
  • Break pointy oraz ustawienia “watch’y”
  • położenie okien Visual Studio

     

Tworzenie stron ASPX

Po tym, jak utworzyliśmy nasz projekt, wypadałoby dodać do niego jakąś podstronę :) Aby to osiągnąć należy:

1.Kliknąć prawym przyciskiem na naszym projekcie i wybrać Add->New Item
2.Jako rodzaj nowego elementu wybieramy Web Form oraz domyślny język

image

 

 

Budowa strony ASPX

Strona w ASP.NET zawiera interfejs użytkownika, kod, który wykonuje się na serwerze oraz dyrektywy do łączenia interfejsu z kodem. Standardowa strona ASP.NET posiada rozszerzenie .ASPX i zawiera 3 sekcje:

  • Dyrektywy strony – ta sekcja używana jest do ustawiania środowiska oraz określa jak strona powinna być przetwarzana.
  • Kod – zawiera kod do obsługiwania zdarzeń, które wykonywane są na serwerze. Domyślnie Visual Studio tworzy oddzielny plik, który trzyma kod. Plik nazywany “Code-behind” jest dołączany do pliku .ASPX. Jego rozszerzenie do .cs bądź .vb.
  • Wygląd strony – pisany w htmlu. Zawiera tagi html, css, kontrolki, text itp.

Kompilacja strony

Większość aplikacji Web nie jest prekompilowana. Zamiast tego, strona i kod są kopiowane na serwer, i tam, przez niego, dynamicznie kompilowane.

Tagi: , ,