Kontrakty w WCF
Po co są kontrakty?
Podejście kontraktowe w WCF nie jest podejściem nowym. Wywodzi się ono z dziedziny inżynierii oprogramowania i było znane znacznie wcześniej niż sam WCF. Polegało ono na tym, że zanim programista zaczął pisać implementację właściwą, projektant definiował pewien interfejs, zestaw metod, które dany obiekt lub komponent będzie udostępniał światu zewnętrznemu. Definiował też struktury danych, które zwracane były z tychże metod. Co takie podejście dawało? Przede wszystkim to, że dwie niezależne grupy mając definicję takiego interfejsu mogły pracować rzeczywiście niezależnie. Należało przestrzegać tylko jednego warunku - interfejs nie mógł zostać naruszony. Cała wewnętrzna implementacja leżała wyłacznie w gestii piszącego dany moduł. Dla drugiej grupy lub osoby liczył się tylko interfejs i jego dokumentacja. Nawet późniejsze zmiany w implementacji nie mają wpływu na napisany wcześniej kod, pod warunkiem, że sam interfejs, czyli nasz tytułowy kontrakt, nie uległ zmianie.
Należy pamiętać też o tym, że WCF projektowany jest z myślą o wymianie informacji w całym Internecie. Nie można zatem zakładać, że odbiorca będzie miał taki sam system, taki sam komputer. Nie można nawet zakładać, że odbiorca usług WCF będzie także korzystał z WCF.
Problemy i wymagania, które zostały właśnie poruszone, nie są jedynymi, które musiały być wzięte pod uwagę podczas projektowania Windows Communication Foundation. Wydaje się, że definiowanie kontraktów, czyli czegoś na kształt interfejsów, jest czynnością bardzo skomplikowaną. Otóż spieszę z wyjaśnieniami: wcale nie jest.
Definiowanie struktur danych
Zanim zajmiemy się metodami, zatrzymamy się na chwilę nad danymi, które do tych metod trafiają lub są z niej zwracane. Tu pojawia się pierwsza dobra wiadomość: wszystkie typy proste, czyli int, decimal, string, DateTime oraz część mniej prostych, wbudowanych, na przykład tablice, nie wymagają żadnej ingerencji. WCF sam zadba o ich serializację i poprawną transmisję. Problem zaczyna się dopiero wtedy, gdy zamierzamy przesyłać struktury własne, czyli klasy i struktury, czy chociażby pola wyliczeniowe. Wtedy musimy wykonać kilka dodatkowych czynności.
Kontrakty na danych
Do definiowania kontraktów na strukturach danych służy klasa DateContract, oraz mocno z nią powiązana klasa DataMember, która służy do oznaczania właściwości, które będą serializowane przez WCF. Przyjrzyjmy się zatem przykładowej definicji własnej struktury danych zaprezentowanej poniżej:
public class Customer
{
[DataMember]
public int ID { get; set; }
[DataMember]
public string LastName { get; set; }
[DataMember]
public string FirstName { get; set; }
[DataMember]
public int Age { get; set; }
}
Atrybut DataContract stanowi dla kompilatora cenną informację. Definiuje on typ, który może być serializowany i deserializowany przez strumień WCF. Jest to nie tyle możliwość, co wręcz przymus. Każdy bowiem typ, który zamierzamy przesłać za pomocą WCF musi dać się serializować. Serializacja i deserializacja wykonywana jest niejawnie przez środowisko WCF i .NET. Każda natomiast składowa, czyli w tym przypadku właściwość także musi być oznaczona. Jeżeli pole składowe klasy lub struktury nie zostanie oznaczone, nie zostanie poddane serializacji, a co za tym idzie, nie zostanie przesłane. Co się z takim polem dzieje i kiedy taka możliwość może nam się przydać napiszę później.
Należy też wspomnieć o pewnego rodzaju przechodniości atrybutu DataContract. Jeżeli klasa A oznaczona jest atrybutem DataContract, to może być z powodzeniem wykorzystywana jako typ w klasie B, podobnie jak inne typy proste. Podobnie, jeżeli klasa B oznaczona jest atrybutem DataContract, to może być ona z powodzeniem wykorzystywana w klasie C (Klasa C zawiera wtedy klasę B i pośrednio klasę A). Serializacja takiego łańcucha nie będzie stwarzała dla WCF żadnego problemu.
Kontrakty na usłudze i metodach
W tej kategorii zajmiemy się dwoma rodzajami kontraktów. Pierwszy służy wyłącznie do definiowania usługi, czyli prościej mówiąc do oznaczenia interfejsu, który zawiera deklaracje udostępnianych metod. Drugim rodzajem kontraktu jest kontrakt stosowany na metodach. Za ich pomocą definiujemy to, co metoda przyjmuje w postaci parametrów, oraz to, co metoda zwraca. Do oznaczania usług stosuje się atrubut ServiceContract (klasa ServiceContractAttribute), natomiast do oznaczania metod stosuje się artybut OperationContract (klasa OperationContractAttribute).
Przyjrzyjmy się zatem przykładowej definicji kontraktu na usługę i na metody w niej umieszczone. Przykładowy kod znajduje się poniżej:
public interface ICustomers
{
[OperationContract]
List<int> GetIDsOfCustomers();
[OperationContract]
Customer GetCustomerById(int id);
[OperationContract]
List<Customer> GetCustomersByName(string name);
}
Jest to najprostsza wersja definiowania kontraktu na usłudze. Po co właściwie należy wykonywać takie operacje? Przede wszystkim po to, aby silnik WCF mógł wygenerować na podstawie tych atrybutów metadane wykorzystywane przez aplikacje klienckie. Wszystkie metody, które mają być udostępnione, powinny być oznaczone atrybutem OperationContract.
Warto zwrócić uwagę na to, że w kontrakcie użyto listy. WCF radzi sobie z listami bez żadnego problemu. Nie trzeba podejmować żadnego dodatkowego wysiłku, aby przesłać kilka elementów danego typu.
Zawarte tutaj informacje są wyjątkowo ważne. Definiowanie kontraktów jest podstawą projektowania wszystkich usług WCF. Przed przystąpieniem do dalszych etapów nauki warto poćwiczyć sobie z różnymi typami, także tymi złożonymi, z listami i tablicami. Wszystkie wspomniane atrybuty mogą przyjmować różne parametry. Napiszę o nich w jednym z kolejnych artykułów.
Kategoria:C#Windows Communication Foundation
Komentarze: