10. Usługi internetowe
10.1. Wprowadzenie
W poprzednim rozdziale przedstawiliśmy kilka aplikacji typu klient-serwer opartych na protokole TCP/IP. Ponieważ klienci i serwer wymieniają między sobą wiersze tekstu, mogą one być napisane w dowolnym języku programowania. Klient musi jedynie znać protokół komunikacji oczekiwany przez serwer. Usługi internetowe to aplikacje serwerowe oparte na protokole TCP/IP, charakteryzujące się następującymi cechami:
- Są one hostowane przez serwery internetowe, a protokołem wymiany danych między klientem a serwerem jest zatem 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ęść związaną z komunikacją sieciową i pozwala na stworzenie klienta niezależnego od tej warstwy. Jeśli warstwa ta ulegnie zmianie, klient nie wymaga modyfikacji. Jest to ogromna zaleta i prawdopodobnie główny atut usług internetowych.
- 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.
10.2. Przeglądarki i XML
Usługi internetowe wysyłają dane w formacie XML do swoich klientów. Przeglądarki mogą różnie reagować na odbiór tego strumienia danych XML. Przeglądarka Internet Explorer posiada predefiniowany arkusz stylów, który umożliwia jego wyświetlenie. Netscape Communicator nie posiada tego arkusza stylów i nie wyświetla otrzymanego kodu XML. Aby uzyskać dostęp do kodu XML, należy wyświetlić kod źródłowy otrzymanej strony. Oto przykład dla następującego kodu XML:
<?xml version="1.0" encoding="utf-8"?>
<string xmlns="st.istia.univ-angers.fr">bonjour de nouveau !</string>
Przeglądarka Internet Explorer wyświetli następującą stronę:

natomiast przeglądarka Netscape Navigator wyświetli:

Jeśli wyświetlimy kod źródłowy strony otrzymanej przez przeglądarkę Netscape, otrzymamy:

Netscape rzeczywiście otrzymał to samo co Internet Explorer, ale wyświetlił to inaczej. W dalszej części będziemy używać przeglądarki Internet Explorer do tworzenia zrzutów ekranu.
10.3. Pierwsza usługa internetowa
Zapoznamy się z usługami internetowymi na podstawie bardzo prostego przykładu, który występuje w trzech wersjach.
10.3.1. Wersja 1
W tej pierwszej wersji wykorzystamy VS.NET, który ma tę zaletę, że pozwala wygenerować szkielet usługi internetowej gotowy do natychmiastowego użycia. Gdy już zrozumiemy tę architekturę, będziemy mogli zacząć działać samodzielnie. Będzie to przedmiotem kolejnych wersji.
Korzystając z VS.NET, utwórzmy nowy projekt z opcją [Fichier/Nouveau/Projet]:

Należy zwrócić uwagę na następujące kwestie:
- typ projektu to Visual Basic (lewy panel)
- szablon projektu to usługa internetowa ASP.NET (prawy panel)
- lokalizacja jest dowolna. W tym przypadku usługa internetowa będzie hostowana przez lokalny serwer internetowy IIS. Jego adres będzie zatem http://localhost/[chemin], gdzie [chemin] należy zdefiniować. W tym miejscu wybieramy ścieżkę http://localhost/polyvbnet/demo. VS.NET utworzy wówczas folder dla tego projektu. Gdzie? Serwer IIS posiada katalog główny drzewa dokumentów internetowych, które udostępnia. Nazwijmy ten katalog główny <IISroot>. Odpowiada on adresowi http://localhost. Wynika z tego, że adres http://localhost/polyvbnet/demo będzie powiązany z folderem <IISroot>/polyvbnet/demo. <IISroot> to zazwyczaj folder \inetpub\wwwroot na dysku, na którym zainstalowano plik IIS. W naszym przykładzie jest to dysk E. Folder utworzony przez VS.NET to zatem folder e:\inetpub\wwwroot\polyvbnet\demo:

Jak zawsze, powstało zbyt wiele folderów. Nie zawsze są one przydatne. Omówimy tylko te, które są nam w danym momencie potrzebne. VS.NET utworzył projekt:

Znajdujemy tu niektóre pliki obecne w fizycznym folderze projektu. Najbardziej interesujący dla nas jest plik z rozszerzeniem asmx. Jest to rozszerzenie usług internetowych. Usługa internetowa jest zarządzana przez VS.NET jako aplikacja systemu Windows, c.a.d – aplikacja posiadająca interfejs graficzny oraz kod służący do jej obsługi. Dlatego też mamy okno projektowe:

Usługa internetowa zazwyczaj nie posiada graficznego interfejsu użytkownika. Stanowi ona obiekt, który można wywołać zdalnie. Posiada ona metody, które są wywoływane przez aplikacje. Będziemy więc traktować go jako klasyczny obiekt, z tą szczególną cechą, że można go instancjonować zdalnie przez sieć. Nie będziemy więc korzystać z okna projektowego przedstawionego przez VS.NET. Skupmy się raczej na kodzie serwisu, korzystając z opcji Widok/Kod:

Należy zwrócić uwagę na kilka kwestii:
- plik nazywa się Service1.asmx.vb, a nie Service1.asmx. Do zawartości pliku Service1.asmx wrócimy nieco później.
- widzimy okno kodu podobne do tego, które mieliśmy podczas tworzenia aplikacji dla systemu Windows za pomocą VS.NET
Kod wygenerowany przez VS.NET wygląda następująco:
Imports System.Web.Services
<System.Web.Services.WebService(Namespace := "http://tempuri.org/demo/Service1")> _
Public Class Service1
Inherits System.Web.Services.WebService
#Region „Kod wygenerowany przez narzędzie do projektowania usług internetowych”
Public Sub New()
MyBase.New()
'To wywołanie jest wymagane przez Kreator usług internetowych.
InitializeComponent()
'Dodaj swój kod inicjalizacyjny po wywołaniu InitializeComponent()
End Sub
'Wymagane przez Kreator usług internetowych
Private components As System.ComponentModel.IContainer
'REMARQUE: następująca procedura jest wymagana przez Kreator usług internetowych
'Można ją zmodyfikować za pomocą Projektanta usług internetowych.
'Nie należy jej modyfikować za pomocą edytora kodu.
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
components = New System.ComponentModel.Container()
End Sub
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
'CODEGEN: ta procedura jest wymagana przez Kreator usług internetowych
'Nie należy jej modyfikować za pomocą edytora kodu.
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
#Koniec regionu
' EXEMPLE DE SERVICE WEB
' Przykładowa usługa HelloWorld() zwraca ciąg znaków „Hello World”.
' Aby wygenerować projekt, nie należy komentować poniższych wierszy, a następnie należy zapisać i wygenerować projekt.
' Aby przetestować tę usługę internetową, upewnij się, że plik .asmx jest stroną startową
' i naciśnij F5.
'
'<WebMethod()> Public Function HelloWorld() As String
' HelloWorld = „Hello World”
' Koniec funkcji
End Class
Na początek zauważmy, że mamy tu klasę Service1, która wywodzi się z klasy WebService:
W związku z tym musimy zaimportować przestrzeń nazw System.Web.Services:
Imports System.Web.Services
Deklaracja klasy jest poprzedzona atrybutem kompilacji:
<System.Web.Services.WebService(Namespace := "http://tempuri.org/demo/Service1")> _
Public Class Service1
Inherits System.Web.Services.WebService
Atrybut System.Web.Services.WebService() wskazuje, że następująca po nim klasa jest usługą internetową. Atrybut ten przyjmuje różne parametry, w tym jeden o nazwie NameSpace. Służy on do umieszczenia usługi internetowej w przestrzeni nazw. Można sobie bowiem wyobrazić, że na świecie istnieje kilka usług internetowych o nazwie meteo. Potrzebujemy sposobu, aby je rozróżnić. Umożliwia to przestrzeń nazw. Jedna z nich może nazywać się [espacenom1].meteo, a inna [espacenom2].meteo. Mamy tu do czynienia z koncepcją analogiczną do przestrzeni nazw klas. VS.NET automatycznie wygenerowało kod, który umieściło w określonym obszarze kodu źródłowego:
Jeśli przyjrzymy się temu kodowi, zauważymy, że jest to ten sam kod, który generował projektant podczas tworzenia aplikacji dla systemu Windows. Jest to kod, który można po prostu usunąć, jeśli nie mamy interfejsu graficznego, co ma miejsce w przypadku usług internetowych.
Klasa kończy się przykładem tego, jak mogłaby wyglądać usługa internetowa:
#Koniec regionu
' EXEMPLE DE SERVICE WEB
' Przykładowa usługa HelloWorld() zwraca ciąg znaków „Hello World”.
' Aby wygenerować projekt, nie należy komentować poniższych wierszy, a następnie należy zapisać i wygenerować projekt.
' Aby przetestować tę usługę internetową, upewnij się, że plik .asmx jest stroną startową
' i naciśnij F5.
'
'<WebMethod()> Public Function HelloWorld() As String
' HelloWorld = „Hello World”
' End Function
W oparciu o powyższe uwagi porządkujemy kod, tak aby wyglądał następująco:
Imports System.Web.Services
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class Bonjour
Inherits System.Web.Services.WebService
<WebMethod()> Public Function Bonjour() As String
Return "bonjour !"
End Function
End Class
Teraz sytuacja jest nieco bardziej przejrzysta.
- usługa internetowa to klasa wywodząca się z klasy WebService
- klasa ta jest oznaczona atrybutem <System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")>. Umieszczamy więc naszą usługę w przestrzeni nazw st.istia.univ-angers.fr.
- Metody klasy są kwalifikowane przez atrybut <WebMethod()>, wskazujący, że mamy do czynienia z metodą, którą można wywołać zdalnie przez sieć
Klasa obsługująca naszą usługę internetową nazywa się zatem Bonjour i posiada jedną metodę, również o nazwie Bonjour, która zwraca ciąg znaków. Jesteśmy gotowi do pierwszego testu.
- Uruchommy serwer internetowy IIS, jeśli jeszcze tego nie zrobiliśmy
- użyjmy opcji Debuguj/Uruchom bez debugowania. VS.NET
VS.NET skompiluje wówczas całą aplikację, uruchomi przeglądarkę (często Internet Explorer, jeśli jest zainstalowany) i wyświetli adres URL http://localhost/polyvbnet/demo/Service1.asmx:

Dlaczego adres URL http://localhost/polyvbnet/demo/Service1.asmx? Ponieważ był to jedyny plik .asmx w projekcie:

Gdyby istniało kilka plików .asmx, musielibyśmy określić, który z nich ma zostać uruchomiony jako pierwszy. Można to zrobić, klikając prawym przyciskiem myszy na dany plik .asmx i wybierając opcję [Définir comme page de démarrage].

Można by się zastanawiać, co zawiera plik service1.asmx. W rzeczywistości przy pomocy pliku VS.NET pracowaliśmy nad plikiem service1.asmx.vb, a nie nad plikiem service1.asmx. Plik ten znajduje się w folderze projektu:

Otwórzmy go w edytorze tekstu (Notatnik lub innym). Otrzymujemy następującą treść:
Plik zawiera prostą dyrektywę skierowaną do serwera IIS, wskazującą,
- że mamy do czynienia z usługą internetową (słowo kluczowe WebService)
- że językiem klasy tej usługi jest Visual Basic (Language="vb")
- że kod źródłowy tej klasy znajduje się w pliku Service1.asmx.vb (Codebehind="Service1.asmx.vb")
- że klasa implementująca usługę nosi nazwę demo.Bonjour (Class="demo.Bonjour"). Warto zauważyć, że plik VS.NET umieścił klasę Bonjour w przestrzeni nazw demo, która jest jednocześnie nazwą projektu.
Wróćmy do strony dostępnej pod adresem http://localhost/polyvbnet/demo/Service1.asmx:

Kto napisał kod HTML na powyższej stronie? Nie my, to wiemy. Jest to IIS, który przedstawia usługi internetowe w standardowy sposób. Ta strona zawiera dwa linki. Kliknijmy pierwszy z nich, [Description du service]:

Ups… to raczej niezrozumiały kod XML. Zwróćmy jednak uwagę na URL
http://localhost/polyvbnet/demo/Service1.asmx?WSDL. Otwórz przeglądarkę i wpisz bezpośrednio ten adres URL. Otrzymasz to samo, co poprzednio. Należy więc pamiętać, że adres URL http://serviceweb?WSDL zapewnia dostęp do opisu XML usługi internetowej. Wróćmy do strony startowej i kliknijmy link [Bonjour]. Przypomnijmy, że „Bonjour” jest metodą serwisu internetowego. Gdyby istniało więcej metod, wszystkie zostałyby tutaj wyświetlone. Otrzymujemy następującą stronę:

