12. Usługi internetowe
12.1. Introduction
W poprzednim rozdziale przedstawiliśmy kilka aplikacji klient-serwer opartych na protokole TCP/IP. Ponieważ klienci i serwer wymieniają między sobą wiersze tekstu, mogą być napisane w dowolnym języku programowania. Klient musi jedynie znać protokół komunikacji oczekiwany przez serwer.
Usługi internetowe są również aplikacjami serwerowymi opartymi na protokole TCP/IP. Charakteryzują się następującymi cechami:
- Są one hostowane przez serwery internetowe, a protokołem wymiany danych między klientem a serwerem jest HTTP (HyperText Transport Protocol), protokół działający na poziomie wyższym niż TCP-IP.
- Usługa internetowa posiada standardowy protokół komunikacyjny niezależnie od świadczonej usługi. Usługa internetowa oferuje różne usługi: S1, S2, …, Sn. Każda z nich oczekuje na parametry dostarczone przez klienta i zwraca mu wynik. W przypadku każdej usługi klient musi znać:
- dokładną nazwę usługi oraz, jeśli
- listę parametrów, które należy mu przekazać, oraz ich typ
- typ wyniku zwracanego przez usługę
Gdy te elementy są już znane, komunikacja między klientem a serwerem przebiega według tego samego schematu, niezależnie od wywoływanej usługi internetowej. Dzięki temu sposób pisania kodu przez klientów jest ujednolicony.
- Ze względów bezpieczeństwa przed atakami z Internetu wiele organizacji posiada sieci prywatne i udostępnia w Internecie jedynie niektóre porty swoich serwerów: głównie port 80 usługi internetowej. Wszystkie pozostałe porty są zablokowane. Dlatego też aplikacje typu klient-serwer, takie jak te przedstawione w poprzednim rozdziale, są tworzone w ramach sieci prywatnej (intranetu) i zazwyczaj nie są dostępne z zewnątrz. Umieszczenie usługi na serwerze internetowym sprawia, że staje się ona dostępna dla całej społeczności internetowej.
- Usługę internetową można modelować jako obiekt zdalny. Oferowane usługi stają się wówczas metodami tego obiektu. Klient może uzyskać dostęp do tego obiektu zdalnego tak, jakby był on lokalny. Ukrywa to całą część komunikacji sieciowej i pozwala na stworzenie klienta niezależnego od tej warstwy. Jeśli ta ulegnie zmianie, klient nie musi być modyfikowany.
- Podobnie jak w przypadku aplikacji klient-serwer TCP/IP omówionych w poprzednim rozdziale, zarówno klient, jak i serwer mogą być napisane w dowolnym języku programowania. Wymieniają one między sobą wiersze tekstu. Składają się one z dwóch części:
- nagłówki wymagane przez protokół HTTP
- treść wiadomości. W przypadku odpowiedzi serwera skierowanej do klienta ma ona format XML (eXtensible Markup Language). W przypadku żądania klienta skierowanego do serwera treść wiadomości może przybierać różne formy, w tym XML. Żądanie klienta o formacie XML może mieć specjalny format zwany SOAP (Simple Object Access Protocol). W takim przypadku odpowiedź serwera również ma format SOAP.
Architektura aplikacji klient-serwer opartej na usłudze internetowej wygląda następująco:
![]() |
Jest to rozszerzenie architektury trójwarstwowej, do której dodano wyspecjalizowane klasy komunikacji sieciowej. Z podobną architekturą mieliśmy już do czynienia w przypadku graficznej aplikacji klienckiej dla systemu Windows i serwera TCP urzędu skarbowego, omówionej w paragrafie 11.9.1.
Wyjaśnijmy te ogólne zasady na pierwszym przykładzie.
12.2. Pierwsza usługa internetowa z wykorzystaniem programu Visual Web Developer
Zbudujemy pierwszą aplikację klient-serwer o następującej uproszczonej architekturze:
![]() |
12.2.1. Część serwerowa
Wspomnieliśmy już, że usługa internetowa jest hostowana przez serwer internetowy. Tworzenie usług internetowych mieści się w ogólnym zakresie programowania internetowego po stronie serwera. Wcześniej mieliśmy okazję tworzyć aplikacje klienckie, co również jest formą programowania internetowego, ale tym razem po stronie klienta. Termin „programowanie internetowe” najczęściej odnosi się do programowania po stronie serwera, a nie po stronie klienta. Do tworzenia usług internetowych lub, bardziej ogólnie, aplikacji internetowych, Visual C# nie jest odpowiednim narzędziem. Będziemy korzystać z Visual Developer, jednej z wersji Express programu Visual Studio 2008, którą można pobrać pod adresem [2]: [1]: [http://msdn.microsoft.com/fr-fr/express/future/bb421473(en-us).aspx] (maj 2008):
![]() |
- [1]: adres pobierania
- [2]: zakładka „Pobieranie”
- [3]: pobierz Visual Developer 2008
Aby utworzyć swoją pierwszą usługę internetową, po uruchomieniu programu Visual Developer można postępować w następujący sposób:
![]() |
- [1]: wybierz opcję Plik / Nowa strona internetowa
- [2]: wybrać typ aplikacji ASP.NET Usługa internetowa
- [3]: wybrać język programowania: C#
- [4]: wskazać folder, w którym ma zostać utworzony projekt
![]() |
- [5]: projekt utworzony w programie Visual Web Developer
- [6]: folder projektu na dysku
Aplikacja internetowa ma w programie Web Developer następującą strukturę:
- katalog główny, w którym znajdują się dokumenty serwisu internetowego (statyczne strony HTML, obrazy, dynamiczne strony .aspx, usługi internetowe .asmx itp.). Znajduje się tam również plik [web.config], który jest plikiem konfiguracyjnym aplikacji internetowej. Pełni on tę samą rolę, co plik [App.config] w aplikacjach Windows i ma taką samą strukturę.
- folder [App_Code], w którym znajdują się klasy i interfejsy serwisu internetowego przeznaczone do kompilacji.
- folder [App_Data], w którym umieszcza się dane wykorzystywane przez klasy z folderu [App_Code]. Można tam na przykład znaleźć bazę danych SQL Server *.mdf.
[Service.asmx] to usługa internetowa, o której utworzenie wnioskowaliśmy. Zawiera ona wyłącznie następujący wiersz:
<%@ WebService Language="C#" CodeBehind="~/App_Code/Service.cs" Class="Service" %>
Powyższy kod źródłowy jest przeznaczony dla serwera internetowego, na którym będzie hostowana aplikacja. W trybie produkcyjnym serwerem tym jest zazwyczaj IIS (Internet Information Server), serwer internetowy firmy Microsoft. Visual Web Developer zawiera wbudowany lekki serwer internetowy, który jest używany w trybie programowania. Powyższa dyrektywa informuje serwer internetowy:
- [Service.asmx] to usługa internetowa (dyrektywa WebService)
- napisana w języku C# (atrybut Language)
- że kod C# usługi internetowej znajduje się w pliku [~/App_Code/Service.cs] (atrybut CodeBehind). To właśnie tam serwer internetowy będzie go szukał w celu skompilowania.
- że klasa implementująca usługę internetową nosi nazwę Service (atrybut Class)
Kod C# [Service.cs] usługi internetowej wygenerowany przez Visual Developer wygląda następująco:
using System.Web.Services;
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// Aby umożliwić wywołanie tej usługi internetowej ze skryptu przy użyciu ASP.NET AJAX, należy usunąć komentarz z poniższego wiersza.
// [System.Web.Script.Services.ScriptService]
public class Service : System.Web.Services.WebService
{
public Service () {
//Usuń komentarz z poniższego wiersza, jeśli korzystasz z gotowych komponentów
//InitializeComponent();
}
[WebMethod]
public string HelloWorld() {
return "Hello World";
}
}
Klasa Service przypomina klasyczną klasę C#, jednak należy zwrócić uwagę na kilka kwestii:
- wiersz 7: klasa ta dziedziczy po klasie WebService zdefiniowanej w przestrzeni nazw System.Web.Services. Dziedziczenie to nie zawsze jest obowiązkowe. W tym konkretnym przykładzie można by się bez niego obejść.
- wiersz 3: przed samą klasą znajduje się atrybut [WebService(Namespace="http://tempuri.org/")], którego zadaniem jest nadanie przestrzeni nazw usłudze internetowej. Dostawca klas nadaje swoim klasom przestrzeń nazw, aby zapewnić im unikalną nazwę i w ten sposób uniknąć konfliktów z klasami innych dostawców, które mogłyby nosić tę samą nazwę. W przypadku usług internetowych zasada jest taka sama. Każda usługa internetowa musi być identyfikowana za pomocą unikalnej nazwy, w tym przypadku http://tempuri.org/. Nazwa ta może być dowolna. Nie musi koniecznie mieć postaci adresu URI HTTP.
- Wiersz 15: przed metodą HelloWorld znajduje się atrybut [WebMethod], który informuje kompilator, że metoda ta ma być widoczna dla zdalnych klientów serwisu internetowego. Metoda, której nie poprzedza ten atrybut, nie jest widoczna dla klientów serwisu internetowego. Może to być metoda wewnętrzna wykorzystywana przez inne metody, ale nieprzeznaczona do publikacji.
- wiersz 9: konstruktor serwisu internetowego. W naszej aplikacji jest on zbędny.
Wygenerowana klasa [Service.cs] zostaje przekształcona w następujący sposób:
using System.Web.Services;
[WebService(Namespace = "http://st.istia.univ-angers.fr")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
{
[WebMethod]
public string DisBonjourALaDame(string nomDeLaDame) {
return string.Format("Bonjour Mme {0}", nomDeLaDame);
}
}
Plik konfiguracyjny [web.config] wygenerowany dla aplikacji internetowej wygląda następująco:
<?xml version="1.0"?>
<!--
Note: As an alternative to hand editing this file you can use the
web admin tool to configure settings for your application. Use
the Website->Asp.Net Configuration option in Visual Studio.
A full list of settings and comments can be found in
machine.config.comments usually located in
\Windows\Microsoft.Net\Framework\v2.x\Config
-->
<configuration>
<configSections>
<sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
...
</sectionGroup>
</configSections>
<appSettings/>
<connectionStrings/>
...
</configuration>
Plik ma 140 wierszy. Jest złożony i nie będziemy go omawiać. Pozostawimy go bez zmian. Powyżej znajdują się tagi <configuration>, <configSections>, <sectionGroup>, <appSettings>, <connectionString>, które spotkaliśmy już w pliku [App.config] aplikacji dla systemu Windows.
Mamy działającą usługę internetową, którą można uruchomić:
![]() |
- [1,2]: klikamy prawym przyciskiem myszy na plik [Service.asmx] i wybieramy opcję wyświetlenia strony w przeglądarce
- [3]: Program Visual Web Developer uruchamia swój wbudowany serwer WWW i umieszcza jego ikonę w prawym dolnym rogu paska zadań. Serwer WWW uruchamia się na losowym porcie, w tym przypadku 1906. Wyświetlany adres URI /WsHello to nazwa strony internetowej [4].
Program Visual Web Developer uruchomił również przeglądarkę w celu wyświetlenia żądanej strony, a mianowicie [Service.asmx]:
![]() |
- w [1], co stanowi adres URI tej strony. Widoczny jest adres URI witryny [http://localhost:1906/WsHello], po którym następuje adres URI strony /Service.asmx.
- W przypadku [2] rozszerzenie .asmx wskazało serwerowi WWW, że nie jest to zwykła strona internetowa (z rozszerzeniem .aspx), generująca stronę HTML, ale strona usługi internetowej. Następnie automatycznie generuje stronę internetową zawierającą link do każdej z metod serwisu internetowego o atrybucie [WebMethod]. Linki te służą do testowania metod.
Kliknięcie powyższego linku [2] przenosi nas na następującą stronę:
![]() |
- w [1] należy zwrócić uwagę na adres URI [http://localhost:1906/WsHello/Service.asmx?op=DisBonjourALaDame] nowej strony. Jest to adres URI usługi internetowej z parametrem op=M, gdzie M to nazwa jednej z metod tej usługi.
- Przypomnijmy sygnaturę metody [DisBonjourALaDame]:
public string DisBonjourALaDame(string nomDeLaDame) ;
Metoda przyjmuje parametr typu string i zwraca wynik typu string. Strona umożliwia nam wywołanie metody [DisBonjourALaDame]: w polu [2] wpisujemy wartość parametru nomDeLaDame, a w polu [3] żądamy wykonania metody. Otrzymujemy następujący wynik:
![]() |
- W polu [1] należy zauważyć, że adres URI odpowiedzi nie jest identyczny z adresem żądania. Uległ on zmianie.
- W przypadku [2] otrzymujemy odpowiedź serwera WWW. Należy zwrócić uwagę na następujące kwestie:
- jest to odpowiedź o adresie XML, a nie HTML
- wynik metody [DisBonjourALaDame] jest zawarty w tagu <string>, który określa jego typ.
- tag <string> posiada atrybut xmlns (xml name space), który jest przestrzenią nazw nadaną przez nas naszej usłudze internetowej (wiersz 1 poniżej).
[WebService(Namespace = "http://st.istia.univ-angers.fr")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
Aby dowiedzieć się, w jaki sposób przeglądarka internetowa wysłała żądanie, należy przyjrzeć się kodowi HTML formularza testowego:
- wiersz 11: wartości z formularza (tag form) zostaną wysłane (atrybut method) na adres URL [ http://localhost:1906/WsHello/Service.asmx/DisBonjourALaDame] (atrybut action).
- wiersz 19: pole wprowadzania danych nosi nazwę nomDeLaDame (atrybut name).
Wywołanie usługi internetowej [/Service.asmx] pozwoliło nam przetestować jej metody i uzyskać podstawową wiedzę na temat komunikacji między klientem a serwerem.
12.2.2. Część kliencka
![]() |
Klienta powyższej zdalnej usługi internetowej można zaimplementować za pomocą podstawowego klienta TCP/IP. Oto przykładowy dialog klient–serwer zrealizowany przy użyciu klienta putty połączonego ze zdalną usługą internetową (localhost,1906):
- wiersze 1–5: komunikaty wysłane przez klienta putty
- wiersz 1: polecenie POST
- wiersze 6–10: odpowiedź serwera. Oznacza to, że klient może wysłać wartości POST.
- wiersz 11: wartości przesłane w postaci param1=val1¶m2=val2&.... Niektóre znaki muszą być znakami dopuszczalnymi w adresie URL. To właśnie nazwaliśmy wcześniej zakodowanym adresem URL. W tym przypadku formularz ma tylko jeden parametr o nazwie nomDeLaDame. Wartość przesłana w metodzie POST ma łącznie 23 znaki. Rozmiar ten należy zadeklarować w nagłówku HTTP w wierszu 4.
- wiersze 12–22: odpowiedź serwera
- wiersz 22: wynik metody internetowej [DisBonjourALaDame].
W języku Visual C# można za pomocą kreatora wygenerować klienta zdalnej usługi internetowej. Właśnie to teraz zobaczymy.
![]() |
Powyższa warstwa [1] jest zaimplementowana w projekcie Visual Studio C# typu Aplikacja Windows o nazwie ClientWsHello:
![]() |
- w [1], projekt ClientWsHello w Visual C#
- w [2], domyślną przestrzenią nazw projektu będzie Client (kliknij prawym przyciskiem myszy na projekt / Właściwości / Aplikacja). Ta przestrzeń nazw posłuży do utworzenia przestrzeni nazw klienta, który zostanie wygenerowany.
- W przypadku projektu o nazwie [3] należy kliknąć prawym przyciskiem myszy na projekcie, aby dodać do niego odwołanie do zdalnej usługi internetowej
![]() |
- w [4] wprowadź adres URI utworzonej wcześniej usługi internetowej
- w pliku [4b] należy połączyć Visual C# z usługą internetową wskazaną w pliku [4]. Visual C# pobierze opis usługi internetowej i na podstawie tego opisu będzie w stanie wygenerować klienta.
- w [5], po pobraniu opisu usługi internetowej Visual C# może wyświetlić jej metody publiczne
- W polu [6] należy podać przestrzeń nazw dla klienta, który ma zostać wygenerowany. Zostanie ona dodana do przestrzeni nazw zdefiniowanej w polu [2]. W ten sposób przestrzeń nazw klienta będzie miała postać Client.WsHello.
- W [6b] zatwierdzić kreatora.
- W [7] w projekcie pojawia się odwołanie do serwisu internetowego WsHello. Ponadto utworzono plik konfiguracyjny [app.config].
- W [8] wyświetl wszystkie pliki projektu.
- W pliku [9] odwołanie do usługi internetowej WsHello zawiera różne pliki, których nie będziemy tutaj szczegółowo omawiać. Przyjrzymy się jednak plikowi [Reference.cs], który zawiera wygenerowany kod klienta w języku C#:
namespace Client.WsHello {
...
public partial class ServiceSoapClient : System.ServiceModel.ClientBase<Client.WsHello.ServiceSoap>, Client.WsHello.ServiceSoap {
public ServiceSoapClient() {
}
...
public string DisBonjourALaDame(string nomDeLaDame) {
Client.WsHello.DisBonjourALaDameRequest inValue = new Client.WsHello.DisBonjourALaDameRequest();
inValue.Body = new Client.WsHello.DisBonjourALaDameRequestBody();
inValue.Body.nomDeLaDame = nomDeLaDame;
Client.WsHello.DisBonjourALaDameResponse retVal = ((Client.WsHello.ServiceSoap)(this)).DisBonjourALaDame(inValue);
return retVal.Body.DisBonjourALaDameResult;
}
}
}
- wiersz 1: przestrzeń nazw wygenerowanego klienta to Client.WsHello. Jeśli chcemy zmienić tę przestrzeń nazw, należy to zrobić właśnie w tym miejscu.
- wiersz 3: klasa ServiceSoapClient to klasa wygenerowanego klienta. Jest to klasa proxy w tym sensie, że ukrywa przed aplikacją Windows fakt, iż wykorzystywana jest zdalna usługa internetowa. Aplikacja Windows będzie korzystać ze zdalnej klasy WsHello za pośrednictwem lokalnej klasy Client.WsHello.ServiceSoapClient. Aby utworzyć instancję klienta, należy użyć konstruktora z wiersza 5:
- wiersz 8: metoda DisBonjourALaDame jest odpowiednikiem po stronie klienta metody DisBonjourALaDame usługi internetowej. Aplikacja Windows będzie korzystać ze zdalnej metody DisBonjourALaDame za pośrednictwem lokalnej metody Client.WsHello.ServiceSoapClient.DisBonjourALaDame w następującej postaci:
Wygenerowany plik [app.config] ma następującą postać:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
....
</bindings>
<client>
<endpoint address="http://localhost:1906/WsHello/Service.asmx"... />
</client>
</system.serviceModel>
</configuration>
Z tego pliku zachowamy jedynie wiersz 8, który zawiera adres URI serwisu internetowego. Jeśli adres URI ulegnie zmianie, nie ma potrzeby ponownego tworzenia klienta dla systemu Windows. Wystarczy zmienić adres URI w pliku [app.config].
Wróćmy do architektury aplikacji dla systemu Windows, którą chcemy zbudować:
![]() |
Stworzyliśmy warstwę [client] usługi internetowej. Kolejną warstwą będzie [ui]:
![]() |
nr | typ | nazwa | rola |
1 | TextBox | textBoxNomDame | imię kobiety |
2 | Button | buttonSalutations | w celu połączenia się ze zdalną usługą internetową WsHello i wywołania metody DisBonjourALaDame. |
3 | Etykieta | labelBonjour | wynik zwrócony przez serwis internetowy |
Kod formularza [Form1.cs] jest następujący:
using System;
using System.Windows.Forms;
using Client.WsHello;
namespace ClientSalutations {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void buttonSalutations_Click(object sender, EventArgs e) {
// klepsydra
Cursor=Cursors.WaitCursor;
// zapytanie do serwisu internetowego
labelBonjour.Text = new ServiceSoapClient().DisBonjourALaDame(textBoxNomDame.Text.Trim());
// normalny kursor
Cursor = Cursors.Arrow;
}
}
}
- wiersz 15: instancja klienta serwisu internetowego jest tworzona. Jest to typ Client.WsHello.ServiceSoapClient. W wierszu 3 zadeklarowano przestrzeń nazw Client.WsHello. Wywołano metodę lokalną ServiceSoapClient().DisBonjourALaDame. Wiadomo, że ona sama wywołuje zdalną metodę o tej samej nazwie w usłudze internetowej.
12.3. Serwis internetowy do operacji arytmetycznych
Zbudujemy drugą aplikację klient-serwer, która ponownie będzie miała następującą uproszczoną architekturę:
![]() |
Poprzednia usługa internetowa udostępniała tylko jedną metodę. Rozważamy usługę internetową, która będzie udostępniać cztery operacje arytmetyczne:
- dodaj(a,b), która zwróci a+b
- odejmowanie(a,b), które zwróci a-b
- mnożenie(a,b), które zwróci a*b
- dzielenie(a,b), które zwróci a/b
i który będzie wywoływany za pomocą następującego interfejsu graficznego:
![]() |
- w [1], operacja do wykonania
- w [2,3]: operandy
- w [4], przycisk wywołujący usługę internetową
- w [5] – wynik zwrócony przez usługę internetową
12.3.1. Część serwerowa
Tworzymy projekt typu usługa internetowa za pomocą programu Visual Web Developer:
![]() |
- w [1] – wygenerowana aplikacja internetowa WsOperations
- w [2] aplikacja internetowa WsOperations została zmodyfikowana w następujący sposób:
- strona internetowa [Service.asmx] została przemianowana na [Operations.asmx]
- klasa [Service.cs] została przemianowana na [Operations.cs]
- plik [web.config] został usunięty, aby pokazać, że nie jest niezbędny.
Strona internetowa [Service.asmx] zawiera następujący wiersz:
<%@ WebService Language="C#" CodeBehind="~/App_Code/Operations.cs" Class="Operations" %>
Usługa internetowa jest obsługiwana przez następującą klasę [Operations.cs]:
using System.Web.Services;
[WebService(Namespace = "http://st.istia.univ-angers.fr/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Operations : System.Web.Services.WebService
{
[WebMethod]
public double Ajouter(double a, double b)
{
return a + b;
}
[WebMethod]
public double Soustraire(double a, double b)
{
return a - b;
}
[WebMethod]
public double Multiplier(double a, double b)
{
return a * b;
}
[WebMethod]
public double Diviser(double a, double b)
{
return a / b;
}
}
Aby uruchomić usługę internetową, postępujemy zgodnie z instrukcjami zawartymi w [3]. Otrzymujemy wówczas stronę testową przedstawiającą 4 metody usługi internetowej WsOperations:

Zachęcamy czytelnika do przetestowania tych czterech metod.
12.3.2. Część kliencka
![]() |
W języku Visual C# tworzymy aplikację dla systemu Windows o nazwie ClientWsOperations:
![]() |
- w [1], projekt ClientWsOperations w Visual C#
- w [2] domyślną przestrzenią nazw projektu będzie Client (kliknij prawym przyciskiem myszy na projekt / Właściwości / Aplikacja). Ta przestrzeń nazw posłuży do utworzenia przestrzeni nazw klienta, który zostanie wygenerowany.
- w [3], kliknij prawym przyciskiem myszy na projekt, aby dodać do niego odwołanie do istniejącej usługi internetowej
![]() |
- w [4] należy wprowadzić adres URI wcześniej utworzonej usługi internetowej. W tym celu należy sprawdzić, co jest wyświetlane w polu adresu przeglądarki, w której wyświetlana jest strona testowa usługi internetowej.
- w pliku [4b], podłącz Visual C# do serwisu internetowego wskazanego przez plik [4]. Visual C# pobierze opis serwisu internetowego i na podstawie tego opisu będzie w stanie wygenerować klienta.
- W [5], po pobraniu opisu serwisu internetowego, Visual C# może wyświetlić jego metody publiczne
- w [6] należy wskazać przestrzeń nazw dla klienta, który zostanie wygenerowany. Zostanie ona dodana do przestrzeni nazw zdefiniowanej w [2]. W ten sposób przestrzeń nazw klienta będzie wynosić Client.WsOperations.
- W pliku [6b] zatwierdź kreator.
- W [7] w projekcie pojawia się odwołanie do serwisu internetowego WsOperations. Ponadto utworzono plik konfiguracyjny [app.config].
Przypominamy, że wygenerowany klient ma typ Client.WsOperations.OperationsSoapClient, gdzie
- Client.WsOperations to przestrzeń nazw klienta usługi internetowej
- Operations to klasa zdalnej usługi internetowej.
Chociaż istnieje logiczny sposób tworzenia tej nazwy, często łatwiej jest ją znaleźć w pliku [Reference.cs], który domyślnie jest plikiem ukrytym. Jego zawartość jest następująca:
namespace Client.WsOperations {
...
public partial class OperationsSoapClient : System.ServiceModel.ClientBase<Client.WsOperations.OperationsSoap>, Client.WsOperations.OperationsSoap {
public OperationsSoapClient() {
}
...
public double Ajouter(double a, double b) {
...
}
public double Soustraire(double a, double b) {
...
}
public double Multiplier(double a, double b) {
...
}
public double Diviser(double a, double b) {
...
}
}
}
Metody Ajouter, Soustraire, Multiplier, Diviser zdalnej usługi internetowej będą wywoływane za pośrednictwem metod proxy o tych samych nazwach (wiersze 8, 12, 16, 20) klienta typu Client.WsOperations.OperationsSoapClient (wiersz 3).
Pozostaje nam jeszcze stworzyć interfejs graficzny:
![]() |
nr | typ | nazwa | rola |
1 | ComboBox | comboBoxOperations | lista operacji arytmetycznych |
2 | TextBox | textBoxA | liczba a |
3 | TextBox | textBoxB | liczba b |
4 | Przycisk | buttonExécuter | wysyła zapytanie do zdalnej usługi internetowej |
5 | Etykieta | labelRésultat | wynik operacji |
Kod [Form1.cs] jest następujący:
using System;
using System.Windows.Forms;
using Client.WsOperations;
namespace ClientWsOperations {
public partial class Form1 : Form {
// tabela operacji
private string[] opérations = { "Ajouter", "Soustraire", "Multiplier", "Diviser" };
// usługa internetowa, z którą należy się skontaktować
private OperationsSoapClient opérateur = new OperationsSoapClient();
// konstruktor
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
// wypełnianie listy rozwijanej operacji
comboBoxOperations.Items.AddRange(opérations);
comboBoxOperations.SelectedIndex = 0;
}
private void buttonExécuter_Click(object sender, EventArgs e) {
// weryfikacja parametrów a i b operacji
textBoxMessage.Text = "";
bool erreur = false;
Double a = 0;
if (!Double.TryParse(textBoxA.Text, out a)) {
textBoxMessage.Text += "Nombre a erroné...";
}
Double b = 0;
if (!Double.TryParse(textBoxB.Text, out b)) {
textBoxMessage.Text += String.Format("{0}Nombre b erroné...", Environment.NewLine);
}
if (erreur) {
return;
}
// wykonanie operacji
Double c=0;
try {
switch (comboBoxOperations.SelectedItem.ToString()) {
case "Ajouter":
c=opérateur.Ajouter(a, b);
break;
case "Soustraire":
c=opérateur.Soustraire(a, b);
break;
case "Multiplier":
c=opérateur.Multiplier(a, b);
break;
case "Diviser":
c=opérateur.Diviser(a, b);
break;
}
// wyświetlenie wyniku
labelRésultat.Text = c.ToString();
} catch (Exception ex) {
textBoxMessage.Text = ex.Message;
}
}
}
}
- wiersz 3: przestrzeń nazw klienta zdalnej usługi internetowej
- wiersz 10: klient zdalnej usługi internetowej jest instancjonowany jednocześnie z formularzem
- wiersze 17–21: lista operacji jest wypełniana podczas początkowego ładowania formularza
- wiersz 23: wykonanie operacji żądanej przez użytkownika
- wiersze 25–37: sprawdzamy, czy wartości a i b wprowadzone przez użytkownika są liczbami rzeczywistymi
- wiersze 41–54: instrukcja switch służąca do wykonania operacji zdalnej żądanej przez użytkownika
- wiersze 43, 46, 49, 52: zapytanie kierowane jest do lokalnego klienta. Ten w sposób przezroczysty kieruje zapytanie do zdalnej usługi internetowej.
12.4. Serwis internetowy do obliczania podatku
Wracamy do dobrze znanej już aplikacji do obliczania podatku. Kiedy ostatnio nad nią pracowaliśmy, stworzyliśmy z niej zdalny serwer TCP, do którego można było się połączyć przez Internet. Teraz przekształcamy ją w usługę internetową.
Architektura wersji 8 wyglądała następująco:
![]() |
Architektura wersji 9 będzie podobna:
![]() |
Jest to architektura podobna do tej z wersji 8 omówionej w paragrafie 11.9.1, z tą różnicą, że serwer i klient TCP zostały zastąpione przez usługę internetową i jej klienta proxy. W całości przejmujemy warstwy [ui], [metier] oraz [dao] z wersji 8.
12.4.1. Część serwerowa
Tworzymy projekt typu usługa internetowa za pomocą programu Visual Web Developer:
![]() |
- w [1], aplikacja internetowa WsImpot wygenerowana
- w [2], aplikacja internetowa WsImpot została zmodyfikowana w następujący sposób:
- strona internetowa [Service.asmx] została przemianowana na [ServiceImpot.asmx]
- klasa [Service.cs] została przemianowana na [ServiceImpot .cs]
Strona internetowa [ServiceImpot.asmx] zawiera następujący wiersz:
<%@ WebService Language="C#" CodeBehind="~/App_Code/ServiceImpot.cs" Class="ServiceImpot" %>
Usługa internetowa jest obsługiwana przez następującą klasę: [ServiceImpot.cs]:
using System.Web.Services;
[WebService(Namespace = "http://st.istia.univ-angers.fr/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class ServiceImpot : System.Web.Services.WebService
{
[WebMethod]
public int CalculerImpot(bool marié, int nbEnfants, int salaire)
{
return 0;
}
}
Usługa internetowa udostępni wyłącznie metodę CalculerImpot z wiersza 9.
Wróćmy do architektury klient-serwer w wersji 8:
![]() |
Projekt Visual Studio serwera [1] wyglądał następująco:
![]() |
- w projekcie [1]. Zawierał on następujące elementy:
- [ServeurImpot.cs]: serwer TCP/IP do obliczania podatku w postaci aplikacji konsolowej.
- [dbimpots.sdf]: baza danych SQL Server compact w wersji 7 opisanej w paragrafie 9.8.5.
- [App.config]: plik konfiguracyjny aplikacji.
- W pliku [2] folder [lib] zawiera pliki DLL niezbędne dla projektu:
- [ImpotsV7-dao]: warstwa [dao] w wersji 7
- [ImpotsV7-metier]: warstwa [metier] w wersji 7
- [antlr.runtime, CommonLogging, Spring.Core] dla Spring
- w [3], numery referencyjne projektu
Warstwy [metier] i [dao] tej wersji już istnieją: są to warstwy używane w wersjach 7 i 8. Mają one postać DLL, którą włączamy do projektu w następujący sposób:
![]() |
- w [1] folder [lib] z serwera wersji 8 został skopiowany do projektu serwisu internetowego wersji 9.
- W pliku [2] modyfikujemy właściwości strony, aby dodać pliki DLL z folderu [lib] oraz [4] do odwołań projektu [3].
Po wykonaniu tej operacji dysponujemy wszystkimi warstwami niezbędnymi dla serwera [1], przedstawionymi poniżej:
![]() |
Jeśli elementy serwera [1], [serveur], [metier], [dao], [entites], [spring] są rzeczywiście obecne w projekcie Visual Studio, brakuje nam jednak elementu, który instancjonuje je podczas uruchamiania aplikacji internetowej. W wersji 8 główną klasą zawierającą metodę statyczną [Main], która instancjonowała warstwy przy pomocy Springa, była klasa główna. W aplikacji internetowej klasą zdolną do wykonania podobnego zadania jest klasa powiązana z plikiem [Global.asax] :
![]() |
- w pliku [1] dodaje się nowy element do projektu internetowego
- w pliku [2] wybieramy typ Global Application Class
- w [3] domyślna nazwa proponowana dla tego elementu
- w [4] zatwierdzamy dodanie
- w [5] nowy element został włączony do projektu
Przyjrzyjmy się zawartości pliku [Global.asax]:
<%@ Application Language="C#" %>
<script runat="server">
void Application_Start(object sender, EventArgs e)
{
// Kod uruchamiany przy starcie aplikacji
}
void Application_End(object sender, EventArgs e)
{
// Kod uruchamiany podczas zamykania aplikacji
}
void Application_Error(object sender, EventArgs e)
{
// Kod uruchamiany w przypadku wystąpienia nieobsłużonego błędu
}
void Session_Start(object sender, EventArgs e)
{
// Kod uruchamiany przy rozpoczęciu nowej sesji
}
void Session_End(object sender, EventArgs e)
{
// Kod uruchamiany po zakończeniu sesji.
}
</script>
Plik stanowi połączenie znaczników przeznaczonych dla serwera WWW (wiersze 1, 3, 30) oraz kodu C#. Metoda ta była jedyną stosowaną w przypadku ASP, poprzednika ASP.NET, który jest aktualną technologią firmy Microsoft do programowania stron internetowych. W przypadku ASP.NET metoda ta jest nadal dostępna, ale nie jest metodą domyślną. Metodą domyślną jest tzw. metoda „CodeBehind”, którą spotkaliśmy na stronach usług internetowych, na przykład tutaj w [ServiceImpot.asmx]:
<%@ WebService Language="C#" CodeBehind="~/App_Code/ServiceImpot.cs" Class="ServiceImpot" %>
Atrybut CodeBehind określa, gdzie znajduje się kod źródłowy strony [ServiceImpot.asmx]. Bez tego atrybutu kod źródłowy znajdowałby się na stronie [ServiceImpot.asmx], a jego składnia byłaby analogiczna do tej występującej w [Global.asax]. Nie zachowamy pliku [Global.asax] w postaci, w jakiej został wygenerowany, ale jego kod pozwala nam zrozumieć, do czego służy:
- klasa powiązana z plikiem Global.asax jest instancjonowana podczas uruchomienia aplikacji. Jej cykl życia pokrywa się z cyklem życia całej aplikacji. Konkretnie znika ona dopiero po wyłączeniu serwera WWW.
- Następnie wykonywana jest metoda Application_Start. Jest to jedyny moment, w którym zostanie ona wywołana. Dlatego wykorzystuje się ją do instancjonowania obiektów współdzielonych przez wszystkich użytkowników. Obiekty te są umieszczane:
- albo w statycznych polach klasy powiązanej z Global.asax. Ponieważ klasa ta istnieje na stałe, każdy użytkownik może odczytać z niej informacje w dowolnym zapytaniu.
- albo w kontenerze Application. Ten kontener jest również tworzony podczas uruchamiania aplikacji, a jego czas życia pokrywa się z czasem działania aplikacji.
- aby umieścić dane w tym kontenerze, piszemy Application["clé"]=wartość;
- aby je pobrać, należy wpisać T wartość=(T)Application["clé"]; gdzie T jest typem valeur.
- Metoda Session_Start jest wykonywana za każdym razem, gdy nowy użytkownik wysyła żądanie. Jak rozpoznaje się nowego użytkownika? Każdy użytkownik (najczęściej przeglądarka) otrzymuje po wysłaniu pierwszego żądania token sesji, który jest unikalnym ciągiem znaków dla każdego użytkownika. Następnie użytkownik odsyła otrzymany token sesji przy każdym nowym żądaniu, które wysyła. Dzięki temu serwer WWW może go rozpoznać. W miarę kolejnych żądań tego samego użytkownika dane, które są z nim powiązane, mogą być zapisywane w kontenerze Session:
- aby umieścić dane w tym kontenerze, piszemy Session["clé"]=wartość;
- aby je pobrać, należy wpisać T wartość=(T)Session["clé"]; gdzie T jest typem valeur.
Czas trwania sesji jest domyślnie ograniczony do 20 minut bezczynności użytkownika (c.a.d, tzn. nie odesłał on swojego tokenu sesji od 20 minut).
- Metoda Application_Error jest wykonywana, gdy wyjątek nieobsługiwany przez aplikację internetową jest przekazywany do serwera WWW.
- Pozostałe metody są wykorzystywane rzadziej.
Po tych ogólnych informacjach, do czego może nam służyć Global.asax? Wykorzystamy jego metodę Application_Start do zainicjowania warstw [metier], [dao] oraz [entites] zawarte w DLL i [ImpotsV7-metier, ImpotsV7-dao]. Do ich instancjonowania użyjemy Springa. Odwołania do tak utworzonych warstw zostaną następnie zapisane w statycznych polach klasy powiązanej z Global.asax.
W pierwszym etapie przenosimy kod C# z pliku Global.asax do osobnej klasy. Projekt zmienia się w następujący sposób:
![]() |
W pliku [1] plik [Global.asax] zostanie powiązany z klasą [Global.cs] [2], zawierając wyłącznie następujący wiersz:
<%@ Application Language="C#" Inherits="WsImpot.Global"%>
Atrybut Inherits="WsImpot.Global" wskazuje, że klasa powiązana z Global.asax dziedziczy po klasie WsImpot.Global. Klasa ta jest zdefiniowana w pliku [Global.cs] w następujący sposób:
using System;
using Metier;
using Spring.Context.Support;
namespace WsImpot
{
public class Global : System.Web.HttpApplication
{
// warstwa biznesowa
public static IImpotMetier Metier;
// metoda wykonywana podczas uruchamiania aplikacji
private void Application_Start(object sender, EventArgs e)
{
// instancje warstw [metier] i [dao]
Metier = ContextRegistry.GetContext().GetObject("metier") as IImpotMetier;
}
}
}
- wiersz 4: przestrzeń nazw klasy
- wiersz 6: klasa Global. Można nadać jej dowolną nazwę. Ważne jest, aby wywodziła się z klasy System.Web.HttpApplication.
- wiersz 9: publiczne pole statyczne, które będzie zawierało odwołanie do warstwy [metier].
- wiersz 12: metoda Application_Start, która zostanie wykonana podczas uruchamiania aplikacji.
- wiersz 15: biblioteka Spring służy do przetwarzania pliku [web.config], w którym znajdzie obiekty do instancjonowania w celu utworzenia warstw [metier] i [dao]. Nie ma żadnej różnicy między wykorzystaniem Springa z plikiem [App.config] w aplikacji dla systemu Windows a wykorzystaniem Springa z plikiem [web.config] w aplikacji internetowej. [web.config] i [App.config] mają zresztą tę samą strukturę. Wiersz 15 umieszcza odwołanie do warstwy [metier] w polu statycznym wiersza 9, tak aby odwołanie to było dostępne dla wszystkich zapytań wszystkich użytkowników.
Plik [web.config] będzie wyglądał następująco:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object name="dao" type="Dao.DataBaseImpot, ImpotsV7-dao">
<constructor-arg index="0" value="MySql.Data.MySqlClient"/>
<constructor-arg index="1" value="Server=localhost;Database=bdimpots;Uid=admimpots;Pwd=mdpimpots;"/>
<constructor-arg index="2" value="select limite, coeffr, coeffn from tranches"/>
</object>
<object name="metier" type="Metier.ImpotMetier, ImpotsV7-metier">
<constructor-arg index="0" ref="dao"/>
</object>
</objects>
</spring>
</configuration>
Jest to plik [App.config] używany w wersji 7 aplikacji i omówiony w paragrafie 9.8.4.
- wiersze 16–20: definiują warstwę [dao] współpracującą z bazą danych MySQL5. Baza ta została opisana w paragrafie 9.8.1.
- wiersze 21–23: definiują warstwę [metier]
Wróćmy do zagadki serwera:
![]() |
Podczas uruchamiania aplikacji utworzono instancje warstw [metier] i [dao]. Czas życia warstw pokrywa się z czasem życia samej aplikacji. Kiedy tworzona jest instancja serwisu internetowego? W rzeczywistości przy każdym wysłanym do niego żądaniu. Po zakończeniu żądania obiekt, który je obsłużył, jest usuwany. Usługa internetowa jest zatem na pierwszy rzut oka bezstanowa. Nie może ona zapisywać informacji między żądaniami w polach, które należą do niej. Może jednak zapisywać je w sesji użytkownika. W tym celu metody, które udostępnia, muszą być oznaczone specjalnym atrybutem:
[WebMethod(EnableSession=true)]
public int CalculerImpot(bool marié, int nbEnfants, int salaire)
....
W powyższym przykładzie wiersz 1 zezwala metodzie CalculerImpot na dostęp do kontenera Session, o którym wspominaliśmy wcześniej. W naszej aplikacji nie będziemy musieli używać tego atrybutu. Usługa internetowa WsImpot będzie zatem instancjonowana przy każdym żądaniu i będzie bezstanowa.
Możemy teraz napisać kod [ServiceImpot.cs], który implementuje usługę internetową:
using System.Web.Services;
using WsImpot;
[WebService(Namespace = "http://st.istia.univ-angers.fr/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class ServiceImpot : System.Web.Services.WebService
{
[WebMethod]
public int CalculerImpot(bool marié, int nbEnfants, int salaire)
{
return Global.Metier.CalculerImpot(marié, nbEnfants, salaire);
}
}
- wiersz 10: jedyna metoda serwisu internetowego
- wiersz 12: stosowana jest metoda CalculerImpot z warstwy [metier]. Odwołanie do tej warstwy znajduje się w polu statycznym Metier klasy Global. Należy ona do przestrzeni nazw WsImpot (wiersz 2).
Jesteśmy gotowi do uruchomienia serwisu internetowego. Wcześniej należy uruchomić SGBD i MySQL5, aby zapewnić dostęp do bazy danych bdimpots. Po wykonaniu tej czynności uruchamiamy serwis internetowy [1]:
![]() |
Przeglądarka wyświetla wówczas stronę [2]. Klikamy na link:
![]() |
Przypisujemy wartości każdemu z trzech parametrów metody CalculerImpot i uruchamiamy metodę. Otrzymujemy następujący, poprawny wynik:

12.4.2. Graficzny klient Windows dla zdalnej usługi internetowej
Teraz, gdy usługa internetowa została już napisana, przechodzimy do klienta. Przyjrzyjmy się ponownie architekturze aplikacji klient-serwer:
![]() |
Musimy napisać klienta [2]. Interfejs graficzny będzie identyczny jak w wersji 8:
![]() |
Aby napisać część [client] wersji 9, zaczniemy od części [client] wersji 8, a następnie wprowadzimy niezbędne zmiany. Duplikujemy projekt Visual Studio omówiony w paragrafie 11.9.4.1, zmieniamy jego nazwę na ClientWsImpot i wczytujemy go do programu Visual Studio:
![]() |
Rozwiązanie Visual Studio z wersji 8 składało się z 2 projektów:
- projekt [metier] [1], który był klientem TCP serwera TCP służącego do obliczania podatków
- projekt [ui] [2] zawierający interfejs graficzny.
Należy wprowadzić następujące zmiany:
- projekt [metier] musi odtąd pełnić rolę klienta serwisu internetowego
- projekt [ui] musi odwoływać się do projektu DLL z nowej warstwy [metier]
- konfiguracja warstwy [metier] w [App.config] musi ulec zmianie.
12.4.2.1. Nowa warstwa [metier]
![]() |
- w [1], IImpotMetier stanowi interfejs warstwy [metier], a ImpotMetierTcp jest jej implementacją przez klienta TCP
- w [2] usuwamy implementację ImpotMetierTcp. Musimy utworzyć kolejną implementację interfejsu IImpotMetier, która będzie klientem serwisu internetowego.
- W pliku [3] nadajemy nazwę Client domyślnej przestrzeni nazw projektu [metier]. Wygenerowany plik DLL będzie nosił nazwę [ImpotsV9-metier.dll].
![]() |
- w pliku [4] tworzymy odwołanie do serwisu internetowego WsImpot.
- w pliku [5] konfigurujemy go i zatwierdzamy.
- W pliku [6] utworzono odwołanie do usługi internetowej WsImpot oraz wygenerowano plik [app.config].
W pliku ukrytym [Reference.cs]:
- przestrzenią nazw jest Client.WsImpot
- klasa kliencka nosi nazwę ServiceImpotSoapClient
- posiada ona jedną metodę podpisywania:
public int CalculerImpot(bool marié, int nbEnfants, int salaire) ;
Pozostaje nam zaimplementować interfejs IImpotMetier:
namespace Metier {
public interface IImpotMetier {
int CalculerImpot(bool marié, int nbEnfants, int salaire);
}
}
Implementujemy go za pomocą następującej klasy ImpotMetierWs:
using System.Net.Sockets;
using System.IO;
using Client.WsImpot;
namespace Metier {
public class ImpotMetierWs : IImpotMetier {
// klient zdalnej usługi internetowej
private ServiceImpotSoapClient client = new ServiceImpotSoapClient();
// obliczanie podatku
public int CalculerImpot(bool marié, int nbEnfants, int salaire) {
return client.CalculerImpot(marié, nbEnfants, salaire);
}
}
}
- wiersz 6: klasa ImpotMetierWs implementuje interfejs IImpotMetier.
- wiersz 9: podczas tworzenia instancji klasy ImpotMetierWs pole client jest inicjowane instancją klienta serwisu internetowego służącego do obliczania podatku.
- wiersz 12: jedyna metoda interfejsu IImpotMetier, którą należy zaimplementować.
- wiersz 13: stosuje się metodę CalculerImpot klienta zdalnej usługi internetowej służącej do obliczania podatku. Ostatecznie zostanie wywołana metoda CalculerImpot tej zdalnej usługi internetowej.
Można wygenerować metodę DLL dla projektu:
![]() |
- na [1], projekt [client] w stanie końcowym
- do [2], wygenerowanie pliku DLL z projektu
- na [3], plik DLL oraz ImpotsV9-metier.dll znajdują się w folderze /bin/Release projektu.
12.4.2.2. Nowa warstwa [ui]
![]() |
Warstwa klienta o nazwie [client] została już napisana. Pozostało nam jeszcze napisać warstwę o nazwie [ui]. Wróćmy do projektu w Visual Studio:
![]() |
- w [1], projekt [ui] pochodzący z wersji 8
- do [2], DLL i ImpotsV8-metier ze starej warstwy [metier] zostają zastąpione przez DLL ImpotsV9-metier z nowej warstwy
- w [3], do referencji projektu dodano pliki DLL i ImpotsV9-metier.
Druga zmiana dotyczy pliku [App.config]. Należy pamiętać, że plik ten jest wykorzystywany przez Spring do instancjonowania warstwy [metier]. Ponieważ ta ostatnia uległa zmianie, należy odpowiednio zmodyfikować konfigurację pliku [App.config]. Z drugiej strony plik [App.config] musi zawierać konfigurację umożliwiającą połączenie z zdalną usługą internetową służącą do obliczania podatku. Konfiguracja ta została wygenerowana w pliku [app.config] projektu [metier] w momencie dodania do niego odniesienia do zdalnej usługi internetowej.
Plik [App.config] przyjmuje zatem następującą postać:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object name="metier" type="Metier.ImpotMetierWs, ImpotsV9-metier">
</object>
</objects>
</spring>
<!-- usługa internetowa -->
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="ServiceImpotSoap" closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false"
bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
</client>
</system.serviceModel>
</configuration>
- wiersze 15–18: Spring instancjonuje tylko jeden obiekt, warstwę [metier]
- wiersz 16: warstwa [metier] jest instancjonowana przez klasę [Metier.ImpotMetierWs], która znajduje się w DLL ImpotsV9-metier.
- wiersze 22–46: konfiguracja klienta zdalnej usługi internetowej. Jest to skopiowana i wklejona zawartość pliku [app.config] z projektu [metier].
Wszystko gotowe. Uruchamiamy aplikację za pomocą skrótu Ctrl-F5 (usługa internetowa musi być uruchomiona, pliki SGBD i MySQL5 muszą być uruchomione, a port podany w wierszu 42 powyżej musi być prawidłowy):
![]() |
12.5. Klient internetowy dla serwisu internetowego do obliczania podatków
Przyjrzyjmy się ponownie architekturze aplikacji klient-serwer, która została właśnie napisana:
![]() |
Warstwa [ui] przedstawiona powyżej została zaimplementowana przez graficzny klient systemu Windows. Teraz zaimplementujemy ją za pomocą interfejsu internetowego:
![]() |
Jest to istotna zmiana dla użytkowników. Obecnie nasza aplikacja klient-serwer w wersji 9 może obsługiwać wielu klientów jednocześnie. Stanowi to ulepszenie w stosunku do wersji 8, w której obsługiwała ona tylko jednego klienta na raz. Ograniczeniem jest to, że użytkownicy pragnący korzystać z internetowej usługi obliczania podatków muszą posiadać na swoim komputerze klienta dla systemu Windows, który stworzyliśmy. W tej nowej wersji, którą nazwiemy wersją 10, użytkownicy będą mogli uzyskać dostęp do internetowej usługi obliczania podatków za pomocą przeglądarki.
W powyższej architekturze:
- strona serwerowa pozostaje bez zmian. Wygląda tak samo jak w wersji 9.
- Po stronie klienta warstwa [client du service web] pozostaje bez zmian. Została ona enkapsulowana w warstwach DLL i [ImpotsV9-metier]. Będziemy ponownie wykorzystywać tę warstwę DLL.
- Ostatecznie jedyną zmianą jest zastąpienie graficznego interfejsu systemu Windows interfejsem internetowym.
Zajmiemy się nowymi zagadnieniami programowania internetowego po stronie serwera. Ponieważ celem niniejszego dokumentu nie jest nauczanie programowania internetowego, postaramy się wyjaśnić podejście, które zostanie zastosowane, nie wchodząc jednak w szczegóły. W tej sekcji pojawi się zatem element pewnej „magii”. Uważamy jednak, że warto podjąć to podejście, aby pokazać nowy przykład architektury wielowarstwowej, w której zmieniono jedną z warstw.
Architektura wersji 10 wygląda zatem następująco:
![]() |
Mamy już wszystkie warstwy, z wyjątkiem warstwy [web]. Aby lepiej zrozumieć, co zostanie zrobione, musimy dokładniej omówić architekturę klienta. Będzie ona wyglądać następująco:
![]() |
- użytkownik internetowy ma w swojej przeglądarce formularz internetowy
- formularz ten jest wysyłany na serwer internetowy 1, który przekazuje go do przetworzenia przez warstwę [web]
- warstwa [web] będzie potrzebowała usług klienta zdalnej usługi internetowej, zamkniętych w [ImpotsV9-metier.dll].
- Klient zdalnej usługi internetowej będzie komunikował się z serwerem internetowym 2, na którym hostowana jest zdalna usługa internetowa.
- Odpowiedź zdalnej usługi internetowej zostanie przekazana z powrotem do warstwy internetowej klienta, która sformatuje ją w postaci strony, którą następnie wyśle do użytkownika.
Naszym zadaniem jest zatem:
- stworzenie formularza internetowego, który użytkownik zobaczy w swojej przeglądarce
- napisanie aplikacji internetowej, która przetworzy żądanie użytkownika i wyśle mu odpowiedź w postaci nowej strony internetowej. Będzie to w rzeczywistości ta sama strona co formularz, do którego dodaliśmy kwotę podatku do zapłaty
- napisanie „kleju”, który sprawi, że wszystko to będzie działać razem.
Wszystko to zostanie zrealizowane za pomocą nowej strony internetowej utworzonej w programie Visual Web Developer:
![]() |
- [1]: wybierz opcję File / New Web Site
- [2]: wybrać typ aplikacji „ASP.NET Web Site”
- [3]: wybierz język programowania: C#
- [4]: wskazać folder, w którym ma zostać utworzony projekt
![]() |
- [5]: projekt utworzony w programie Visual Web Developer
- [Default.aspx] to strona internetowa zwana stroną domyślną. Jest to strona, która zostanie wyświetlona w przypadku wpisania adresu URL http://.../ClientAspImpot bez podania konkretnego dokumentu. To właśnie na tej stronie znajduje się formularz do obliczania podatku, który użytkownik zobaczy w swojej przeglądarce.
- [Default.aspx.cs] to klasa powiązana z tą stroną, która wygeneruje formularz wysyłany do użytkownika, a następnie przetworzy go po jego wypełnieniu i zatwierdzeniu przez użytkownika.
- [web.config] to plik konfiguracyjny aplikacji. W przeciwieństwie do poprzednich przypadków, ten plik zachowamy.
Wracając do architektury, którą musimy zbudować:
![]() |
- [1] zostanie zaimplementowany przez [Default.aspx]
- [2] zostanie zaimplementowany przez [Default.aspx.cs]
- [3] zostanie zaimplementowana przez DLL [ImpotV9-metier]
Zacznijmy od wdrożenia warstwy [3]. Składa się to z kilku etapów:
![]() |
- w [1] folder [lib] z klienta graficznego Windows w wersji 9 jest kopiowany do folderu projektu internetowego [ClientAspWsImpot]. Odbywa się to za pomocą Eksploratora Windows. Aby folder ten pojawił się w rozwiązaniu Web Developer, należy odświeżyć rozwiązanie za pomocą przycisku [2].
- Następnie należy dodać je do odwołań projektu [3,4,5]. Odwołane biblioteki DLL są automatycznie kopiowane do folderu /bin projektu [6].
Mamy teraz biblioteki DLL niezbędne do działania Springa, a warstwa kliencka zdalnej usługi internetowej jest również zaimplementowana. Jeśli kod tej usługi jest już obecny, pozostaje jeszcze jej konfiguracja. W wersji 9 była ona konfigurowana za pomocą następującego pliku [App.config]:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object name="metier" type="Metier.ImpotMetierWs, ImpotsV9-metier">
</object>
</objects>
</spring>
<!-- usługa internetowa -->
<system.serviceModel>
<bindings>
<basicHttpBinding>
...
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
</client>
</system.serviceModel>
</configuration>
Przenosimy tę konfigurację bez zmian i włączamy ją do pliku [web.config] w następujący sposób:
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="system.web.extensions"...>
...
</sectionGroup>
<!-- początek sekcji Spring -->
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
<!-- koniec sekcji Spring -->
</configSections>
<!-- początek konfiguracji Spring -->
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object name="metier" type="Metier.ImpotMetierWs, ImpotsV9-metier">
</object>
</objects>
</spring>
<!-- koniec konfiguracji Spring -->
<!-- początek konfiguracji klienta zdalnej usługi internetowej -->
<system.serviceModel>
<bindings>
<basicHttpBinding>
...
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
</client>
</system.serviceModel>
<!-- koniec konfiguracji klienta zdalnej usługi internetowej -->
<!-- inne konfiguracje już obecne w wygenerowanym pliku web.config -->
...
</configuration>
Należy zauważyć, że wiersz 37 odnosi się do portu zdalnej usługi internetowej. Port ten może ulec zmianie, ponieważ Visual Developer uruchamia usługę internetową na losowym porcie.
Wróćmy do architektury klienta internetowego, który musimy zbudować:
![]() |
- [1] zostanie zaimplementowany przez [Default.aspx]
- [2] zostanie zaimplementowany przez [Default.aspx.cs]
- [3] zostało zaimplementowane przez DLL [ImpotV9-metier]
Właśnie wdrożyliśmy warstwę [3]. Przechodzimy do interfejsu internetowego [1] zaimplementowanego przez stronę [Default.aspx]. Kliknijmy dwukrotnie na stronę [Default.aspx], aby przejść do trybu projektowania.
![]() |
Istnieją dwa sposoby tworzenia strony internetowej:
- graficznie, tak jak w [2]. Należy wtedy wybrać tryb [Design] w [1]. Pasek przycisków znajduje się na dole w pasku stanu edytora strony internetowej.
- za pomocą języka znaczników, jak w przypadku [3]. Należy wówczas wybrać tryb [Source] w [1].
Tryby [Design] i [Source] są dwukierunkowe: zmiana dokonana w trybie [Design] przekłada się na zmianę w trybie [Source] i odwrotnie. Przypomnijmy, że formularz internetowy, który ma być wyświetlany w przeglądarce, ma następujący wygląd:
![]() |
- w [1], formularz wyświetlany w przeglądarce
- w [2] – komponenty użyte do jego stworzenia
- w [3] – strona projektowa formularza. Zawiera ona następujące elementy:
- w wierszu A dwa przyciski opcji o nazwach RadioButtonOui i RadioButtonNon
- wiersz B: pole wprowadzania danych o nazwie TextBoxEnfants oraz etykieta o nazwie LabelErreurEnfants
- wiersz C: pole wprowadzania danych o nazwie TextBoxSalaire oraz etykieta o nazwie LabelErreurSalaire
- wiersz D: etykieta o nazwie LabelImpot
- w wierszu E dwa przyciski o nazwach ButtonCalculer i ButtonEffacer
Po umieszczeniu komponentu na obszarze projektowym uzyskuje się dostęp do jego właściwości:
![]() |
- w [1] – dostęp do właściwości komponentu
- w [2] – karta właściwości komponentu [LabelErreurEnfants ]
- w [3], (ID) to nazwa komponentu
- w [4] nadaliśmy czerwoną barwę znakom etykiety.
Nie wystarczy umieścić komponenty na formularzu, a następnie ustawić ich właściwości. Należy również zorganizować ich rozmieszczenie. W graficznym interfejsie systemu Windows rozmieszczenie to jest absolutne. Komponent przeciąga się w miejsce, w którym ma się znaleźć. Na stronie internetowej jest to inne, bardziej złożone, ale też bardziej zaawansowane. Ten aspekt nie zostanie tutaj omówiony.
Kod źródłowy [Default.aspx] wygenerowany na podstawie tego projektu wygląda następująco:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Calculer votre impôt</title>
</head>
<body bgcolor="#ffff99">
<h2>
Calculer votre impôt</h2>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager2" runat="server" EnablePartialRendering="true" />
<asp:UpdatePanel runat="server" ID="UpdatePanelPam">
<ContentTemplate>
<div>
</div>
<table>
<tr>
<td>
Etes-vous marié(e)
</td>
<td>
<asp:RadioButton ID="RadioButtonOui" runat="server" GroupName="statut" Text="Oui" />
<asp:RadioButton ID="RadioButtonNon" runat="server" GroupName="statut" Text="Non"
Checked="True" />
</td>
</tr>
<tr>
<td>
Nombre d'elementy podrzędne
</td>
<td>
<asp:TextBox ID="TextBoxEnfants" runat="server" Columns="3"></asp:TextBox>
</td>
<td>
<asp:Label ID="LabelErreurEnfants" runat="server" ForeColor="#FF3300"></asp:Label>
</td>
</tr>
<tr>
<td>
Salaire annuel
</td>
<td>
<asp:TextBox ID="TextBoxSalaire" runat="server" Columns="8"></asp:TextBox>
</td>
<td>
<asp:Label ID="LabelErreurSalaire" runat="server" ForeColor="#FF3300"></asp:Label>
</td>
</tr>
<tr>
<td>
Impôt à payer
</td>
<td>
<asp:Label ID="LabelImpot" runat="server" BackColor="#99CCFF"></asp:Label>
</td>
</tr>
</table>
<br />
<table>
<tr>
<td>
<asp:Button ID="ButtonCalculer" runat="server" Text="Calculer" OnClick="ButtonCalculer_Click" />
</td>
<td>
<asp:Button ID="ButtonEffacer" runat="server" Text="Effacer" OnClick="ButtonEffacer_Click" />
</td>
<td>
</td>
</tr>
</table>
</div>
</ContentTemplate>
</asp:UpdatePanel>
</form>
</body>
</html>
Elementy formularza można rozpoznać w wierszach 23, 24, 33, 36, 44, 47, 55, 63 i 66. Reszta to głównie formatowanie.
Wróćmy do architektury, którą musimy zbudować:
![]() |
- [1] zostało zaimplementowane przez [Default.aspx]
- [2] zostanie zaimplementowana przez [Default.aspx.cs]
- [3] została zaimplementowana przez DLL [ImpotV9-metier]
Warstwy [1] i [3] zostały już zaimplementowane. Pozostało nam jeszcze napisać warstwę [2], która generuje formularz, wysyła go do użytkownika, przetwarza go po otrzymaniu wypełnionego formularza, wykorzystuje warstwę [3] do obliczenia podatku, generuje stronę internetową z odpowiedzią dla użytkownika i wysyła ją z powrotem. Całą tę pracę wykonuje kod [Default.aspx.cs]:
using System;
using WsImpot;
public partial class _Default : System.Web.UI.Page
{
protected void ButtonCalculer_Click(object sender, EventArgs e)
{
...
}
protected void ButtonEffacer_Click(object sender, EventArgs e)
{
...
}
}
Jest to kod bardzo zbliżony do kodu klasycznego formularza Windows. To główna zaleta technologii ASP.NET: nie ma rozbieżności między modelem programowania Windows a modelem programowania internetowego ASP.NET. Należy po prostu zawsze pamiętać o następującym schemacie:
![]() |
Gdy w [1] użytkownik kliknie przycisk [Calculer], zostanie wykonana procedura ButtonCalculer_Click z wiersza 6 pliku [Default.aspx.cs]. Jednak w międzyczasie:
- wartości z wypełnionego formularza zostaną przesłane z przeglądarki na serwer WWW za pośrednictwem protokołu HTTP
- serwer ASP.NET przeanalizuje żądanie i przekaże je do strony [Default.aspx]
- strona [Default.aspx] zostanie zainicjowana.
- jej komponenty (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire, LabelErreurEnfants, LabelErreurSalaire, LabelImpot) zostaną zainicjowane wartościami, jakie miały w momencie pierwotnego wysłania formularza do przeglądarki za pomocą mechanizmu o nazwie „ViewState”.
- Wartości przesłane w żądaniu zostaną przypisane do odpowiednich elementów (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire). Jeśli więc użytkownik podał 2 jako liczbę dzieci, otrzymamy TextBoxEnfants.Text="2".
- Jeśli strona [Default.aspx] posiada metodę [Page_Load], zostanie ona wykonana
- metoda [ButtonCalculer_Click] z linii 6 zostanie wykonana, jeśli kliknięto przycisk [Calculer]
- metoda [ButtonEffacer_Click] z linii 10 zostanie wykonana, jeśli kliknięto przycisk [Effacer]
Między momentem, w którym użytkownik generuje zdarzenie w przeglądarce, a momentem, w którym jest ono przetwarzane w [Default.aspx.cs], zachodzi bardzo złożony proces. Jest on ukryty i podczas pisania procedur obsługi zdarzeń na stronie internetowej można traktować go tak, jakby nie istniał. Nie wolno jednak zapominać, że między zdarzeniem a jego procedurą obsługi znajduje się sieć, dlatego nie należy obsługiwać zdarzeń myszy, takich jak Mouse_Move, które powodowałyby kosztowne wymiany danych między klientem a serwerem...
Kod obsługi kliknięć przycisków [Calculer] i [Effacer] jest taki sam, jak ten, który napisalibyśmy dla klasycznej aplikacji Windows:
protected void ButtonCalculer_Click(object sender, EventArgs e)
{
// weryfikacja danych
int nbEnfants;
bool erreur = false;
if (!int.TryParse(TextBoxEnfants.Text.Trim(), out nbEnfants) || nbEnfants < 0)
{
LabelErreurEnfants.Text = "Valeur incorrecte...";
erreur = true;
}
int salaire;
if (!int.TryParse(TextBoxSalaire.Text.Trim(), out salaire) || salaire < 0)
{
LabelErreurSalaire.Text = "Valeur incorrecte...";
erreur = true;
}
// błąd?
if (erreur) return;
// usuwamy ewentualne błędy
LabelErreurEnfants.Text = "";
LabelErreurSalaire.Text = "";
// stan cywilny
bool marié = RadioButtonOui.Checked;
// obliczenie podatku
try
{
LabelImpot.Text = String.Format("{0} euros",Global.Metier.CalculerImpot(marié, nbEnfants, salaire));
}
catch (Exception ex)
{
LabelImpot.Text = ex.Message;
}
}
- aby zrozumieć ten kod, należy wiedzieć,
- że na początku wykonywania formularz [Default.aspx] ma postać taką, w jakiej został wypełniony przez użytkownika. W ten sposób pola (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire) zawierają wartości wprowadzone przez użytkownika.
- tak, że po zakończeniu jej wykonania ta sama strona [Default.aspx] zostanie ponownie wyświetlona użytkownikowi. Odbywa się to automatycznie.
Procedura ButtonCalculer_Click musi zatem na podstawie aktualnych wartości pól (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire) ustalić wartości wszystkich pól (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire, LabelErreurEnfants, LabelErreurSalaire, LabelImpot) nowej strony [Default.aspx], która zostanie zwrócona użytkownikowi.
Kod ten nie zawiera żadnych szczególnych trudności. Wyjaśnienia wymaga jedynie wiersz 27. Wykorzystuje on metodę CalculerImpot pola Global.Metier, które nie zostało jeszcze omówione. Wrócimy do tego wkrótce.
Metoda ButtonEffacer_Click wygląda następująco:
protected void ButtonEffacer_Click(object sender, EventArgs e)
{
// wyzerowanie formularza
TextBoxEnfants.Text = "";
TextBoxSalaire.Text = "";
LabelImpot.Text = "";
LabelErreurEnfants.Text = "";
LabelErreurSalaire.Text = "";
}
Wróćmy do architektury, którą musimy zbudować:
![]() |
- [1] została zaimplementowana przez [Default.aspx]
- [2] został zaimplementowany przez [Default.aspx.cs]
- [3] została zaimplementowana przez DLL [ImpotV9-metier]
Pozostaje nam jeszcze połączyć te trzy warstwy. Zasadniczo chodzi o:
- utworzenia instancji warstwy [3] podczas uruchamiania aplikacji
- umieszczenia odwołania do niej w miejscu, z którego strona internetowa [Default.aspx.cs] będzie mogła ją pobrać za każdym razem, gdy zostanie zainicjowana i otrzyma polecenie obliczenia podatku.
Nie jest to nowy problem. Wystąpił on już podczas tworzenia zdalnej usługi internetowej i został omówiony w paragrafie 12.4.1. Wiemy, że rozwiązanie polega na:
- utworzenie pliku [Global.asax] powiązanego z klasą [Global.cs]
- do instancjonowania warstwy [3] w metodzie Application_Start klasy [Global.cs]
- umieścić odwołanie do warstwy [3] w polu statycznym klasy [Global.cs], ponieważ okres życia tej klasy pokrywa się z okresem życia aplikacji.
W związku z tym nasz projekt internetowy ewoluuje w następujący sposób:
![]() |
- do [1], a plik [Global.asax]
- w [2], a powiązany z nim kod to [Global.cs]. Folder [App_Code], w którym znajduje się ten plik, nie występuje domyślnie w rozwiązaniu internetowym. Aby go utworzyć, należy użyć pliku [3].
Plik Global.asax ma następującą treść:
<%@ Application Language="C#" Inherits="WsImpot.Global"%>
Kod [Global.cs] ma następującą postać:
using System;
using Metier;
using Spring.Context.Support;
namespace WsImpot
{
public class Global : System.Web.HttpApplication
{
// warstwa biznesowa
public static IImpotMetier Metier;
// metoda wykonywana przy uruchomieniu aplikacji
private void Application_Start(object sender, EventArgs e)
{
// instancje warstw [metier] i [dao]
Metier = ContextRegistry.GetContext().GetObject("metier") as IImpotMetier;
}
}
}
- wiersz 6: klasa nosi nazwę Global i należy do przestrzeni nazw WsImpot (wiersz 4). Zatem jej pełna nazwa to WsImpot.Global i właśnie tę nazwę należy wpisać w atrybucie Inherits klasy Global.asax.
- wiersz 6: wiadomo, że klasa powiązana z Global.asax musi koniecznie pochodzić od klasy System.Web.HttpApplication.
- wiersz 12: metoda Application_Start wykonywana podczas uruchamiania aplikacji internetowej.
- wiersz 15: instancjonujemy warstwę [metier] (warstwa [3] aplikacji w trakcie tworzenia) za pomocą Springa i następującej konfiguracji w [web.config]:
<!-- obiekty Spring -->
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object name="metier" type="Metier.ImpotMetierWS, ImpotsV9-metier">
</object>
</objects>
</spring>
Klasa [Metier.ImpotMetierWS] z wiersza (g) powyżej znajduje się w pliku [ImpotsV9-metier.dll].
Odniesienie do utworzonej warstwy [metier] jest umieszczane w polu statycznym w wierszu 9. To właśnie to pole jest wykorzystywane w wierszu 27 procedury ButtonCalculer_Click:
LabelImpot.Text = String.Format("{0} euros",Global.Metier.CalculerImpot(marié, nbEnfants, salaire));
Jesteśmy gotowi do przeprowadzenia testu. Należy uruchomić SGBD MySQL5, zdalną usługę internetową, i zanotować port, na którym działa:
![]() |
Następnie należy sprawdzić, czy w pliku [web.config] klienta internetowego port zdalnej usługi internetowej jest prawidłowy:
![]() |
Po wykonaniu tych czynności klienta internetowego zdalnej usługi internetowej można uruchomić za pomocą skrótu klawiszowego Ctrl-F5:
![]() |
12.6. Klient konsolowy Java dla usługi internetowej służącej do obliczania podatku
Aby pokazać, że usługi internetowe są dostępne dla klientów napisanych w dowolnym języku, tworzymy prosty konsolowy klient Java. Architektura aplikacji klient-serwer będzie wyglądać następująco:
![]() |
- klient [1] zostanie napisany w Javie
- serwer [2] jest napisany w języku C#
Na początek zmienimy jeden szczegół w naszej usłudze internetowej służącej do obliczania podatku. Jej obecna definicja w pliku [ServiceImpot.cs] wygląda następująco:
...
public class ServiceImpot : System.Web.Services.WebService
{
[WebMethod]
public int CalculerImpot(bool marié, int nbEnfants, int salaire)
{
return Global.Metier.CalculerImpot(marié, nbEnfants, salaire);
}
}
Testy wykazały, że akcent w parametrze marié w wierszach 6 i 8 może stanowić problem w zakresie współdziałania między językami Java i C#. Przyjmiemy następującą nową definicję:
...
public class ServiceImpot : System.Web.Services.WebService
{
[WebMethod]
public int CalculerImpot(bool marie, int nbEnfants, int salaire)
{
return Global.Metier.CalculerImpot(marie, nbEnfants, salaire);
}
}
Usługa ta zostanie umieszczona w nowym projekcie Web Developer o nazwie WsImpotsSansAccents. Usługa internetowa będzie miała wówczas adres URL [/WsImpotSansAccents/ServiceImpot.asmx].

Aby napisać klienta Java, użyjemy IDE NetBeans [http://www.netbeans.org/]:
![]() |
- w [1], utworzyć nowy projekt
- w [2,3], wybierz projekt typu „Java Application” w Java.
- w [4], przejść do następnego etapu
- w [5], nadać nazwę projektowi
- w [6], wskazać folder, w którym zostanie utworzony podfolder o nazwie projektu
- w [7], nadać nazwę klasie, która będzie zawierała metodę main wykonywaną przy uruchomieniu aplikacji
- w [8], zakończyć pracę kreatora
![]() |
- w [9]: wygenerowany projekt Java
- jako [10]: kliknij prawym przyciskiem myszy na projekt, aby wygenerować klienta serwisu internetowego do obliczania podatku
![]() |
- w [11], adres URL pliku opisującego usługę internetową do obliczania podatku:
http://localhost:1089/WsImpotSansAccents/ServiceImpot.asmx?WSDL
Ten adres URL odnosi się do usługi [ServiceImpot.asmx], do której dodaje się parametr ?WSDL. Dokument znajdujący się pod tym adresem opisuje w języku XML, jakie funkcje pełni usługa [15]. Jest to standardowy element usługi internetowej.
- w [12] – pakiet (odpowiednik przestrzeni nazw w języku C#), w którym należy umieścić klasy, które zostaną wygenerowane
- w [13] należy pozostawić wartość domyślną
- w polu [14] zakończ pracę kreatora
![]() |
- W [16] zaimportowana usługa internetowa została zintegrowana z projektem Java. Obsługuje ona dwa protokoły komunikacyjne: Soap i Soap12.
- W pliku [17] znajduje się klasa [Main], w której będziemy korzystać z wygenerowanego klienta
![]() |
- w [18] wstawimy kod do metody [main]. Umieść kursor w miejscu, w którym ma zostać wstawiony kod, kliknij prawym przyciskiem myszy i wybierz opcję [19]
- w [20], należy wskazać, że chcesz wygenerować kod wywołania funkcji CalculerImpot zdalnej usługi obliczania podatku, a następnie kliknij OK.
Kod wygenerowany w [Main] jest następujący:
Wygenerowany kod pokazuje, jak wywołać funkcję CalculerImpot zdalnej usługi obliczania podatku. Jeśli porównamy to z tym, co widzieliśmy w języku C#, zmienna port w wierszu 7 jest odpowiednikiem klienta używanego w języku C#. Nie będziemy dalej komentować tego kodu. Przekształcamy go w następujący sposób:
- wiersz 1: importujemy klasę ServiceImpot, która reprezentuje klienta wygenerowanego przez kreatora.
- wiersz 6: wywołujemy metodę zdalną CalculerImpot, postępując zgodnie z procedurą wskazaną w kodzie wygenerowanym w funkcji main.
Wyniki uzyskane w konsoli podczas wykonywania (F6) są następujące:



































































