WCF i konfiguracja połączenia TCP/IP
Dlaczego TCP/IP
Jak wspominałem w którymś z poprzednich wpisów, WCF może być skonfigurowany do pracy z różnymi protokołami. Jednym z najpowszechniejszych protokołów jest właśnie protokół TCP/IP. Dlaczego mielibyśmy zmieniać HTTP na TCP/IP? Przede wszystkim dlatego, że jest szybszy. Odbywa się to jednak pewnym kosztem. HTTP to protokół tekstowy (zbudowany zresztą na bazie protokoł€ TCP/IP). HTTP jest protokołem wyższego poziomu, warstwy aplikacji, jest bardziej zrozumiały przez inne aplikacje. TCP/IP jest protokołem warstwy transportowej. Dane w TCP/IP nie są przesyłane w postaci tekstu, lecz w postaci binarnej.
Zadaniem tego artykułu nie jest wyjaśnianie różnic między protokołami - tym zajmę się być może w innym, oddzielnym artykule. Przystąpmy zatem do części właściwej, czyli stworzenia prostej konfiguracji klient-serwer w WCF działającej w oparciu o protokół TCP/IP.
Od czego zacząć
Przypomnijmy sobie o pewnej zasadzie: pracę z WCF należy rozpocząć od zdefiniowania kontraktów, czyli intefrejsów. Sposób tworzenia i oznaczania kontraktów opisany jest tutaj: Kontrakty w WCF. Nasza aplikacja WCF będzie udostępniała usługę wyliczania składki emerytalnej z kwoty brutto pracownika zatrudnionego na podstawie umowy o pracę w roku 2011.
Stwórzmy zatem trzy projekty: projekt z interfejsami, projekt serwera implementujący interfejs i projekt klienta wykorzystujący interfejs. Referencja projektu z interfejsem musi oczywiście zostać włączona do pozostałych projektów.
Przejdźmy do samej definicji interfejsu, który będzie wyglądał następująco:
{
[OperationContract]
decimal GetValue(decimal gross);
[OperationContract]
List<decimal> GetValues(List<decimal> grossValues);
}
Usługa będzie udostępniała dwie metody. Pierwsza policzy składki emerytalne dla pojedynczej wartości, druga natomiast składki emerytalne dla wielu wartości jednocześnie.
Implementacja metod kontraktu i kod serwera
Kontrakt mamy gotowy i znajduje się on w projekcie z interfejsami. Przechodzimy zatem do projektu serwera. Dodajmy sobie klasę Skladki, która będzie implementowała wspomniany interfejs. Przypominam o dodaniu referencji do projektu z interfejsami i do biblioteki System.ServiceModel. Kod klasy skladki pokazany jest poniżej:
{
public decimal GetValue(decimal gross)
{
return gross * 0.0976M;
}
public List<decimal> GetValues(List<decimal> grossValues)
{
List<decimal> returnValues = new List<decimal>();
foreach (var value in grossValues)
returnValues.Add(value * 0.0976M);
return returnValues;
}
}
Po stronie serwera należy jeszcze wystawić zaimplementowany interfejs. Sam kod klasy Program.cs nie będzie się różnił od kodu zaprezentowanego w artykule Konfiguracja WCF za pomocą plików app.config. Przypomnijmy sobie zatem, jak on wyglądał:
{
ServiceHost host = new ServiceHost(typeof(HelloWorld));
host.Open();
Console.WriteLine("Serwer uruchomiony");
Console.ReadLine();
}
Strona serwerowa jest prawie gotowa. Potrzebny nam jest jeszcze plik App.config z właściwą konfiguracją. Tutaj znajduje się to, co sprawia, że nasza usługa sieciowa będzie działała w ramach TCP/IP. Zanim przystąpię do wyjaśnień, przyjrzyjmy się przykładowej konfiguracji zaprezentowanej poniżej:
<configuration>
<system.serviceModel>
<services>
<service name="Skladki">
<endpoint address="net.tcp://localhost:2222/Skladki"
binding="netTcpBinding"
contract="ISkladki"/>
</service>
</services>
</system.serviceModel>
</configuration>
Zwróćmy teraz uwagę na drobne różnice. Pomijam oczywiście różnice spowodowane inną nazwą klasy i inną nazwą interfejsu. Pierwsza znacząca różnica ujawnia się w definicji adresu. W przypadku basicHttpBinding adres rozpoczynał się od http://, w przypadku TCP/IP adres należy rozpocząć od net.tcp://.
Druga kluczowa różnica to zmiana w atrybucie binding. Zamiast basicHttpBinding pojawia się netTcpBinding. Jest to domyślna konfiguracja sposobu przesyłania danych za pomocą protokołu TCP/IP. Jak zmieniać konfiguracje opiszę wkrótce. Dobra wiadomość jest taka, że mamy ukończony kod serwera. Można się o tym przekonać kompilując i uruchamiając aplikację.
Kod klienta
Kodu klienta nie będę szczegółowo omawiał, bo nie ma tutaj niczego specjalnego. Ot po prostu zwykły kod. Z elementami charakterystycznymi dla WCF mieliśmy już do czynienia przy okazji omawiania wcześniejszych przykładów. Pozostała część ma z WCF niewiele wspólnego. Kod klienta pokazany jest poniżej:
{
using (var c = new ChannelFactory<ISkladki>(""))
{
var s = c.CreateChannel();
Console.WriteLine(s.GetValue(2000.0M));
List<decimal> valuesList = new List<decimal>(){
2000.0M, 2200.0M, 2400.0M,
2600.0M, 2800.0M, 3000.0M
};
List<decimal> returnValues = s.GetValues(valuesList);
foreach (var value in returnValues)
Console.WriteLine(value);
Console.ReadLine();
}
}
Zmiany na kliencie dotyczą głównie pliku konfiguracyjnego. Spośród różnych możliwości konfiguracji przedstawię chyba najprostszą z możliwych. Na skomplikowane przyjdzie jeszcze czas. Plik App.config będzie miał tym razem taką oto zawartość:
<configuration>
<system.serviceModel>
<client>
<endpoint address="net.tcp://localhost:2222/Skladki"
binding="netTcpBinding"
contract="ISkladki">
</endpoint>
</client>
</system.serviceModel>
</configuration>
Różnica w stosunku do basicHttpBinding jest niewielka. Oprócz nazwy usługi i nazwy kontraktu, plik konfiguracyjny różni się sposobem podawania adresu. Tak jak po stronie serwera, tak i tutaj adres rozpoczyna się od net.tcp://.
Wnioski
Skorzystanie z protokołu TCP/IP w WCF nie jest szczególnie trudne. W najprostszej postaci sprowadza się do modyfikacji atrybutu binding w plikach konfiguracyjnych, oraz niewielkiej zmianie adresu. Podobnie jak zawsze, zachęcam do różnych eksperymentów. Ćwiczenia mogą polegać na rozszerzeniu interfejsu o dodatkowe metody liczące oprócz składki emerytalnej składkę rentową, zdrowotną. Dane mogą być zwracane w klasie, którą, przypominam, należy oznaczyć atrybutami DataContract i DataMember. Zachęcam do dzielenia się problemami i spostrzeżeniami w komentarzach.
Kategoria:C#Windows Communication Foundation
Komentarze:
Czy jest to możliwe?
Przykład:
{
List<IDane> lista = new List<IDane>();
public Nosnik()
{
lista.Add(new Dane1());
lista.Add(new Dane2());
}
}
public interface IDane
{
string dana { get; }
}
public class Dane1:IDane
{
private string _dana = "Jestem obiektem klasy Dane1";
public string dana
{
get { return _dana; }
}
}
public class Dane2:IDane
{
private string _dana = "Jestem obiektem klasy Dane2";
public string dana
{
get { return _dana; }
}
}
Pozdrawiam
Morfi
Problem może się pojawić jeszcze wtedy, gdy właściwość nie ma atrybutu set (ma tylko get). Wewnętrzna budowa serializatora nie pozwala na takie konstrukcje i wyświetla komunikat Brak metody Set dla właściwości XXX w typie YYY (ang. No set method for property XXX in type YYY). Da się to trochę zmiękczyć stosując kwalifikator protected, dzięki czemu tylko klasy dziedziczące będą miały dostęp (dla innych tak jak private). Serializator, co interesujce, poradzi sobie z takim czymś.