Celowo skróciliśmy wyświetloną stronę, aby nie przeciążać naszej demonstracji. Zwróćmy ponownie uwagę na uzyskany adres URL:
Jeśli wpiszemy ten adres URL bezpośrednio w przeglądarce, otrzymamy to samo, co powyżej. Jesteśmy zachęcani do użycia przycisku [Appeler]. Zróbmy to. Otrzymujemy nową stronę:

Znowu mamy do czynienia z XML. Znajdujemy tam dwie informacje, które były obecne w naszej usłudze internetowej:
- przestrzeń nazw st.istia.univ-angers.fr naszej usługi
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")>
- wartość zwrócona przez metodę Bonjour:
Return "bonjour !"
Czego się nauczyliśmy?
- jak napisać usługę internetową S
- jak z niej skorzystać
Teraz zajmiemy się pisaniem serwisu internetowego bez pomocy VS.NET.
10.3.2. Wersja 2
W poprzednim przykładzie narzędzie VS.NET wykonało wiele czynności samodzielnie. Czy można stworzyć usługę internetową bez tego narzędzia? Odpowiedź brzmi: tak, i właśnie to teraz pokażemy. Za pomocą edytora tekstu tworzymy następującą usługę internetową:
Imports System.Web.Services
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class Bonjour2
Inherits System.Web.Services.WebService
<WebMethod()> Public Function getBonjour() As String
Return "bonjour de nouveau !"
End Function
End Class
Klasa nosi nazwę Bonjour2 i posiada metodę o nazwie getBonjour. Została umieszczona w pliku demo2.vb, który z kolei znajduje się w drzewie katalogów serwera IIS, w folderze E:\Inetpub\wwwroot\polyvbnet\demo2. Jest to klasyczna klasa VB.NET, którą można zatem skompilować:
dos>vbc /out:demo2 /t:library /r:system.dll /r:system.web.services.dll demo2.vb
dos>dir
02/03/2004 18:04 286 demo2.vb
02/03/2004 18:10 77 demo2.asmx
02/03/2004 18:12 3 072 demo2.dll
Umieszczamy zestaw demo2.dll w folderze o nazwie „bin” (ta nazwa jest obowiązkowa):
Teraz tworzymy plik demo2.asmx. To właśnie ten plik będzie wywoływany przez klientów internetowych. Jego zawartość jest następująca:
Spotkaliśmy się już z tą dyrektywą. Oznacza ona, że:
- klasa serwisu internetowego nosi nazwę Bonjour2 i znajduje się w zestawie demo2.dll. IIS będzie szukać tego zestawu w różnych lokalizacjach, w szczególności w folderze bin serwisu internetowego. Dlatego właśnie umieściliśmy tam zestaw demo2.dll.
Teraz możemy przeprowadzić różne testy. Upewniamy się, że IIS jest aktywny, a następnie w przeglądarce wpisujemy adres http://localhost/polyvbnet/demo2/demo2.asmx:

Następnie adres http://localhost/polyvbnet/demo2/demo2.asmx?WSDL

Następnie URL http://localhost/polyvbnet/demo2/demo2.asmx?op=getBonjour, gdzie getBonjour to nazwa jedynej metody naszej usługi internetowej:

Korzystamy z powyższego przycisku [Appeler]:

Otrzymujemy prawidłowy wynik wywołania metody getBonjour serwisu internetowego. Wiemy już, jak zbudować serwis internetowy bez vs.net. Od tej pory pominiemy sposób budowy serwisu internetowego, skupiając się wyłącznie na podstawowych plikach.
10.3.3. Wersja 3
Dwie poprzednie wersje usługi internetowej [Bonjour] wykorzystywały dwa pliki:
- plik .asmx, będący punktem wejścia do serwisu internetowego
- plik .vb, zawierający kod źródłowy usługi internetowej
W tym miejscu pokazujemy, że wystarczy sam plik .asmx. Kod usługi demo3.asmx wygląda następująco:
Zauważamy, że kod źródłowy usługi znajduje się teraz bezpośrednio w pliku źródłowym demo3.asmx. Dyrektywa
nie odwołuje się już do klasy w zewnętrznym zestawie, lecz do klasy znajdującej się w tym samym pliku źródłowym. Umieśćmy go w folderze <IISroot>\polyvbnet\demo3:

Uruchommy IIS i wpiszmy adres URL http://localhost/polyvbnet/demo3/demo3.asmx:

Zauważamy istotną różnicę w stosunku do poprzedniej wersji: nie musieliśmy kompilować kodu VB usługi. IIS sam przeprowadził tę kompilację za pomocą kompilatora VB.NET zainstalowanego na tym samym komputerze. Następnie wygenerował stronę. Jeśli wystąpi błąd kompilacji, zostanie on zgłoszony przez IIS:

10.3.4. Wersja 4
W tym miejscu skupiamy się na konfiguracji serwera IIS. Do tej pory zawsze umieszczaliśmy nasze serwisy internetowe w katalogu głównym <IISroot> serwera IIS, w tym przypadku [e:\inetpub\wwwroot]. W tym miejscu pokazujemy, że usługę internetową można umieścić w dowolnym miejscu. Odbywa się to za pomocą folderów wirtualnych serwera IIS. Umieśćmy naszą usługę w następującym folderze:

Folder [D:\data\devel\vbnet\poly\chap9\demo3] nie znajduje się w drzewie katalogów serwera IIS. Należy to wskazać serwerowi, tworząc wirtualny folder IIS. Uruchommy IIS i wybierzmy poniższą opcję [Avancé]:

Wyświetla się lista katalogów wirtualnych. Nie będziemy się nad nią rozwodzić. Tworzymy nowy katalog wirtualny za pomocą przycisku [Ajouter] powyżej:

