Spis treści:

Kategoria:C#Windows Communication Foundation


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.

Inną kwestią wartą poruszenia jest tzw. niejawne zmuszanie do zastanowienia się nad problemem. Wiedząc, że kontrakt nie może ulec zmianie, należało się nad nim poważniej zastanowić i zaprojektować go właściwie. Znacznie łatwiej też ocenić sam interfejs, niż pełną klasę wypełnioną z pewnością znakomitej jakości kodem.

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:

[DataContract]
public class Customer
{
    [DataMember]
    public int ID { getset; }
    [DataMember]
    public string LastName { getset; }
    [DataMember]
    public string FirstName { getset; }
    [DataMember]
    public int Age { getset; }
}
Jeżeli podczas kompilacji pojawia się błąd związany z typem DataContract i/lub DataMember, a właściwie z typem DataContractAttribute i/lub DataMemberAttribute, należy upewnić się, że do referencji projektu dodana jest biblioteka System.Runtime.Serialization.dll.

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.

Atrybut DataMember może być umieszczany także na zwykłych polach. W przykładzie pokazane są właściwości automatyczne, ale nic nie stoi na przeszkodzie, aby były to właśnie zwykłe pola publiczne klasy.

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.

Wspomniałem wcześniej o możliwości wyłaczenia z serializacji jednego z pól. Przypuśćmy, że klasa A ma w sobie klasę B, natomiast jedno z pól klasy B zawiera referencję wskazującą na tę samą klasę A. Powstaje pętla, zwana czasem refencją cykliczną, lub jeszcze prościej cyklem. Domyślny serializator nie jest w stanie poradzić sobie z takimi zależnościami i zgłasza błąd. Jednym z rozwiązań jest wyłączenie któregoś pola z procesu serializacji i przywrócenie go w kodzie po odebraniu komunikatu.

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:

[ServiceContract]
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.

Jeżeli podczas pracy z WCF pojawi się komunikat o zbliżonej treści: Komunikat o parametrze Action {adres} nie może zostać przetworzony w punkcie odbioru z powodu niezgodności parametru ContractFilter w EndpointDispatcher. Może to być spowodowane niezgodnością kontraktu (niezgodność czynności między nadawcą i odbiorcą) lub niezgodnością powiązania/zabezpieczeń między nadawcą i odbiorcą. Sprawdź, czy nadawca i odbiorca mają te same kontrakty i powiązania (w tym wymagania zabezpieczeń, np.: Message, Transport, None).. lub The message with Action {adres} cannot be processed at the receiver, due to a ContractFilter mismatch at the EndpointDispatcher. This may be because of either a contract mismatch (mismatched Actions between sender and receiver) or a binding/security mismatch between the sender and the receiver. Check that sender and receiver have the same contract and the same binding (including security requirements, e.g. Message, Transport, None). to może to oznaczać właśnie brak atrybutu OperationContract.
Drugą możliwością jest pojawienie się komunikatu następującej treści: Element ContractDescription {interfejs} ma zero operacji; kontrakt musi zawierać przynajmniej jedną operację, ewentualnie ContractDescription {interfejs} has zero operations; a contract must have at least one operation.. Oznacza to, że żadna metoda w interfejsie nie jest oznaczona 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

, 2013-12-20

Komentarze:

Marcin  (2015-07-07 07:40:38)
Super! Dziękuję za artykuł jest bardzo pomocny , dobrze i przejrzyście opisany. Pozdrawiam
Dodaj komentarz
Wyślij
Ostatnie komentarze
Dzieki za rozjasnienie zagadnienia upsert. wlasnie sie ucze programowania :).
Co się stanie gdy spróbuję wyszukać:
SELECT * FROM NV_Airport WHERE Code='SVO'
SELECT * FROM V_Airport WHERE Code=N'SVO'
(odwrotnie są te N-ki)
Będzie konwersja czy nie znajdzie żadnego rekordu?