Za pomocą przycisku [Parcourir] wskazujemy fizyczny katalog zawierający serwis internetowy, w tym przypadku katalog [D:\data\devel\vbnet\poly\chap9\demo3]. Nadajemy temu katalogowi nazwę logiczną (wirtualną): [virdemo3]. Oznacza to, że dokumenty znajdujące się w fizycznym katalogu [D:\data\devel\vbnet\poly\chap9\demo3] będą dostępne w sieci pod adresem URL [http://<machine>/virdemo3]. Powyższe okno dialogowe zawiera inne parametry, które pozostawiamy bez zmian. Zatwierdzamy okno. Nowy folder wirtualny pojawia się na liście folderów wirtualnych IIS:

Teraz otwieramy przeglądarkę i wpisujemy adres URL [http://localhost/virdemo3/demo3.asmx]. Otrzymujemy ten sam wynik co poprzednio:

10.3.5. Wnioski
Pokazaliśmy kilka sposobów tworzenia serwisu internetowego. W dalszej części będziemy korzystać z metody opisanej w wersji 3 do utworzenia serwisu oraz z metody 4 do jego lokalizacji. Nie będziemy więc potrzebować adresu VS.NET. Warto jednak zwrócić uwagę na korzyści płynące z użycia adresu VS.NET, który ułatwia debugowanie. Istnieją bezpłatne narzędzia do tworzenia aplikacji internetowych, w szczególności produkt WebMatrix sponsorowany przez Microsoft, który można znaleźć na stronie URL [http://www.asp.net/webmatrix]. Jest to doskonałe narzędzie do rozpoczęcia programowania internetowego bez konieczności wcześniejszych inwestycji.
10.4. Usługa internetowa realizująca operacje
Rozważamy serwis internetowy, który oferuje pięć funkcji:
- dodaj(a,b), która zwraca a+b
- odejmowanie(a,b), które zwraca a-b
- mnożyć(a,b), która zwraca a*b
- dziel(a,b), która zwraca a/b
- wszystko(a,b), która zwraca tablicę [a+b, a-b, a*b, a/b]
Kod VB.NET tej usługi jest następujący:
<%@ WebService language="VB" class=operations %>
imports system.web.services
<WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class operations
Inherits WebService
<WebMethod> _
Function ajouter(a As Double, b As Double) As Double
Return a + b
End Function
<WebMethod> _
Function soustraire(a As Double, b As Double) As Double
Return a - b
End Function
<WebMethod> _
Function multiplier(a As Double, b As Double) As Double
Return a * b
End Function
<WebMethod> _
Function diviser(a As Double, b As Double) As Double
Return a / b
End Function
<WebMethod> _
Function toutfaire(a As Double, b As Double) As Double()
Return New Double() {a + b, a - b, a * b, a / b}
End Function
End Class
Powtórzymy tutaj niektóre wyjaśnienia, które już podaliśmy, ale które warto przypomnieć lub uzupełnić. Klasa operations przypomina klasę VB.NET, jednak należy zwrócić uwagę na kilka kwestii:
- metody są poprzedzone atrybutem <WebMethod()>, który wskazuje kompilatorowi, które metody mają zostać „opublikowane” (c.a.d) i udostępnione klientowi. Metoda, przed którą nie umieszczono tego atrybutu, byłaby niewidoczna dla klientów zdalnych. Mogłaby to być metoda wewnętrzna wykorzystywana przez inne metody, ale nieprzeznaczona do publikacji.
- 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ść.
- Sama klasa jest poprzedzona atrybutem <WebService(Namespace="st.istia.univ-angers.fr")>, którego celem 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 jest podobnie. Każda usługa internetowa musi być identyfikowana za pomocą unikalnej nazwy, w tym przypadku st.istia.univ-angers.fr.
- Nie zdefiniowaliśmy konstruktora. Zostanie więc domyślnie użyty konstruktor klasy nadrzędnej.
Powyższy kod źródłowy nie jest przeznaczony bezpośrednio dla kompilatora VB.NET, lecz dla serwera WWW IIS. Musi on nosić rozszerzenie .asmx i zostać zapisany w drzewie katalogów serwera WWW. W tym przypadku zapisujemy go pod nazwą operations.asmx w folderze <IISroot>\polyvbnet\operations:

Do tego fizycznego folderu przypisujemy folder wirtualny IIS [operations]:
![]() | ![]() |
Wejdźmy do serwisu za pomocą przeglądarki. Plik URl, o który należy poprosić, to [http://localhost/operations/operations.asmx]:

Otrzymujemy dokument internetowy zawierający link do każdej z metod zdefiniowanych w usłudze internetowej operations. Przejdźmy do linku ajouter:

Wyświetlona strona proponuje nam przetestowanie metody ajouter poprzez przekazanie jej dwóch argumentów a i b, których potrzebuje. Przypomnijmy definicję metody ajouter:
Warto zauważyć, że strona przejęła nazwy argumentów a i b użytych w definicji metody. Korzystamy z przycisku Appeler i otrzymujemy następującą odpowiedź w osobnym oknie przeglądarki:

Jeśli w powyższym przykładzie wprowadzimy [Affichage/Source], otrzymamy następujący kod:

Powtórzmy operację dla metody [toutfaire]:

Otrzymujemy następującą stronę:

Skorzystajmy z powyższego przycisku [Appeler]:

W każdym przypadku odpowiedź serwera ma postać:
- odpowiedź ma format XML
- wiersz 1 jest standardowy i zawsze występuje w odpowiedzi
- kolejne wiersze zależą od typu wyniku (double, ArrayOfDouble), liczby wyników oraz przestrzeni nazw serwisu internetowego (w tym przypadku st.istia.univ-angers.fr).
Istnieje kilka metod wysyłania zapytania do serwisu internetowego i uzyskiwania odpowiedzi. Wróćmy do kodu odpowiedzi URL serwisu:

i kliknijmy link [ajouter]. Na wyświetlonej stronie przedstawiono dwie metody wywołania funkcji [ajouter] serwisu internetowego:



Te dwie metody dostępu do funkcji serwisu internetowego noszą odpowiednio nazwy: HTTP-POST i SOAP. Przyjrzyjmy się im teraz kolejno.
Uwaga: w pierwszych wersjach VS.NET istniała trzecia metoda o nazwie HTTP-GET. W momencie pisania niniejszego dokumentu (marzec 2004 r.) metoda ta wydaje się już niedostępna. Oznacza to, że usługa internetowa wygenerowana przez VS.NET nie akceptuje żądań GET. Nie oznacza to jednak, że nie można tworzyć usług internetowych akceptujących żądania GET, zwłaszcza przy użyciu innych narzędzi niż VS.NET lub po prostu ręcznie.
10.5. Klient HTTP-POST
Postępujemy zgodnie z metodą proponowaną przez serwis internetowy:

Omówmy to, co jest tam napisane. Przede wszystkim klient internetowy musi wysłać następujące nagłówki HTTP:
Klient internetowy wysyła żądanie POST do URL /operations/operations.asmx/dodaj zgodnie z protokołem HTTP w wersji 1.1 | |
Określa się maszynę docelową żądania. W tym przypadku jest to localhost. Nagłówek ten stał się obowiązkowy w wersji 1.1 protokołu HTTP | |
W tym miejscu określa się, że po nagłówkach HTTP zostaną wysłane dodatkowe parametry w formacie urlencoded. Format ten zastępuje niektóre znaki ich kodami szesnastkowymi. | |
Jest to liczba znaków w ciągu parametrów, który zostanie wysłany po nagłówkach HTTP. |
Po nagłówkach HTTP następuje pusty wiersz, a następnie ciąg parametrów o długości POST znaków, składający się z [Content-Length] znaków, w postaci a=XX&b=YY, gdzie XX i YY to „url-kodowane” ciągi znaków odpowiadające wartościom parametrów a i b. Wiemy już wystarczająco dużo, aby odtworzyć powyższy przykład za pomocą naszego ogólnego klienta TCP, z którego korzystaliśmy już w rozdziale poświęconym programowaniu TCP/IP:
- uruchamiamy IIS
- usługa jest dostępna pod adresem URL [http://localhost/operations/operations.asmx]
- używamy generycznego klienta TCP w oknie o nazwie DOS
dos>clttcpgenerique localhost 80
Commandes :
POST /operations/operations.asmx/ajouter HTTP/1.1
HOST: localhost
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-length: 7
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:17 GMT
<-- X-Powered-By: ASP.NET
<--
a=2&b=3
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:26 GMT
<-- X-Powered-By: ASP.NET
<-- Connection: close
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 90
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">5</double>
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Zauważmy przede wszystkim, że dodaliśmy nagłówek [Connection: close], aby poprosić serwer o zamknięcie połączenia po wysłaniu odpowiedzi. Jest to tutaj konieczne. Jeśli tego nie zrobimy, serwer domyślnie pozostawi połączenie otwarte. Jednak jego odpowiedź to ciąg wierszy tekstu, z których ostatni nie jest zakończony znakiem końca wiersza. Okazuje się, że nasz ogólny klient TCP odczytuje wiersze tekstu zakończone znakiem końca wiersza za pomocą metody ReadLine. Jeśli serwer nie zamknie połączenia po wysłaniu ostatniego wiersza, klient zostanie zablokowany, ponieważ oczekuje znaku końca wiersza, który nie nadchodzi. Jeśli serwer zamknie połączenie, metoda klienta ReadLine zakończy działanie, a klient nie zostanie zablokowany.
Natychmiast po otrzymaniu pustego wiersza sygnalizującego koniec nagłówków HTTP serwer IIS wysyła pierwszą odpowiedź:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:17 GMT
<-- X-Powered-By: ASP.NET
<--
Ta odpowiedź, składająca się wyłącznie z nagłówków HTTP, informuje klienta, że może wysłać 7 znaków, które zamierzał wysłać. Co robimy:
Należy tutaj zauważyć, że nasz klient TCP wysyła więcej niż 7 znaków, ponieważ wysyła je wraz ze znakiem końca linii (WriteLine). Nie stanowi to problemu dla serwera, który z otrzymanych znaków pobierze tylko pierwsze 7, a następnie połączenie zostanie zamknięte (Connection: close). Te nadmiarowe znaki mogłyby stanowić problem, gdyby połączenie pozostało otwarte, ponieważ wówczas zostałyby one potraktowane jako pochodzące z kolejnego polecenia klienta. Po otrzymaniu parametrów serwer wysyła swoją odpowiedź:
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:26 GMT
<-- X-Powered-By: ASP.NET
<-- Connection: close
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 90
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">5</double>
Mamy teraz wszystkie elementy potrzebne do napisania klienta dla naszej usługi internetowej. Będzie to klient konsolowy o nazwie httpPost2, z którego korzysta się w następujący sposób:
dos>httpPost2 http://localhost/operations/operations.asmx
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 6 7
--> POST /operations/operations.asmx/ajouter HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<--
--> a=6&b=7
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
[résultat=13]
soustraire 8 9
--> POST /operations/operations.asmx/soustraire HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:47 GMT
<-- X-Powered-By: ASP.NET
<--
--> a=8&b=9
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:47 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">-1</double>
[résultat=-1]
fin
dos>
Klient jest wywoływany poprzez przekazanie mu kodu URL z serwisu internetowego:
Następnie klient odczytuje polecenia wpisane z klawiatury i je wykonuje. Polecenia te mają następujący format:
gdzie „funkcja” to nazwa wywoływanej funkcji serwisu internetowego (dodawanie, odejmowanie, mnożenie, dzielenie), a „a” i „b” to wartości, na których ta funkcja będzie operować. Na przykład:
Na tej podstawie klient wysyła niezbędne żądanie HTTP do serwera internetowego i otrzymuje odpowiedź. Wymiana danych między klientem a serwerem jest wyświetlana na ekranie w celu lepszego zrozumienia procesu:
ajouter 6 7
--> POST /operations/operations.asmx/ajouter HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<--
--> a=6&b=7
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
[résultat=13]
Powyżej przedstawiono wymianę danych, z którą mieliśmy już do czynienia w przypadku generycznego klienta TCP, z jedną różnicą: nagłówek HTTP Connection: Keep-Alive nakazuje serwerowi, aby nie zamykał połączenia. Pozostaje ono zatem otwarte dla kolejnej operacji klienta, który dzięki temu nie musi ponownie łączyć się z serwerem. Wymusza to jednak na kliencie użycie innej metody niż ReadLine() do odczytania odpowiedzi serwera, ponieważ wiadomo, że jest to ciąg wierszy, z których ostatni nie kończy się znakiem końca wiersza. Po otrzymaniu całej odpowiedzi serwera klient analizuje ją w celu znalezienia wyniku żądanej operacji i wyświetlenia go:
Przyjrzyjmy się kodowi naszego klienta:
' przestrzenie nazw
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Web
' klient usługi internetowej operations
Public Module clientPOST
Public Sub Main(ByVal args() As String)
' składnia
Const syntaxe As String = "pg URI"
Dim fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' liczba argumentów
If args.Length <> 1 Then
erreur(syntaxe, 1)
End If
' odnotowuje się żądanie URI
Dim URIstring As String = args(0)
' nawiązywane jest połączenie z serwerem
Dim uri As Uri = Nothing ' l'URI du service web
Dim client As TcpClient = Nothing ' la liaison tcp du client avec le serveur
Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
Try
' połączenie z serwerem
uri = New Uri(URIstring)
client = New TcpClient(uri.Host, uri.Port)
' tworzy się strumienie wejścia-wyjścia klienta TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
Catch ex As Exception
' URI nieprawidłowy lub inny problem
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try
' tworzenie słownika funkcji serwisu internetowego
Dim dicoFonctions As New Hashtable
Dim i As Integer
For i = 0 To fonctions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
' żądania użytkownika są wprowadzane za pomocą klawiatury
' w postaci funkcja a b
' kończą się poleceniem „end”
Dim commande As String = Nothing ' commande tapée au clavier
Dim champs As String() = Nothing ' champs d'une ligne de commande
Dim fonction As String = Nothing ' nom d'une fonction du service web
Dim a, b As String ' les arguments des fonctions du service web
' monit dla użytkownika
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b")
' obsługa błędów
Dim erreurCommande As Boolean
Try
' pętla wprowadzania poleceń wpisanych z klawiatury
While True
' brak błędów na początku
erreurCommande = False
' odczyt polecenia
commande = Console.In.ReadLine().Trim().ToLower()
' koniec?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' podział polecenia na pola
champs = Regex.Split(commande, "\s+")
Try
' potrzebne są trzy pola
If champs.Length <> 3 Then
Throw New Exception
End If
' pole 0 musi być rozpoznaną funkcją
fonction = champs(0)
If Not dicoFonctions.ContainsKey(fonction) Then
Throw New Exception
End If
' pole 1 musi być prawidłową liczbą
a = champs(1)
Double.Parse(a)
' pole 2 musi być prawidłową liczbą
b = champs(2)
Double.Parse(b)
Catch
' nieprawidłowe polecenie
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
erreurCommande = True
End Try
' wysyłane jest żądanie do serwisu internetowego
If Not erreurCommande Then executeFonction([IN], OUT, uri, fonction, a, b)
End While
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
' zakończenie połączenia klient-serwer
Try
[IN].Close()
OUT.Close()
client.Close()
Catch
End Try
End Sub
...........
' wyświetlanie błędów
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' wyświetlenie błędu
System.Console.Error.WriteLine(msg)
' zakończenie z błędem
Environment.Exit(exitCode)
End Sub
End Module
Mamy tu elementy, z którymi spotkaliśmy się już kilkakrotnie i które nie wymagają szczególnych komentarzy. Przyjrzyjmy się teraz kodowi metody executeFonction, w której znajdują się nowości:
' executeFonction
Public Sub executeFonction(ByVal [IN] As StreamReader, ByVal OUT As StreamWriter, ByVal uri As Uri, ByVal fonction As String, ByVal a As String, ByVal b As String)
' wykonuje funkcję(a,b) w usłudze internetowej o adresie URI URI
' komunikacja między klientem a serwerem odbywa się za pośrednictwem strumieni IN i OUT
' wynik funkcji znajduje się w wierszu
' <double xmlns="st.istia.univ-angers.fr">double</double>
' wysłana przez serwer
' tworzenie ciągu zapytania
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
' tworzenie tablicy nagłówków HTTP do wysłania
Dim entetes(5) As String
entetes(0) = "POST " + uri.AbsolutePath + "/" + fonction + " HTTP/1.1"
entetes(1) = "Host: " & uri.Host & ":" & uri.Port
entetes(2) = "Content-Type: application/x-www-form-urlencoded"
entetes(3) = "Content-Length: " & nbChars
entetes(4) = "Connection: Keep-Alive"
entetes(5) = ""
' wysyłamy nagłówki HTTP do serwera
Dim i As Integer
For i = 0 To entetes.Length - 1
' wysyłanie do serwera
OUT.WriteLine(entetes(i))
' wyświetlenie na ekranie
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
' odczyt pierwszej odpowiedzi z serwera WWW HTTP/1.1 100
Dim ligne As String = Nothing
' wiersz strumienia odczytu
ligne = [IN].ReadLine()
While ligne <> ""
'echo
Console.Out.WriteLine(("<-- " + ligne))
' następna linia
ligne = [IN].ReadLine()
End While
'echo ostatniego wiersza
Console.Out.WriteLine(("<-- " + ligne))
' wysyłanie parametrów żądania
OUT.Write(requête)
' echo
Console.Out.WriteLine(("--> " + requête))
' tworzenie wyrażenia regularnego umożliwiającego ustalenie rozmiaru odpowiedzi XML
' w strumieniu odpowiedzi serwera WWW
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' odczyt drugiej odpowiedzi serwera WWW po wysłaniu żądania
' zapisywanie wartości wiersza Content-Length
ligne = [IN].ReadLine()
While ligne <> ""
' wyświetlenie na ekranie
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' następny wiersz
ligne = [IN].ReadLine()
End While
' wyświetlenie ostatniego wiersza
Console.Out.WriteLine("<--")
' tworzenie wyrażenia regularnego umożliwiającego odzyskanie wyniku
' w strumieniu odpowiedzi serwera WWW
Dim modèle As String = "<double xmlns=""st.istia.univ-angers.fr"">(.+?)</double>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' odczytuje się pozostałą część odpowiedzi serwera WWW
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' rozbijamy odpowiedź na wiersze tekstu
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' przeszukiwanie wierszy tekstu w poszukiwaniu wyniku
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' kontynuacja
Console.Out.WriteLine(("<-- " + lignes(i)))
' porównanie bieżącego wiersza z wzorcem
MatchRésultat = ModèleRésultat.Match(lignes(i))
' czy znaleziono?
If MatchRésultat.Success Then
' zapisujemy wynik
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' wyświetlanie wyniku
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
Po pierwsze, klient HTTP-POST wysyła swoje żądanie w formacie POST:
' tworzenie ciągu zapytania
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
' tworzenie tablicy nagłówków HTTP do wysłania
Dim entetes(5) As String
entetes(0) = "POST " + uri.AbsolutePath + "/" + fonction + " HTTP/1.1"
entetes(1) = "Host: " & uri.Host & ":" & uri.Port
entetes(2) = "Content-Type: application/x-www-form-urlencoded"
entetes(3) = "Content-Length: " & nbChars
entetes(4) = "Connection: Keep-Alive"
entetes(5) = ""
' wysyłanie nagłówków HTTP na serwer
Dim i As Integer
For i = 0 To entetes.Length - 1
' wysyłanie do serwera
OUT.WriteLine(entetes(i))
' wyświetlenie na ekranie
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
W nagłówku
należy podać rozmiar parametrów, które klient wyśle za nagłówkami HTTP:
W tym celu stosuje się następujący kod:
' tworzenie ciągu zapytania
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
Metoda HttpUtility.UrlEncode(ciąg znaków) przekształca niektóre znaki z chaîne na %n1n2, gdzie n1n2 to kod ASCII przekształconego znaku. Zmianie podlegają wszystkie znaki mające szczególne znaczenie w zapytaniu POST (spacja, znak =, znak & itp.). W tym przypadku metoda HttpUtility.UrlEncode jest zazwyczaj zbędna, ponieważ a i b to liczby, które nie zawierają żadnego z tych szczególnych znaków. Została ona tutaj wykorzystana wyłącznie jako przykład. Wymaga ona przestrzeni nazw System.Web. Po wysłaniu przez klienta nagłówków HTTP:
--> POST /operations/operations.asmx/ajouter HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
serwer odpowiada nagłówkiem HTTP 100 Continue:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:47 GMT
<-- X-Powered-By: ASP.NET
<--
Kod ogranicza się do odczytania i wyświetlenia na ekranie tej pierwszej odpowiedzi:
' odczyt pierwszej odpowiedzi z serwera WWW HTTP/1.1 100
Dim ligne As String = Nothing
' wiersz strumienia odczytu
ligne = [IN].ReadLine()
While ligne <> ""
'echo
Console.Out.WriteLine(("<-- " + ligne))
' kolejny wiersz
ligne = [IN].ReadLine()
End While
'echo ostatniego wiersza
Console.Out.WriteLine(("<-- " + ligne))
Po przeczytaniu tej pierwszej odpowiedzi klient musi wysłać swoje parametry:
Czyni to za pomocą następującego kodu:
Serwer wyśle wówczas swoją odpowiedź. Składa się ona z dwóch części:
- nagłówki HTTP zakończone pustym wierszem
- odpowiedź w formacie XML
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
W pierwszej kolejności klient odczytuje nagłówki HTTP, aby znaleźć w nich wiersz Content-Length i uzyskać rozmiar odpowiedzi XML (w tym przypadku 90). Rozmiar ten jest uzyskiwany za pomocą wyrażenia regularnego. Można było to zrobić inaczej i prawdopodobnie w bardziej efektywny sposób.
' tworzenie wyrażenia regularnego umożliwiającego ustalenie rozmiaru odpowiedzi XML
' w strumieniu odpowiedzi serwera WWW
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' odczyt drugiej odpowiedzi serwera WWW po wysłaniu żądania
' zapisywanie wartości z wiersza Content-Length
ligne = [IN].ReadLine()
While ligne <> ""
' wyświetlenie na ekranie
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' następny wiersz
ligne = [IN].ReadLine()
End While
' wyświetlenie ostatniego wiersza
Console.Out.WriteLine("<--")
Gdy znamy już długość N odpowiedzi XML, wystarczy odczytać N znaków ze strumienia IN odpowiedzi serwera. Ten ciąg N znaków jest rozkładany na wiersze tekstu na potrzeby monitorowania ekranu. Wśród tych wierszy szukamy wiersza z wynikiem:
– również za pomocą wyrażenia regularnego. Po znalezieniu wyniku jest on wyświetlany.
Koniec kodu klienta wygląda następująco:
' tworzenie wyrażenia regularnego umożliwiającego odzyskanie wyniku
' w strumieniu odpowiedzi serwera WWW
Dim modèle As String = "<double xmlns=""st.istia.univ-angers.fr"">(.+?)</double>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' odczytuje się pozostałą część odpowiedzi serwera WWW
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' podział odpowiedzi na wiersze tekstu
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' przeszukiwanie wierszy tekstu w poszukiwaniu wyniku
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' kontynuacja
Console.Out.WriteLine(("<-- " + lignes(i)))
' porównanie bieżącego wiersza z wzorcem
MatchRésultat = ModèleRésultat.Match(lignes(i))
' czy znaleziono wynik?
If MatchRésultat.Success Then
' zapisujemy wynik
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' wyświetlanie wyniku
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
10.6. Klient SOAP
W tym miejscu analizujemy drugiego klienta, który będzie korzystał z dialogu klient-serwer typu SOAP (Simple Object Access Protocol). Przedstawiono nam przykładowy dialog dla funkcji ajouter:


Żądanie klienta to żądanie POST. Zatem spotkamy się z niektórymi mechanizmami znanymi z poprzedniego klienta. Główna różnica polega na tym, że podczas gdy klient HTTP-POST wysyłał parametry a i b w postaci
, to klient SOAP wysyła je w bardziej złożonym formacie XML:
POST /operations/operations.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "st.istia.univ-angers.fr/ajouter"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>double</a>
<b>double</b>
</ajouter>
</soap:Body>
</soap:Envelope>
W odpowiedzi otrzymuje komunikat XML, który jest również bardziej złożony niż poprzednie odpowiedzi:
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouterResponse xmlns="st.istia.univ-angers.fr">
<ajouterResult>double</ajouterResult>
</ajouterResponse>
</soap:Body>
</soap:Envelope>
Mimo że żądanie i odpowiedź są bardziej złożone, chodzi tu o ten sam mechanizm HTTP, co w przypadku klienta HTTP-POST. Kod klienta SOAP można zatem wzorować na kodzie klienta HTTP-POST. Oto przykładowe wykonanie:
dos>clientsoap1 http://localhost/operations/operations.asmx
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 3 4
--> POST /operations/operations.asmx HTTP/1.1
--> Host: localhost:80
--> Content-Type: text/xml; charset=utf-8
--> Content-Length: 321
--> Connection: Keep-Alive
--> SOAPAction: "st.istia.univ-angers.fr/ajouter"
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:29 GMT
<-- X-Powered-By: ASP.NET
<--
--> <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>3</a>
<b>4</b>
</ajouter>
</soap:Body>
</soap:Envelope>
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:33 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 345
<--
<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univ-angers.fr"><ajouterResult>7</ajouterResult></ajouterResponse
></soap:Body></soap:Envelope>
[résultat=7]
Zmienia się jedynie metoda executeFonction. Klient SOAP wysyła nagłówki HTTP swojego żądania. Są one po prostu nieco bardziej złożone niż te z HTTP-POST:
ajouter 3 4
--> POST /operations/operations.asmx HTTP/1.1
--> Host: localhost:80
--> Content-Type: text/xml; charset=utf-8
--> Content-Length: 321
--> Connection: Keep-Alive
--> SOAPAction: "st.istia.univ-angers.fr/ajouter"
-->
Kod, który je generuje:
' executeFonction
Public Sub executeFonction(ByVal [IN] As StreamReader, ByVal OUT As StreamWriter, ByVal uri As Uri, ByVal fonction As String, ByVal a As String, ByVal b As String)
' wywołuje funkcję(a,b) w usłudze internetowej o adresie URI URI
' komunikacja między klientem a serwerem odbywa się za pośrednictwem strumieni IN i OUT
' wynik funkcji znajduje się w wierszu
' <double xmlns="st.istia.univ-angers.fr">double</double>
' wysłana przez serwer
' tworzenie ciągu zapytania SOAP
Dim requêteSOAP As String = "<?xml version=" + """1.0"" encoding=""utf-8""?>" + ControlChars.Lf
requêteSOAP += "<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">" + ControlChars.Lf
requêteSOAP += "<soap:Body>" + ControlChars.Lf
requêteSOAP += "<" + fonction + " xmlns=""st.istia.univ-angers.fr"">" + ControlChars.Lf
requêteSOAP += "<a>" + a + "</a>" + ControlChars.Lf
requêteSOAP += "<b>" + b + "</b>" + ControlChars.Lf
requêteSOAP += "</" + fonction + ">" + ControlChars.Lf
requêteSOAP += "</soap:Body>" + ControlChars.Lf
requêteSOAP += "</soap:Envelope>"
Dim nbCharsSOAP As Integer = requêteSOAP.Length
' tworzenie tablicy nagłówków HTTP do wysłania
Dim entetes(6) As String
entetes(0) = "POST " + uri.AbsolutePath + " HTTP/1.1"
entetes(1) = "Host: " & uri.Host & ":" & uri.Port
entetes(2) = "Content-Type: text/xml; charset=utf-8"
entetes(3) = "Content-Length: " & nbCharsSOAP
entetes(4) = "Connection: Keep-Alive"
entetes(5) = "SOAPAction: ""st.istia.univ-angers.fr/" + fonction + """"
entetes(6) = ""
' wysyłamy nagłówki HTTP do serwera
Dim i As Integer
For i = 0 To entetes.Length - 1
' wysyłanie do serwera
OUT.WriteLine(entetes(i))
' wyświetlenie na ekranie
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
Po otrzymaniu tego żądania serwer wysyła swoją pierwszą odpowiedź, którą klient wyświetla:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:29 GMT
<-- X-Powered-By: ASP.NET
<--
Kod odczytu tej pierwszej odpowiedzi jest następujący:
' odczyt pierwszej odpowiedzi z serwera WWW HTTP/1.1 100
Dim ligne As String = Nothing
' jedna linia strumienia odczytu
ligne = [IN].ReadLine()
While ligne <> ""
'echo
Console.Out.WriteLine(("<-- " + ligne))
' następny wiersz
ligne = [IN].ReadLine()
End While 'while
'echo ostatniego wiersza
Console.Out.WriteLine(("<-- " + ligne))
Klient wyśle teraz swoje parametry w formacie XML w tak zwanej kopercie SOAP:
--> <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>3</a>
<b>4</b>
</ajouter>
</soap:Body>
</soap:Envelope>
Kod:
Serwer wyśle wówczas ostateczną odpowiedź:
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:33 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 345
<--
<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univ-angers.fr"><ajouterResult>7</ajouterResult></ajouterResponse
></soap:Body></soap:Envelope>
Klient wyświetla na ekranie otrzymane nagłówki HTTP, szukając w nich wiersza Content-Length:
' tworzenie wyrażenia regularnego umożliwiającego ustalenie rozmiaru odpowiedzi XML
' w strumieniu odpowiedzi serwera WWW
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' odczyt drugiej odpowiedzi serwera WWW po wysłaniu żądania
' zapisywanie wartości wiersza Content-Length
ligne = [IN].ReadLine()
While ligne <> ""
' wyświetlenie na ekranie
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' następny wiersz
ligne = [IN].ReadLine()
End While 'while
' wyświetlenie ostatniego wiersza
Console.Out.WriteLine("<--")
Po ustaleniu rozmiaru N odpowiedzi XML klient odczytuje N znaków ze strumienia odpowiedzi serwera, dzieli pobrany ciąg znaków na wiersze tekstu w celu wyświetlenia ich na ekranie i wyszukuje w nich znacznik XML wyniku: <ajouterResult>7</ajouterResult> oraz wyświetla tę wartość:
' tworzenie wyrażenia regularnego umożliwiającego odzyskanie wyniku
' w strumieniu odpowiedzi serwera WWW
Dim modèle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' odczytuje się pozostałą część odpowiedzi serwera WWW
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' podział odpowiedzi na wiersze tekstu
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' przeszukiwanie wierszy tekstu w poszukiwaniu wyniku
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' kontynuacja
Console.Out.WriteLine(("<-- " + lignes(i)))
' porównanie bieżącego wiersza z wzorcem
MatchRésultat = ModèleRésultat.Match(lignes(i))
' czy znaleziono wynik?
If MatchRésultat.Success Then
' zapisujemy wynik
strRésultat = MatchRésultat.Groups(1).Value
End If
'następny wiersz
Next i
' wyświetlanie wyniku
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
10.7. Kapsułkowanie komunikacji klient-serwer
Załóżmy, że z naszej usługi internetowej operations korzystają różne aplikacje. Interesujące byłoby udostępnienie im klasy, która pełniłaby rolę interfejsu między aplikacją kliencką a usługą internetową oraz ukrywałaby większość komunikacji sieciowej, która dla większości programistów nie jest sprawą trywialną. Mielibyśmy wówczas następujący schemat:
![]() |
Aplikacja kliencka zwracałaby się do interfejsu klient-serwer w celu wysłania żądań do serwisu internetowego. Interfejs ten przeprowadzałby całą niezbędną komunikację sieciową z serwerem i zwracałby uzyskany wynik do aplikacji klienckiej. Aplikacja kliencka nie musiałaby już zajmować się komunikacją z serwerem, co znacznie ułatwiłoby jej tworzenie.
10.7.1. Klasa enkapsulacyjna
Po omówieniu powyższych zagadnień dobrze znamy już komunikację sieciową między klientem a serwerem. Zapoznaliśmy się nawet z trzema metodami. Postanawiamy enkapsulować metodę SOAP. Klasa wygląda następująco:
' przestrzenie nazw
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports System.Web
Imports Microsoft.VisualBasic
' clientSOAP z serwisu Web operations
Public Class clientSOAP
' zmienne instancji
Private uri As uri = Nothing ' l'URI du service web
Private client As TcpClient = Nothing ' la liaison tcp du client avec le serveur
Private [IN] As StreamReader = Nothing ' le flux de lecture du client
Private OUT As StreamWriter = Nothing ' le flux d'écriture du client
' słownik funkcji
Private dicoFonctions As New Hashtable
' lista funkcji
Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' tryb szczegółowy
Private verbose As Boolean = False ' à vrai, affiche à l'écran les échanges client-serveur
' konstruktor
Public Sub New(ByVal uriString As String, ByVal verbose As Boolean)
' tryb szczegółowy
Me.verbose = verbose
' połączenie z serwerem
uri = New Uri(uriString)
client = New TcpClient(uri.Host, uri.Port)
' tworzenie strumieni wejścia-wyjścia klienta TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' tworzenie słownika funkcji serwisu internetowego
Dim i As Integer
For i = 0 To fonctions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
End Sub
' zamknięcie połączenia z serwerem
Public Sub Close()
' zakończenie połączenia klient-serwer
[IN].Close()
OUT.Close()
client.Close()
End Sub
' executeFonction
Public Function executeFonction(ByVal fonction As String, ByVal a As String, ByVal b As String) As String
' wykonuje funkcję(a,b) w usłudze internetowej o adresie URI URI
' komunikacja między klientem a serwerem odbywa się za pośrednictwem strumieni IN i OUT
' wynik funkcji znajduje się w wierszu
' <double xmlns="st.istia.univ-angers.fr">double</double>
' wysłana przez serwer
' czy funkcja jest poprawna?
fonction = fonction.Trim().ToLower()
If Not dicoFonctions.ContainsKey(fonction) Then
Return "[fonction [" + fonction + "] indisponible : (ajouter, soustraire,multiplier,diviser)]"
End If
' czy argumenty a i b są prawidłowe?
Dim doubleA As Double = 0
Try
doubleA = Double.Parse(a)
Catch
Return "[argument [" + a + "] incorrect (double)]"
End Try
Dim doubleB As Double = 0
Try
doubleB = Double.Parse(b)
Catch
Return "[argument [" + b + "] incorrect (double)]"
End Try
' dzielenie przez zero?
If fonction = "diviser" And doubleB = 0 Then
Return "[division par zéro]"
End If
' tworzenie ciągu zapytania SOAP
Dim requêteSOAP As String = "<?xml version=" + """1.0"" encoding=""utf-8""?>" + ControlChars.Lf
requêteSOAP += "<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">" + ControlChars.Lf
requêteSOAP += "<soap:Body>" + ControlChars.Lf
requêteSOAP += "<" + fonction + " xmlns=""st.istia.univ-angers.fr"">" + ControlChars.Lf
requêteSOAP += "<a>" + a + "</a>" + ControlChars.Lf
requêteSOAP += "<b>" + b + "</b>" + ControlChars.Lf
requêteSOAP += "</" + fonction + ">" + ControlChars.Lf
requêteSOAP += "</soap:Body>" + ControlChars.Lf
requêteSOAP += "</soap:Envelope>"
Dim nbCharsSOAP As Integer = requêteSOAP.Length
' tworzenie tablicy nagłówków HTTP do wysłania
Dim entetes(6) As String
entetes(0) = "POST " + uri.AbsolutePath + " HTTP/1.1"
entetes(1) = "Host: " + uri.Host + ":" + uri.Port.ToString
entetes(2) = "Content-Type: text/xml; charset=utf-8"
entetes(3) = "Content-Length: " + nbCharsSOAP.ToString
entetes(4) = "Connection: Keep-Alive"
entetes(5) = "SOAPAction: ""st.istia.univ-angers.fr/" + fonction + """"
entetes(6) = ""
' wysyłamy nagłówki HTTP na serwer
Dim i As Integer
For i = 0 To entetes.Length - 1
' wysyłanie do serwera
OUT.WriteLine(entetes(i))
' wyświetlenie na ekranie
If verbose Then
Console.Out.WriteLine(("--> " + entetes(i)))
End If
Next i
' odczyt pierwszej odpowiedzi z serwera WWW HTTP/1.1 100
Dim ligne As String = Nothing
' jedna linia strumienia odczytu
ligne = [IN].ReadLine()
While ligne <> ""
'echo
If verbose Then
Console.Out.WriteLine(("<-- " + ligne))
End If
' następny wiersz
ligne = [IN].ReadLine()
End While
'echo ostatniego wiersza
If verbose Then
Console.Out.WriteLine(("<-- " + ligne))
End If
' wysyłanie parametrów żądania
OUT.Write(requêteSOAP)
' echo
If verbose Then
Console.Out.WriteLine(("--> " + requêteSOAP))
End If
' tworzenie wyrażenia regularnego umożliwiającego ustalenie rozmiaru odpowiedzi XML
' w strumieniu odpowiedzi serwera WWW
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' odczyt drugiej odpowiedzi serwera WWW po wysłaniu żądania
' zapisywanie wartości z wiersza Content-Length
ligne = [IN].ReadLine()
While ligne <> ""
' wyświetlenie na ekranie
If verbose Then
Console.Out.WriteLine(("<-- " + ligne))
End If
' Content-Length?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' następny wiersz
ligne = [IN].ReadLine()
End While
' wyświetlenie ostatniego wiersza
If verbose Then
Console.Out.WriteLine("<--")
End If
' tworzenie wyrażenia regularnego umożliwiającego odnalezienie wyniku
' w strumieniu odpowiedzi serwera WWW
Dim modèle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' odczytuje się pozostałą część odpowiedzi serwera WWW
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' podział odpowiedzi na wiersze tekstu
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' przeszukuje się wiersze tekstu w poszukiwaniu wyniku
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' kontynuacja
If verbose Then
Console.Out.WriteLine(("<-- " + lignes(i)))
End If ' comparaison ligne courante au modèle
MatchRésultat = ModèleRésultat.Match(lignes(i))
' czy znaleziono wynik?
If MatchRésultat.Success Then
' zapisujemy wynik
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' zwracamy wynik
Return strRésultat
End Function
End Class
Nie ma tu nic nowego w porównaniu z tym, co już widzieliśmy. Po prostu wykorzystaliśmy kod klienta SOAP, który wcześniej analizowaliśmy, i nieco go przerobiliśmy, aby stworzyć klasę. Klasa ta posiada konstruktor oraz dwie metody:
' konstruktor
Public Sub New(ByVal uriString As String, ByVal verbose As Boolean)
' executeFonction
Public Function executeFonction(ByVal fonction As String, ByVal a As String, ByVal b As String) As String
' zamknięcie połączenia z serwerem
Public Sub Close()
i posiada następujące atrybuty:
' zmienne instancji
Private uri As Uri = Nothing ' l'URI du service web
Private client As TcpClient = Nothing ' la liaison tcp du client avec le serveur
Private [IN] As StreamReader = Nothing ' le flux de lecture du client
Private OUT As StreamWriter = Nothing ' le flux d'écriture du client
' słownik funkcji
Private dicoFonctions As New Hashtable
' lista funkcji
Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' szczegółowe
Private verbose As Boolean = False ' à vrai, affiche à l'écran les échanges client-serveur
Do konstruktora przekazujemy dwa parametry:
- identyfikator URI usługi internetowej, z którą ma się połączyć
- wartość logiczną verbose, która w rzeczywistości określa, czy operacje sieciowe mają być wyświetlane na ekranie; w przeciwnym razie nie będą one wyświetlane.
W trakcie tworzenia obiektu generowane są strumienie IN do odczytu sieciowego, OUT do zapisu sieciowego, a także słownik funkcji obsługiwanych przez usługę. Po utworzeniu obiektu otwierane jest połączenie klient-serwer, a jego strumienie IN i OUT stają się dostępne.
Metoda Close pozwala zamknąć połączenie z serwerem.
Metoda ExecuteFonction jest tą, którą napisaliśmy dla analizowanego klienta SOAP, z kilkoma drobnymi różnicami:
- Parametry uri, IN i OUT, które wcześniej były przekazywane jako parametry do metody, nie muszą już być przekazywane, ponieważ są one teraz atrybutami instancji dostępnymi dla wszystkich metod tej instancji
- Metoda ExecuteFonction, która wcześniej zwracała typ void i wyświetlała wynik funkcji na ekranie, teraz zwraca ten wynik, a zatem typ string.
Zazwyczaj klient będzie korzystał z klasy clientSOAP w następujący sposób:
- utworzenie obiektu clientSOAP, który nawiąże połączenie z serwisem internetowym
- wielokrotne wywołanie metody executeFonction
- zakończenie połączenia z serwisem internetowym za pomocą metody Close.
Przyjrzyjmy się pierwszemu klientowi.
10.7.2. Klient konsolowy
W tym miejscu powracamy do klienta SOAP, który analizowaliśmy, gdy klasa clientSOAP jeszcze nie istniała, i przerabiamy go tak, aby teraz korzystał z tej klasy:
' przestrzenie nazw
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic
Public Module testClientSoap
' żądanie URI od serwisu internetowego operations
' interaktywne wykonywanie poleceń wpisanych z klawiatury
Public Sub Main(ByVal args() As String)
' składnia
Const syntaxe As String = "pg URI [verbose]"
' liczba argumentów
If args.Length <> 1 And args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' szczegółowe wyświetlanie?
Dim verbose As Boolean = False
If args.Length = 2 Then
verbose = args(1).ToLower() = "verbose"
End If
' nawiązywanie połączenia z usługą internetową
Dim client As clientSOAP = Nothing
Try
client = New clientSOAP(args(0), verbose)
Catch ex As Exception
' błąd połączenia
erreur("L'erreur suivante s'est produite lors de la connexion au service web : " + ex.Message, 2)
End Try
' żądania użytkownika są wprowadzane za pomocą klawiatury
' w postaci funkcji a b – kończą się poleceniem „end”
Dim commande As String = Nothing ' commande tapée au clavier
Dim champs As String() = Nothing ' champs d'une ligne de commande
' prompt użytkownika
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b" + ControlChars.Lf)
' obsługa błędów
Dim erreurCommande As Boolean
Try
' pętla wprowadzania poleceń wpisywanych za pomocą klawiatury
While True
' na początku brak błędu
erreurCommande = False
' odczyt polecenia
commande = Console.In.ReadLine().Trim().ToLower()
' koniec?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' podział polecenia na pola
champs = Regex.Split(commande, "\s+")
' potrzebne są trzy pola
If champs.Length <> 3 Then
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
' odnotowano błąd
erreurCommande = True
End If
' wysyłamy zapytanie do serwisu internetowego
If Not erreurCommande Then Console.Out.WriteLine(("résultat=" + client.executeFonction(champs(0).Trim().ToLower(), champs(1).Trim(), champs(2).Trim())))
' kolejne żądanie
End While
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
' zakończenie połączenia klient-serwer
Try
client.Close()
Catch
End Try
End Sub
' wyświetlenie błędów
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' wyświetlenie błędu
System.Console.Error.WriteLine(msg)
' zakończenie z błędem
Environment.Exit(exitCode)
End Sub
End Module
Klient jest teraz znacznie prostszy i nie zawiera żadnej komunikacji sieciowej. Klient przyjmuje dwa parametry:
- identyfikator URI usługi internetowej operations
- opcjonalny parametr verbose. Jeśli zostanie podany, komunikacja sieciowa będzie wyświetlana na ekranie.
Te dwa parametry służą do utworzenia obiektu clientSOAP, który zapewni komunikację z usługą internetową.
' nawiązywanie połączenia z usługą internetową
Dim client As clientSOAP = Nothing
Try
client = New clientSOAP(args(0), verbose)
Catch ex As Exception
' błąd połączenia
erreur("L'erreur suivante s'est produite lors de la connexion au service web : " + ex.Message, 2)
End Try
Po nawiązaniu połączenia z serwisem internetowym klient może wysyłać swoje żądania. Są one wprowadzane za pomocą klawiatury, analizowane, a następnie wysyłane do serwera poprzez wywołanie metody executeFonction obiektu clientSOAP.
' wysyłanie żądania do serwisu internetowego
If Not erreurCommande Then Console.Out.WriteLine(("résultat=" + client.executeFonction(champs(0).Trim().ToLower(), champs(1).Trim(), champs(2).Trim())))
Klasa clientSOAP jest skompilowana w „zestawie”:
dos>vbc /r:clientSOAP.dll testClientSOAP.vb
dos>dir
04/03/2004 08:46 6 913 clientSOAP.vb
04/03/2004 09:07 7 168 clientSOAP.dll
Następnie aplikacja kliencka testClientSoap jest kompilowana przez:
dos>vbc /r:clientSOAP.dll /r:system.dll testClientSOAP.vb
dos>dir
04/03/2004 09:08 2 711 testClientSOAP.vb
04/03/2004 09:08 4 608 testClientSOAP.exe
Oto przykładowe wykonanie bez komentarzy:
dos>testclientsoap http://localhost/st/operations/operations.asmx
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 1 3
résultat=4
soustraire 6 7
résultat=-1
multiplier 4 5
résultat=20
diviser 1 2
résultat=0.5
x
syntaxe : [ajouter|soustraire|multiplier|diviser] a b
x 1 2
résultat=[fonction [x] indisponible : (ajouter, soustraire,multiplier,diviser)]
ajouter a b
résultat=[argument [a] incorrect (double)]
ajouter 1 b
résultat=[argument [b] incorrect (double)]
diviser 1 0
résultat=[division par zéro]
fin
Można śledzić operacje sieciowe, żądając trybu „szczegółowego”:
dos>testClientSOAP http://localhost/operations/operations.asmx tryb szczegółowy
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 4 8
--> POST /operations/operations.asmx HTTP/1.1
--> Host: localhost:80
--> Content-Type: text/xml; charset=utf-8
--> Content-Length: 321
--> Connection: Keep-Alive
--> SOAPAction: "st.istia.univ-angers.fr/ajouter"
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 08:15:25 GMT
<-- X-Powered-By: ASP.NET
<--
--> <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>4</a>
<b>8</b>
</ajouter>
</soap:Body>
</soap:Envelope>
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 08:15:25 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 346
<--
<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univ-angers.fr"><ajouterResult>12</ajouterResult></ajouterResponse></soap:Body></soap:Envelope>
résultat=12
fin
Zbudujmy teraz klienta graficznego.
10.7.3. Graficzny klient dla systemu Windows
Teraz będziemy wysyłać zapytania do naszej usługi internetowej za pomocą klienta graficznego, który również będzie korzystał z klasy clientSOAP. Interfejs graficzny będzie wyglądał następująco:
![]() |
Elementy interfejsu to:
nr | typ | nazwa | rola |
TextBox | txtURI | URI z serwisu web operations | |
Przycisk | btnOuvrir | otwiera połączenie z serwisem internetowym | |
Przycisk | btnFermer | zamyka połączenie z serwisem internetowym | |
ComboBox | cmbFonctions | lista funkcji (dodawanie, odejmowanie, mnożenie, dzielenie) | |
TextBox | txtA | argumenty funkcji | |
TextBox | txtB | argument b funkcji | |
TextBox | txtRésultat | wynik funkcji(a,b) | |
Przycisk | btnCalculer | uruchamia obliczenia funkcji(a,b) | |
TextBox | txtErreur | wyświetla komunikat o stanie połączenia |
Istnieje kilka ograniczeń dotyczących działania:
- przycisk btnOuvrir jest aktywny tylko wtedy, gdy pole txtURI nie jest puste i nie ma już otwartego połączenia
- przycisk btnFermer jest aktywny tylko wtedy, gdy otwarto połączenie z serwisem internetowym
- przycisk btnCalculer jest aktywny tylko wtedy, gdy połączenie jest otwarte, a pola txtA i txtB nie są puste
- pola txtRésultat i txtErreur mają atrybut ReadOnly ustawiony na wartość „prawda”
Klient rozpoczyna od nawiązania połączenia z serwisem internetowym za pomocą przycisku [Ouvrir]:

Następnie użytkownik może wybrać funkcję oraz wartości dla a i b:





Poniżej znajduje się kod aplikacji. Pominęliśmy kod formularza, który nie ma tutaj znaczenia.
'przestrzenie nazw
Imports System
Imports System.Windows.Forms
' klasa formularza
Public Class FormClientSOAP
Inherits System.Windows.Forms.Form
' atrybuty instancji
Dim client As clientSOAP ' client SOAP du service web operations
#Region „Kod wygenerowany przez Windows Form Designer”
Public Sub New()
MyBase.New()
'To wywołanie jest wymagane przez projektant Windows Form.
InitializeComponent()
' inne inicjalizacje
myInit()
End Sub
'Zastąpiona metoda Dispose formularza służy do wyczyszczenia listy komponentów.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
....
End Sub
...
Private Sub InitializeComponent()
....
End Sub
#Koniec regionu
Private Sub myInit()
' inicjalizacja formularza
cmbFonctions.SelectedIndex = 0
btnOuvrir.Enabled = False
btnFermer.Enabled = True
btnCalculer.Enabled = False
End Sub
Private Sub txtURI_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtURI.TextChanged
' Zmieniła się zawartość pola wprowadzania danych – ustalamy stan przycisku „Otwórz”
btnOuvrir.Enabled = txtURI.Text.Trim <> ""
End Sub
Private Sub btnOuvrir_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnOuvrir.Click
' żądanie nawiązania połączenia z serwisem internetowym
Try
' utworzenie obiektu typu [clientSOAP]
client = New clientSOAP(txtURI.Text.Trim, False)
' stany przycisków
btnOuvrir.Enabled = False
btnFermer.Enabled = True
' URI nie może być już modyfikowany
txtURI.ReadOnly = True
' status klienta
txtErreur.Text = "Liaison au service web ouverte"
Catch ex As Exception
' wystąpił błąd – wyświetlany jest komunikat
txtErreur.Text = ex.Message
End Try
End Sub
Private Sub btnFermer_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnFermer.Click
' zamknięcie połączenia z serwisem internetowym
client.Close()
' stany przycisków
btnOuvrir.Enabled = True
btnFermer.Enabled = False
' URI
txtURI.ReadOnly = False
' stan klienta
txtErreur.Text = "Liaison au service web fermée"
End Sub
Private Sub btnCalculer_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
' obliczanie funkcji f(a,b)
' kasowanie poprzedniego wyniku
txtRésultat.Text = ""
Try
txtRésultat.Text = client.executeFonction(cmbFonctions.Text, txtA.Text.Trim, txtB.Text.Trim)
Catch ex As Exception
' wystąpił błąd sieciowy
txtErreur.Text = ex.Message
' zamykanie połączenia
btnFermer_Click(Nothing, Nothing)
End Try
End Sub
Private Sub txtA_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtA.TextChanged
' zmiana wartości A
btnCalculer.Enabled = txtA.Text.Trim <> "" And txtB.Text.Trim <> ""
End Sub
Private Sub txtB_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtB.TextChanged
' zmiana wartości B
txtA_TextChanged(Nothing, Nothing)
End Sub
' metoda główna
Public Shared Sub main()
Application.Run(New FormClientSOAP)
End Sub
End Class
Również w tym przypadku klasa clientSOAP ukrywa całą część sieciową aplikacji. Aplikacja została zbudowana w następujący sposób:
- moduł clientSOAP.dll zawierający klasę clientSOAP został umieszczony w folderze projektu
- interfejs graficzny clientsoapgui.vb został skompilowany przy użyciu pliku VS.NET, a następnie skompilowany w oknie DOS:
dos>vbc /r:system.dll /r:system.windows.forms.dll /r:system.drawing.dll /r:clientSOAP.dll clientsoapgui.vb
dos>dir
04/03/2004 09:13 7 168 clientSOAP.dll
04/03/2004 16:44 9 866 clientsoapgui.vb
04/03/2004 16:44 11 264 clientsoapgui.exe
Następnie uruchomiono interfejs graficzny za pomocą polecenia:
10.8. Klient proxy
Przypomnijmy, co właśnie zrobiliśmy. Utworzyliśmy klasę pośredniczącą, która hermetyzuje komunikację sieciową między klientem a serwisem internetowym zgodnie z poniższym schematem:
![]() |
Platforma .NET rozwija tę logikę jeszcze dalej. Gdy znamy już serwis internetowy, do którego chcemy się połączyć, możemy automatycznie wygenerować klasę, która posłuży nam jako pośrednik w dostępie do funkcji serwisu internetowego i która ukryje całą część sieciową. Klasę tę nazywamy proxy dla serwisu internetowego, dla którego została wygenerowana.
Jak wygenerować klasę proxy dla serwisu internetowego? Do każdego serwisu internetowego dołączony jest plik opisu w formacie XML. Jeśli plik URI naszego serwisu internetowego „operations” to http://localhost/operations/operations.asmx, to jej plik opisowy jest dostępny pod adresem http://localhost/operations/operations.asmx?wsdl, jak pokazuje poniższy zrzut ekranu:

Mamy tu plik XML, który dokładnie opisuje wszystkie funkcje serwisu internetowego, podając dla każdej z nich typ i liczbę parametrów oraz typ wyniku. Plik ten nazywamy plikiem WSDL usługi, ponieważ wykorzystuje on język WSDL (Web Services Description Language). Na podstawie tego pliku można wygenerować klasę proxy za pomocą narzędzia wsdl:
dos>wsdl http://localhost/operations/operations.asmx?wsdl /language=vb
Microsoft (R) Web Services Description Language Utility
[Microsoft (R) .NET Framework, Version 1.1.4322.573]
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Écriture du fichier 'D:\data\devel\vbnet\poly\chap9\clientproxy\operations.vb'.
dos>dir
04/03/2004 17:17 6 663 operations.vb
Narzędzie wsdl generuje plik źródłowy VB.NET (opcja /language=vb) o nazwie odpowiadającej nazwie klasy implementującej usługę internetową, w tym przypadku operations. Przyjrzyjmy się fragmentowi wygenerowanego kodu:
'------------------------------------------------------------------------------
' <autogenerowane>
' Ten kod został wygenerowany przez narzędzie.
' Wersja środowiska uruchomieniowego: 1.1.4322.573
'
' Zmiany w tym pliku mogą spowodować nieprawidłowe działanie i zostaną utracone, jeśli
' kod zostanie wygenerowany ponownie.
' </autogenerated>
'------------------------------------------------------------------------------
Option Strict Off
Option Explicit On
Imports System
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.Xml.Serialization
'
'Ten kod źródłowy został automatycznie wygenerowany przez wsdl, Wersja=1.1.4322.573.
'
'<remarks/>
<System.Diagnostics.DebuggerStepThroughAttribute(), _
System.ComponentModel.DesignerCategoryAttribute("code"), _
System.Web.Services.WebServiceBindingAttribute(Name:="operationsSoap", [Namespace]:="st.istia.univ-angers.fr")> _
Public Class operations
Inherits System.Web.Services.Protocols.SoapHttpClientProtocol
'<uwagi/>
Public Sub New()
MyBase.New
Me.Url = "http://localhost/operations/operations.asmx"
End Sub
'<uwagi/>
<System.Web.Services.Protocols.SoapDocumentMethodAttribute("st.istia.univ-angers.fr/ajouter", RequestNamespace:="st.istia.univ-angers.fr", ResponseNamespace:="st.istia.univ-angers.fr", Use:=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle:=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)> _
Public Function ajouter(ByVal a As Double, ByVal b As Double) As Double
Dim results() As Object = Me.Invoke("ajouter", New Object() {a, b})
Return CType(results(0),Double)
End Function
'<uwagi/>
Public Function Beginajouter(ByVal a As Double, ByVal b As Double, ByVal callback As System.AsyncCallback, ByVal asyncState As Object) As System.IAsyncResult
Return Me.BeginInvoke("ajouter", New Object() {a, b}, callback, asyncState)
End Function
'<uwagi/>
Public Function Endajouter(ByVal asyncResult As System.IAsyncResult) As Double
Dim results() As Object = Me.EndInvoke(asyncResult)
Return CType(results(0),Double)
End Function
....
Na pierwszy rzut oka ten kod wydaje się nieco skomplikowany. Nie musimy jednak rozumieć wszystkich szczegółów, aby móc z niego korzystać. Przyjrzyjmy się najpierw deklaracji klasy:
Klasa nosi nazwę operations, pochodzącą od serwisu internetowego, dla którego została stworzona. Wywodzi się ona z klasy SoapHttpClientProtocol:

Nasza klasa proxy posiada jeden konstruktor:
Konstruktor przypisuje do atrybutu url wartość URL z serwisu internetowego powiązanego z proxy. Powyższa klasa operations sama nie definiuje atrybutu url. Atrybut ten jest dziedziczony z klasy, od której pochodzi proxy: System.Web.Services.Protocols.SoapHttpClientProtocol. Przyjrzyjmy się teraz temu, co odnosi się do metody ajouter:
Public Function ajouter(ByVal a As Double, ByVal b As Double) As Double
Dim results() As Object = Me.Invoke("ajouter", New Object() {a, b})
Return CType(results(0),Double)
End Function
Można zauważyć, że ma ona ten sam podpis, co w usłudze internetowej operations, gdzie została zdefiniowana w następujący sposób:
<WebMethod> _
Function ajouter(a As Double, b As Double) As Double
Return a + b
End Function 'ajouter
Sposób, w jaki ta klasa komunikuje się z usługą internetową, nie jest tutaj przedstawiony. Komunikacja ta jest w całości obsługiwana przez klasę nadrzędną System.Web.Services.Protocols.SoapHttpClientProtocol. W proxy znajduje się jedynie to, co odróżnia ją od innych proxy:
- URL powiązanej usługi internetowej
- definicja metod powiązanej usługi.
Aby korzystać z metod serwisu internetowego operations, klient potrzebuje jedynie wygenerowanej wcześniej klasy proxy operations. Skompilujmy tę klasę do pliku assembly:
Teraz napiszmy program konsolowy. Jest on wywoływany bez argumentów i wykonuje polecenia wpisywane z klawiatury:
dos>testclientproxy
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser|toutfaire] a b
ajouter 4 5
résultat=9
soustraire 9 8
résultat=1
multiplier 10 4
résultat=40
diviser 6 7
résultat=0,857142857142857
toutfaire 10 20
résultats=[30,-10,200,0,5]
diviser 5 0
résultat=+Infini
fin
Kod klienta brzmi następująco:
' przestrzenie nazw
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
Public Module testClientProxy
' interaktywnie wykonuje polecenia wpisane z klawiatury
' i wysyła je do serwisu internetowego operations
Public Sub Main()
' nie ma już argumentów – identyfikator URL usługi internetowej jest zakodowany na stałe w proxy
' tworzenie słownika funkcji serwisu internetowego
Dim fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser", "toutfaire"}
Dim dicoFonctions As New Hashtable
Dim i As Integer
For i = 0 To fonctions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
' tworzy się obiekt proxy operacji
Dim myOperations As operations = Nothing
Try
myOperations = New operations
Catch ex As Exception
' błąd połączenia
erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2)
End Try
' żądania użytkownika są wpisywane za pomocą klawiatury
' w postaci funkcja a b – kończą się poleceniem „end”
Dim commande As String = Nothing ' commande tapée au clavier
Dim champs As String() = Nothing ' champs d'une ligne de commande
' prompt dla użytkownika
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser|toutfaire] a b" + ControlChars.Lf)
' niektóre dane lokalne
Dim erreurCommande As Boolean
Dim fonction As String
Dim a, b As Double
' pętla wprowadzania poleceń wpisywanych za pomocą klawiatury
While True
' na początku brak błędów
erreurCommande = False
' odczyt polecenia
commande = Console.In.ReadLine().Trim().ToLower()
' koniec?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' podział polecenia na pola
champs = Regex.Split(commande, "\s+")
Try
' potrzebne są trzy pola
If champs.Length <> 3 Then
Throw New Exception
End If
' pole 0 musi być rozpoznawalną funkcją
fonction = champs(0)
If Not dicoFonctions.ContainsKey(fonction) Then
Throw New Exception
End If
' pole 1 musi być prawidłową liczbą
a = Double.Parse(champs(1))
' pole 2 musi być prawidłową liczbą
b = Double.Parse(champs(2))
Catch
' nieprawidłowe polecenie
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
erreurCommande = True
End Try
' wysyłamy zapytanie do serwisu internetowego
If Not erreurCommande Then
Try
Dim résultat As Double
Dim résultats() As Double
If fonction = "ajouter" Then
résultat = myOperations.ajouter(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "soustraire" Then
résultat = myOperations.soustraire(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "multiplier" Then
résultat = myOperations.multiplier(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "diviser" Then
résultat = myOperations.diviser(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "toutfaire" Then
résultats = myOperations.toutfaire(a, b)
Console.Out.WriteLine(("résultats=[" + résultats(0).ToString + "," + résultats(1).ToString + "," + _
résultats(2).ToString + "," + résultats(3).ToString + "]"))
End If
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
End If
End While
End Sub
' wyświetlanie błędów
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' wyświetlanie błędu
System.Console.Error.WriteLine(msg)
' zatrzymanie z błędem
Environment.Exit(exitCode)
End Sub
End Module
Analizujemy wyłącznie kod związany z wykorzystaniem klasy proxy. Najpierw tworzony jest obiekt proxy o nazwie operations:
' tworzenie obiektu proxy operacji
Dim myOperations As operations = Nothing
Try
myOperations = New operations
Catch ex As Exception
' błąd połączenia
erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2)
End Try
Za pomocą klawiatury wpisano wiersze funkcji a i b. Na podstawie tych informacji wywoływane są odpowiednie metody obiektu proxy:
' wysyłanie żądania do serwisu internetowego
If Not erreurCommande Then
Try
Dim résultat As Double
Dim résultats() As Double
If fonction = "ajouter" Then
résultat = myOperations.ajouter(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "soustraire" Then
résultat = myOperations.soustraire(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "multiplier" Then
résultat = myOperations.multiplier(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "diviser" Then
résultat = myOperations.diviser(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "toutfaire" Then
résultats = myOperations.toutfaire(a, b)
Console.Out.WriteLine(("résultats=[" + résultats(0).ToString + "," + résultats(1).ToString + "," + _
résultats(2).ToString + "," + résultats(3).ToString + "]"))
End If
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
Po raz pierwszy przetwarzamy tutaj operację toutfaire, która wykonuje cztery operacje. Do tej pory była ona pomijana, ponieważ wysyła tablicę liczb zamkniętą w obudowie XML, która jest bardziej skomplikowana w obsłudze niż proste odpowiedzi XML z innych funkcji, które zwracają tylko jeden wynik. Widać, że dzięki klasie proxy użycie metody toutfaire nie jest bardziej skomplikowane niż korzystanie z innych metod. Aplikacja została skompilowana w oknie DOS w następujący sposób:
dos>vbc /r:operations.dll /r:system.dll /r:system.web.services.dll testClientProxy.vb
dos>dir
04/03/2004 17:17 6 663 operations.vb
04/03/2004 17:24 7 680 operations.dll
04/03/2004 17:41 4 099 testClientProxy.vb
04/03/2004 17:41 5 632 testClientProxy.exe
10.9. Konfiguracja usługi internetowej
Usługa internetowa może wymagać informacji konfiguracyjnych, aby poprawnie się zainicjować. W przypadku serwera IIS informacje te można umieścić w pliku o nazwie web.config, znajdującym się w tym samym folderze co usługa internetowa. Załóżmy, że chcemy utworzyć usługę internetową, która do zainicjowania potrzebuje dwóch informacji: imienia i wieku. Te dwie informacje można umieścić w pliku web.config w następującej postaci:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appSettings>
<add key="nom" value="tintin"/>
<add key="age" value="27"/>
</appSettings>
</configuration>
Parametry inicjalizacji znajdują się w kopercie XML:
Parametr inicjalizacyjny o nazwie P, którego wartość wynosi V, zostanie zadeklarowany za pomocą następującego wiersza:
<add key="P" value="V"/>
W jaki sposób usługa internetowa pobiera te informacje? Gdy IIS ładuje usługę internetową, sprawdza, czy w tym samym folderze znajduje się plik o nazwie web.config. Jeśli tak, odczytuje go. Wartość V parametru P uzyskuje się za pomocą instrukcji:
gdzie ConfigurationSettings jest klasą w przestrzeni nazw System.Configuration.
Sprawdźmy tę technikę na następującej usłudze internetowej:
<%@ WebService language="VB" class=personne %>
Imports System.Web.Services
imports System.Configuration
<WebService([Namespace] := "st.istia.univ-angers.fr")> _
Public Class personne
Inherits WebService
' atrybuty
Private nom As String
Private age As Integer
' konstruktor
Public Sub New()
' inicjowanie atrybutów
nom = ConfigurationSettings.AppSettings("nom")
age = Integer.Parse(ConfigurationSettings.AppSettings("age"))
End Sub
<WebMethod> _
Function id() As String
Return "[" + nom + "," + age.ToString + "]"
End Function
End Class
Usługa internetowa personne posiada dwa atrybuty: nom oraz age, które są inicjowane w jej konstruktorze bez argumentów na podstawie wartości odczytanych z pliku konfiguracyjnego web.config usługi personne. Plik ten ma następującą treść:
<configuration>
<appSettings>
<add key="nom" value="tintin"/>
<add key="age" value="27"/>
</appSettings>
</configuration>
Usługa internetowa posiada ponadto identyfikator <WebMethod> bez parametrów, który ogranicza się do zwracania atrybutów nom i age. Usługa jest zarejestrowana w pliku źródłowym personne.asmx, który wraz z plikiem konfiguracyjnym znajduje się w folderze c:\inetpub\wwwroot\st\personne:
Przypiszmy wirtualny folder IIS/config do powyższego fizycznego folderu. Uruchommy plik IIS, a następnie w przeglądarce wpiszmy adres URL http://localhost/config/personne.asmx usługi personne:

Przejdźmy do linku o unikalnym identyfikatorze metody:

Metoda id nie ma żadnych parametrów. Skorzystajmy z przycisku Appeler:

Udało nam się pobrać informacje zawarte w pliku web.config z serwisu.
10.10. Usługa internetowa IMPOTS
Wracamy do dobrze już nam znanej aplikacji IMPOTS. Kiedy ostatnio z nią pracowaliśmy, przekształciliśmy ją w serwer zdalny, do którego można było uzyskać dostęp przez Internet. Teraz przekształcamy ją w usługę internetową.
10.10.1. Usługa internetowa
Wychodzimy od klasy impôt utworzonej w rozdziale poświęconym bazom danych, która opiera się na informacjach zawartych w bazie danych ODBC:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Collections
Public Class impôt
' dane niezbędne do obliczenia podatku
' pochodzą ze źródła zewnętrznego
Private limites(), coeffR(), coeffN() As Decimal
' twórca
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' sprawdza się, czy wszystkie trzy tabele mają ten sam rozmiar
Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
If Not OK Then
Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
End If
' w porządku
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' konstruktor 2
Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
' inicjuje trzy tablice graniczne: coeffR, coeffN na podstawie
' zawartości tabeli Timpots z bazy danych ODBC DSNimpots
' colLimites, colCoeffR, colCoeffN to trzy kolumny tej tabeli
' może wywołać wyjątek
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
Dim impotsConn As OdbcConnection = Nothing ' la connexion
Dim sqlCommand As OdbcCommand = Nothing ' la commande SQL
' zapytanie SELECT
Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
' tabele służące do pobierania danych
Dim tLimites As New ArrayList
Dim tCoeffR As New ArrayList
Dim tCoeffN As New ArrayList
' próbuje się uzyskać dostęp do bazy danych
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' tworzy się obiekt polecenia
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' wykonuje się zapytanie
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
' Analiza pobranej tabeli
While myReader.Read()
' dane z bieżącego wiersza są umieszczane w tablicach
tLimites.Add(myReader(colLimites))
tCoeffR.Add(myReader(colCoeffR))
tCoeffN.Add(myReader(colCoeffN))
End While
' zwolnienie zasobów
myReader.Close()
impotsConn.Close()
' tabele dynamiczne są umieszczane w tabelach statycznych
Me.limites = New Decimal(tLimites.Count) {}
Me.coeffR = New Decimal(tLimites.Count) {}
Me.coeffN = New Decimal(tLimites.Count) {}
Dim i As Integer
For i = 0 To tLimites.Count - 1
limites(i) = Decimal.Parse(tLimites(i).ToString())
coeffR(i) = Decimal.Parse(tCoeffR(i).ToString())
coeffN(i) = Decimal.Parse(tCoeffN(i).ToString())
Next i
End Sub
' Obliczanie podatku
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
' obliczenie liczby udziałów
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' obliczenie dochodu podlegającego opodatkowaniu i współczynnika rodzinnego
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' obliczenie podatku
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
' zwrot wyniku
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
W usłudze internetowej można używać wyłącznie konstruktora bez parametrów. Dlatego konstruktor klasy będzie wyglądał następująco:
' konstruktor
Public Sub New()
' inicjuje trzy tabele limitów: coeffR, coeffN na podstawie
' zawartości tabeli Timpots z bazy danych ODBC DSNimpots
' colLimites, colCoeffR, colCoeffN to trzy kolumny tej tabeli
' może wygenerować wyjątek
' pobiera parametry konfiguracyjne usługi
Dim DSNimpots As String = ConfigurationSettings.AppSettings("DSN")
Dim Timpots As String = ConfigurationSettings.AppSettings("TABLE")
Dim colLimites As String = ConfigurationSettings.AppSettings("COL_LIMITES")
Dim colCoeffR As String = ConfigurationSettings.AppSettings("COL_COEFFR")
Dim colCoeffN As String = ConfigurationSettings.AppSettings("COL_COEFFN")
' wykorzystuje bazę danych
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
Pięć parametrów konstruktora poprzedniej klasy jest teraz odczytywanych z pliku web.config usługi. Kod pliku źródłowego impots.asmx wygląda następująco. Zawiera on większość poprzedniego kodu. Ograniczyliśmy się do zaznaczenia fragmentów kodu właściwych dla usługi internetowej:
<%@ WebService language="VB" class=impots %>
' tworzenie serwisu internetowego dotyczącego podatków
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Collections
Imports System.Configuration
Imports System.Web.Services
<WebService([Namespace]:="st.istia.univ-angers.fr")> _
Public Class impôt
Inherits WebService
' dane niezbędne do obliczenia podatku
' pochodzą ze źródła zewnętrznego
Private limites(), coeffR(), coeffN() As Decimal
Private OK As Boolean = False
Private errMessage As String = ""
' konstruktor
Public Sub New()
' inicjuje trzy tabele limitów: coeffR, coeffN na podstawie
' zawartości tabeli Timpots z bazy danych ODBC DSNimpots
' colLimites, colCoeffR, colCoeffN to trzy kolumny tej tabeli
' może wygenerować wyjątek
' pobiera parametry konfiguracyjne usługi
Dim DSNimpots As String = ConfigurationSettings.AppSettings("DSN")
Dim Timpots As String = ConfigurationSettings.AppSettings("TABLE")
Dim colLimites As String = ConfigurationSettings.AppSettings("COL_LIMITES")
Dim colCoeffR As String = ConfigurationSettings.AppSettings("COL_COEFFR")
Dim colCoeffN As String = ConfigurationSettings.AppSettings("COL_COEFFN")
' wykorzystuje bazę danych
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
Dim impotsConn As OdbcConnection = Nothing ' la connexion
Dim sqlCommand As OdbcCommand = Nothing ' la commande SQL
Dim myReader As OdbcDataReader ' lecteur de données Odbc
' zapytanie SELECT
Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
' tabele służące do pobierania danych
Dim tLimites As New ArrayList
Dim tCoeffR As New ArrayList
Dim tCoeffN As New ArrayList
' próba uzyskania dostępu do bazy danych
Try
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' tworzy się obiekt polecenia
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' wykonuje się zapytanie
myReader = sqlCommand.ExecuteReader()
' Analiza pobranej tabeli
While myReader.Read()
' dane z bieżącego wiersza są umieszczane w tablicach
tLimites.Add(myReader(colLimites))
tCoeffR.Add(myReader(colCoeffR))
tCoeffN.Add(myReader(colCoeffN))
End While
' zwolnienie zasobów
myReader.Close()
impotsConn.Close()
' tabele dynamiczne są umieszczane w tabelach statycznych
Me.limites = New Decimal(tLimites.Count) {}
Me.coeffR = New Decimal(tLimites.Count) {}
Me.coeffN = New Decimal(tLimites.Count) {}
Dim i As Integer
For i = 0 To tLimites.Count - 1
limites(i) = Decimal.Parse(tLimites(i).ToString())
coeffR(i) = Decimal.Parse(tCoeffR(i).ToString())
coeffN(i) = Decimal.Parse(tCoeffN(i).ToString())
Next i
' wszystko w porządku
OK = True
errMessage = ""
Catch ex As Exception
' błąd
OK = False
errMessage += "[" + ex.Message + "]"
End Try
End Sub
' obliczenie podatku
<WebMethod()> _
Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
' obliczenie liczby udziałów
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' obliczenie dochodu podlegającego opodatkowaniu i współczynnika rodzinnego
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' obliczenie podatku
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
' zwrot wyniku
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
' identyfikator
<WebMethod()> _
Function id() As String
' sprawdzenie, czy wszystko jest w porządku OK
Return "[" + OK + "," + errMessage + "]"
End Function
End Class
Wyjaśnijmy kilka zmian wprowadzonych w klasie impots, poza tymi niezbędnymi do przekształcenia jej w usługę internetową:
- odczytanie bazy danych w konstruktorze może się nie powieść. Dlatego dodaliśmy do naszej klasy dwa atrybuty oraz jedną metodę:
- Wartość logiczna OK przyjmuje wartość vrai, jeśli udało się odczytać bazę danych, a w przeciwnym razie – faux
- ciąg znaków errMessage zawiera komunikat o błędzie, jeśli nie udało się odczytać bazy danych.
- Metoda id bez parametrów pozwala uzyskać wartości tych dwóch atrybutów.
- Aby obsłużyć ewentualny błąd dostępu do bazy danych, część kodu konstruktora związana z tym dostępem została otoczona blokiem try-catch.
Plik konfiguracyjny usługi web.config ma następującą treść:
<configuration>
<appSettings>
<add key="DSN" value="mysql-impots" />
<add key="TABLE" value="timpots" />
<add key="COL_LIMITES" value="limites" />
<add key="COL_COEFFR" value="coeffr" />
<add key="COL_COEFFN" value="coeffn" />
</appSettings>
</configuration>
Podczas pierwszej próby załadowania usługi impots kompilator zgłosił, że nie może znaleźć przestrzeni nazw Microsoft.Data.Odbc użytej w dyrektywie:
Po zapoznaniu się z dokumentacją
- w pliku web.config dodano dyrektywę kompilacji, aby wskazać, że należy użyć modułu asemblerowego Microsoft.Data.odbc
- kopia pliku microsoft.data.odbc.dll została umieszczona w folderze bin projektu. Folder ten jest systematycznie przeszukiwany przez kompilator serwisu internetowego podczas poszukiwania „assembly”.
Wydaje się, że możliwe są inne rozwiązania, ale nie zostały one tutaj szczegółowo omówione. Plik konfiguracyjny przybrał zatem następującą postać:
<configuration>
<appSettings>
<add key="DSN" value="mysql-impots" />
<add key="TABLE" value="timpots" />
<add key="COL_LIMITES" value="limites" />
<add key="COL_COEFFR" value="coeffr" />
<add key="COL_COEFFN" value="coeffn" />
</appSettings>
<system.web>
<compilation>
<assemblies>
<add assembly="Microsoft.Data.Odbc" />
</assemblies>
</compilation>
</system.web>
</configuration>
Zawartość folderu impots\bin:
Usługa wraz z plikiem konfiguracyjnym została umieszczona w folderze impots:
Fizyczny katalog serwisu internetowego został powiązany z wirtualnym katalogiem /impots w IIS. Strona serwisu wygląda zatem następująco:

Jeśli klikniemy link id:

Jeśli użyjemy przycisku Appeler:

Poprzedni wynik wyświetla wartości atrybutów OK (true) i errMessage („”). W tym przykładzie baza została poprawnie załadowana. Nie zawsze tak było i dlatego dodaliśmy metodę id, aby uzyskać dostęp do komunikatu o błędzie. Błąd polegał na tym, że nazwa bazy danych DSN została zdefiniowana jako DSN (użytkownik), podczas gdy należało ją zdefiniować jako DSN (system). Rozróżnienie to wprowadza się w 32-bitowym menedżerze źródeł ODBC:
![]() |
Wróćmy do strony serwisu:

Kliknijmy link calculer:

Określamy parametry wywołania i je wykonujemy:

Wynik jest poprawny.
10.10.2. Wygeneruj proxy usługi impots
Teraz, gdy mamy już działającą usługę internetową impots, możemy wygenerować jej klasę proxy. Przypominamy, że będzie ona wykorzystywana przez aplikacje klienckie do przezroczystego dostępu do usługi internetowej impots. Najpierw używamy narzędzia wsdl do wygenerowania pliku źródłowego klasy proxy, a następnie kompilujemy go do biblioteki DLL.
dos>wsdl /language=vb http://localhost/impots/impots.asmx
Microsoft (R) Web Services Description Language Utility
[Microsoft (R) .NET Framework, Version 1.1.4322.573]
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Écriture du fichier 'D:\data\serge\devel\vbnet\poly\chap9\impots\impots.vb'.
D:\data\serge\devel\vbnet\poly\chap9\impots>dir
09/03/2004 10:20 <REP> bin
09/03/2004 10:58 4 651 impots.asmx
09/03/2004 11:05 3 364 impots.vb
09/03/2004 10:19 431 web.config
dos>vbc /t:library /r:system.dll /r:system.web.services.dll /r:system.xml.dll impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
pour Microsoft (R) .NET Framework version 1.1.4322.573
Copyright (C) Microsoft Corporation 1987-2002. Tous droits réservés.
dos>dir
09/03/2004 10:20 <REP> bin
09/03/2004 10:58 4 651 impots.asmx
09/03/2004 11:09 5 120 impots.dll
09/03/2004 11:05 3 364 impots.vb
09/03/2004 10:19 431 web.config
10.10.3. Korzystanie z serwera proxy za pomocą klienta
W rozdziale poświęconym bazom danych stworzyliśmy aplikację konsolową służącą do obliczania podatku:
dos>dir
27/02/2004 16:56 5 120 impots.dll
27/02/2004 17:12 3 586 impots.vb
27/02/2004 17:08 6 144 testimpots.exe
27/02/2004 17:18 3 328 testimpots.vb
dos>testimpots
pg DSNimpots tabImpots colLimites colCoeffR colCoeffN
dos>testimpots odbc-mysql-dbimpots impots limites coeffr coeffn
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
Program testimpots korzystał wówczas z klasycznej klasy impôt, zawartej w pliku impots.dll. Kod programu testimpots.vb wyglądał następująco:
Option Explicit On
Option Strict On
' przestrzenie nazw
Imports System
Imports Microsoft.VisualBasic
' strona testowa
Module testimpots
Sub Main(ByVal arguments() As String)
' interaktywny program do obliczania podatku
' użytkownik wprowadza trzy dane za pomocą klawiatury: żonaty nbEnfants wynagrodzenie
' następnie program wyświetla kwotę podatku do zapłaty
Const syntaxe1 As String = "pg DSNimpots tabImpots colLimites colCoeffR colCoeffN"
Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' sprawdzanie parametrów programu
If arguments.Length <> 5 Then
' komunikat o błędzie
Console.Error.WriteLine(syntaxe1)
' koniec
Environment.Exit(1)
End If 'if
' pobieranie argumentów
Dim DSNimpots As String = arguments(0)
Dim tabImpots As String = arguments(1)
Dim colLimites As String = arguments(2)
Dim colCoeffR As String = arguments(3)
Dim colCoeffN As String = arguments(4)
' tworzenie obiektu podatku
Dim objImpôt As impôt = Nothing
Try
objImpôt = New impôt(DSNimpots, tabImpots, colLimites, colCoeffR, colCoeffN)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' pętla nieskończona
While True
' na początku brak błędów
Dim erreur As Boolean = False
' żądane są parametry obliczenia podatku
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' co należy zrobić?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' sprawdzanie liczby argumentów w wprowadzonej linii
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe2)
erreur = True
End If
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Integer
If Not erreur Then
' sprawdzanie poprawności parametrów
' żonaty
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
erreur = True
End If
' nbEnfants
nbEnfants = 0
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
erreur = True
End Try
' wynagrodzenie
salaire = 0
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
erreur = True
End Try
End If
If Not erreur Then
' parametry są poprawne – obliczamy podatek
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire).ToString + " F"))
End If
End While
End Sub
End Module
Wykorzystujemy ten sam program, aby teraz korzystał z usługi internetowej impots za pośrednictwem utworzonej wcześniej klasy proxy impots. Jesteśmy zmuszeni do niewielkiej modyfikacji kodu:
- podczas gdy pierwotna klasa impôt miała konstruktor z pięcioma argumentami, klasa proxy impots posiada konstruktor bez parametrów. Jak widzieliśmy, te pięć parametrów jest teraz ustalanych w pliku konfiguracyjnym usługi internetowej.
- nie ma więc już potrzeby przekazywania tych pięciu parametrów jako argumentów do programu testowego
Nowy kod wygląda następująco:
Imports System
Imports Microsoft.VisualBasic
' strona testowa
Module testimpots
Public Sub Main(ByVal arguments() As String)
' interaktywny program do obliczania podatku
' użytkownik wprowadza trzy dane za pomocą klawiatury: żonaty nbEnfants wynagrodzenie
' następnie program wyświetla kwotę podatku do zapłaty
Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' utworzenie obiektu „podatek”
Dim objImpôt As impôt = Nothing
Try
objImpôt = New impôt
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' pętla nieskończona
Dim erreur As Boolean
While True
' na początku nie ma błędu
erreur = False
' wymagane są parametry obliczenia podatku
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' co należy zrobić?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' sprawdzanie liczby argumentów w wprowadzonej linii
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe2)
erreur = True
End If
If Not erreur Then
' sprawdzanie poprawności parametrów
' żonaty
Dim marié As String = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
erreur = True
End If
' nbEnfants
Dim nbEnfants As Integer = 0
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument nbEnfants incorrect : tapez un entier positif ou nul"))
erreur = True
End Try
' wynagrodzenie
Dim salaire As Integer = 0
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument salaire incorrect : tapez un entier positif ou nul"))
erreur = True
End Try
' jeśli parametry są poprawne – obliczamy podatek
If Not erreur Then Console.Out.WriteLine(("impôt=" + objImpôt.calculer(marié = "o", nbEnfants, salaire).ToString + " F"))
End If
End While
End Sub
End Module
W tym samym folderze mamy proxy impots.dll oraz kod źródłowy testimpots.
dos>dir
09/03/2004 11:28 <REP> bin
09/03/2004 11:09 5 120 impots.dll
09/03/2004 11:34 3 396 testimpots.vb
09/03/2004 10:19 431 web.config
Kompilujemy kod źródłowy testimpots.vb:
dos>vbc /r:impots.dll /r:microsoft.visualbasic.dll /r:system.web.services.dll /r:system.dll testimpots.vb
dos>dir
09/03/2004 11:28 <REP> bin
09/03/2004 11:09 5 120 impots.dll
09/03/2004 11:05 3 364 impots.vb
09/03/2004 11:35 5 632 testimpots.exe
09/03/2004 11:34 3 396 testimpots.vb
09/03/2004 10:19 431 web.config
a następnie uruchamiamy:
dos>testimpots
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
Otrzymujemy oczekiwany wynik.





