Skip to content

12. Веб-сервіси

12.1. Introduction

У попередньому розділі ми розглянули кілька клієнт-серверних додатків на базі TCP/IP. Оскільки клієнти та сервер обмінюються текстовими рядками, їх можна писати будь-якою мовою програмування. Клієнт повинен лише знати протокол взаємодії, якого очікує сервер.

Веб-сервіси також є серверними додатками на базі TCP/IP. Вони мають такі особливості:

  • Вони розміщуються на веб-серверах, а протоколом обміну даними між клієнтом і сервером є HTTP (HyperText Transport Protocol) — протокол, що працює на рівні вище TCP-IP.
  • Веб-сервіс має стандартний протокол взаємодії незалежно від типу наданої послуги. Веб-сервіс пропонує різні послуги: S1, S2, …, Sn. Кожна з них очікує на параметри, надані клієнтом, і повертає йому результат. Для кожної послуги клієнт повинен знати:
    • точну назву сервісу, якщо
    • перелік параметрів, які потрібно надати, та їхній тип
    • тип результату, що повертається сервісом

Як тільки ці елементи відомі, діалог «клієнт-сервер» відбувається за однаковим форматом, незалежно від того, до якої веб-служби звертаються. Таким чином, написання клієнтських програм стандартизується.

  • З міркувань безпеки щодо атак з Інтернету багато організацій мають приватні мережі та відкривають в Інтернеті лише певні порти своїх серверів: переважно порт 80 веб-сервісу. Усі інші порти заблоковані. Тому клієнтсько-серверні додатки, представлені в попередньому розділі, будуються в межах приватної мережі (інтранету) і, як правило, недоступні ззовні. Розміщення сервісу на веб-сервері робить його доступним для всієї інтернет-спільноти.
  • Веб-сервіс можна моделювати як віддалений об’єкт. Тоді пропоновані сервіси стають методами цього об’єкта. Клієнт може отримати доступ до цього віддаленого об’єкта так, ніби він є локальним. Це приховує всю частину мережевої комунікації та дозволяє створити клієнт, незалежний від цього рівня. Якщо цей рівень зміниться, клієнт не потребуватиме модифікації.
  • Як і у випадку з клієнт-серверними додатками TCP/IP, розглянутими в попередньому розділі, клієнт і сервер можуть бути написані на будь-якій мові програмування. Вони обмінюються рядками тексту. Ці рядки складаються з двох частин:
    • заголовки, необхідні для протоколу HTTP
    • тіло повідомлення. У разі відповіді сервера клієнту воно має формат XML (eXtensible Markup Language). У разі запиту клієнта до сервера тіло повідомлення може мати кілька форм, зокрема XML. Запит XML від клієнта може мати особливий формат, який називається SOAP (Simple Object Access Protocol). У цьому випадку відповідь сервера також відповідає формату SOAP.

Архітектура клієнт-серверного додатка на основі веб-сервісу виглядає наступним чином:

Це розширення трирівневої архітектури, до якої додаються спеціалізовані класи мережевої комунікації. Ми вже зустрічали подібну архітектуру у графічному клієнтському додатку Windows / сервері TCP податкової служби в розділі 11.9.1.

Роз’яснимо ці загальні поняття на першому прикладі.

12.2. Перший веб-сервіс із Visual Web Developer

Ми створимо перший клієнтсько-серверний додаток із такою спрощеною архітектурою:

12.2.1. Серверна частина

Ми вже зазначали, що веб-сервіс розміщується на веб-сервері. Створення веб-сервісу входить до загальної сфери веб-програмування на стороні сервера. Раніше ми вже мали нагоду писати веб-клієнти, що також є веб-програмуванням, але цього разу на стороні клієнта. Термін «веб-програмування» найчастіше позначає саме серверне програмування, а не клієнтське. Для розробки веб-сервісів або, загалом, веб-додатків Visual C# не є відповідним інструментом. Ми будемо використовувати Visual Developer — одну з версій Express для Visual Studio 2008, яку можна завантажити за адресою [2]: [1]: [http://msdn.microsoft.com/fr-fr/express/future/bb421473(en-us).aspx] (травень 2008 р.):

  • [1]: посилання для завантаження
  • [2]: вкладка «Завантаження»
  • [3]: завантажити Visual Developer 2008

Щоб створити свій перший веб-сервіс, після запуску Visual Developer можна виконати такі дії:

  • [1]: вибрати опцію «Файл» / «Новий веб-сайт»
  • [2]: вибрати тип додатка ASP.NET «Веб-сервіс»
  • [3]: вибрати мову програмування: C#
  • [4]: вказати папку, в якій буде створено проєкт
  • [5]: проект, створений у Visual Web Developer
  • [6]: папка проекту на диску

Веб-додаток у Web Developer має таку структуру:

  • коренева папка, в якій містяться документи веб-сайту (статичні HTML-сторінки, зображення, динамічні веб-сторінки .aspx, веб-сервіси .asmx тощо). Там також знаходиться файл [web.config] — файл конфігурації веб-додатку. Він виконує ту саму роль, що й файл [App.config] у додатках для Windows, і має таку саму структуру.
  • папка [App_Code], у якій містяться класи та інтерфейси веб-сайту, призначені для компіляції.
  • папку [App_Data], у яку слід помістити дані, що використовуються класами з [App_Code]. Наприклад, там може бути база даних SQL Server *.mdf.

[Service.asmx] — це веб-сервіс, створення якого ми ініціювали. Він містить лише такий рядок:


<%@ WebService Language="C#" CodeBehind="~/App_Code/Service.cs" Class="Service" %>

Наведений вище вихідний код призначений для веб-сервера, на якому розміщуватиметься додаток. У виробничому середовищі цим сервером зазвичай є IIS (Internet Information Server) — веб-сервер компанії Microsoft. Visual Web Developer має вбудований полегшений веб-сервер, який використовується в режимі розробки. Попередня директива вказує веб-серверу:

  • [Service.asmx] — це веб-служба (директива WebService)
  • написаний на C# (атрибут Language)
  • що код C# веб-сервісу міститься у файлі [~/App_Code/Service.cs] (атрибут CodeBehind). Саме там веб-сервер буде шукати його для компіляції.
  • що клас, який реалізує веб-сервіс, називається Service (атрибут Class)

Код C# [Service.cs] веб-сервісу, згенерований Visual Developer, має такий вигляд:


using System.Web.Services;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// Щоб дозволити виклик цього веб-сервісу зі скрипту за допомогою ASP.NET AJAX, видаліть коментар із наступного рядка. 
// [System.Web.Script.Services.ScriptService]
public class Service : System.Web.Services.WebService
{
    public Service () {

        //Зніміть коментар з наступного рядка, якщо використовуєте розроблені компоненти 
        //InitializeComponent(); 
    }

    [WebMethod]
    public string HelloWorld() {
        return "Hello World";
    }
    
}

Клас Service схожий на класичний клас C#, однак слід звернути увагу на кілька моментів:

  • рядок 7: клас походить від класу WebService, визначеного в просторі імен System.Web.Services. Таке успадкування не завжди є обов’язковим. Зокрема, у цьому прикладі можна було б обійтися без нього.
  • рядок 3: перед самим класом стоїть атрибут [WebService(Namespace="http://tempuri.org/")], призначений для надання веб-сервісу простору імен. Постачальник класів надає своїм класам простір імен, щоб забезпечити їм унікальну назву та уникнути конфліктів із класами інших постачальників, які можуть мати таку саму назву. Для веб-сервісів це працює так само. Кожен веб-сервіс повинен мати можливість ідентифікації за допомогою унікального імені, в даному випадку — http://tempuri.org/. Це ім’я може бути будь-яким. Воно не обов’язково має форму URI HTTP.
  • рядок 15: перед методом HelloWorld стоїть атрибут [WebMethod], який вказує компілятору, що метод має бути видимим для віддалених клієнтів веб-сервісу. Метод, якому не передує цей атрибут, не є видимим для клієнтів веб-сервісу. Це може бути внутрішній метод, який використовується іншими методами, але не призначений для публікації.
  • рядок 9: конструктор веб-сервісу. У нашому додатку він не використовується.

Згенерований клас [Service.cs] трансформується наступним чином:


using System.Web.Services;

[WebService(Namespace = "http://st.istia.univ-angers.fr")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
{
    [WebMethod]
    public string DisBonjourALaDame(string nomDeLaDame) {
        return string.Format("Bonjour Mme {0}", nomDeLaDame);
    }
    
}

Файл конфігурації [web.config], згенерований для веб-додатка, має такий вигляд:


<?xml version="1.0"?>
<!-- 
    Note: As an alternative to hand editing this file you can use the 
    web admin tool to configure settings for your application. Use
    the Website->Asp.Net Configuration option in Visual Studio.
    A full list of settings and comments can be found in 
    machine.config.comments usually located in 
    \Windows\Microsoft.Net\Framework\v2.x\Config 
-->
<configuration>


    <configSections>
      <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
...
      </sectionGroup>
    </configSections>  


    <appSettings/>
    <connectionStrings/>
...
</configuration>

Файл містить 140 рядків. Він є складним, тому ми не будемо його коментувати. Ми залишимо його без змін. Вище наведено теги <configuration>, <configSections>, <sectionGroup>, <appSettings>, <connectionString>, які ми вже зустрічали у файлі [App.config] для програм Windows.

У нас є робочий веб-сервіс, який можна запустити:

  • [1,2]: клацніть правою кнопкою миші на [Service.asmx] і виберіть опцію «Переглянути сторінку в браузері»
  • [3]: Visual Web Developer запускає вбудований веб-сервер і розміщує його піктограму в правому нижньому куті панелі завдань. Веб-сервер запускається на випадковому порту, у даному випадку — 1906. Відображений URI /WsHello є назвою веб-сайту [4].

Visual Web Developer також запустив браузер для відображення запитуваної сторінки, а саме [Service.asmx]:

  • в [1], URI сторінки. Тут міститься URI сайту [http://localhost:1906/WsHello], за яким слідує URI сторінки /Service.asmx.
  • У [2] суфікс .asmx вказав веб-серверу, що це не звичайна веб-сторінка (суфікс .aspx), яка генерує HTML-сторінку, а сторінка веб-сервісу. Тоді він автоматично генерує веб-сторінку, на якій містяться посилання на кожен із методів веб-сервісу, що має атрибут [WebMethod]. Ці посилання дають змогу протестувати методи.

Клік на посилання [2] вище перенаправляє нас на таку сторінку:

  • у [1] слід звернути увагу на URI [http://localhost:1906/WsHello/Service.asmx?op=DisBonjourALaDame] нової сторінки. Це URI веб-сервісу з параметром op=M, де M — це назва одного з методів веб-сервісу.
  • Нагадаємо сигнатуру методу [DisBonjourALaDame]:

    public string DisBonjourALaDame(string nomDeLaDame) ;

Метод приймає параметр типу string і повертає результат типу string. Ця сторінка дозволяє нам виконати метод [DisBonjourALaDame]: у поле [2] вводимо значення параметра nomDeLaDame, а у полі [3] запускаємо виконання методу. Ми отримуємо такий результат:

  • у [1] слід звернути увагу, що URI відповіді не збігається з URI запиту. Він змінився.
  • у [2] — відповідь веб-сервера. Звернемо увагу на наступні моменти:
    • це відповідь XML, а не HTML
    • результат методу [DisBonjourALaDame] інкапсульовано в тег <string>, що позначає його тип.
    • тег <string> має атрибут xmlns (xml name space), який є простором імен, що ми присвоїли нашому веб-сервісу (рядок 1 нижче).

[WebService(Namespace = "http://st.istia.univ-angers.fr")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService

Щоб дізнатися, як веб-браузер сформував свій запит, потрібно переглянути HTML-код тестової форми:

...
<span>
  <p class="intro">Click <a href="Service.asmx">here</a> for a complete list of operations.</p>
  <h2>DisBonjourALaDame</h2>
  <p class="intro"></p>

  <h3>Test</h3>

         To test the operation using the HTTP POST protocol, click the 'Invoke' button.

   <form action='http://localhost:1906/WsHello/Service.asmx/DisBonjourALaDame' method="POST">                                      
      <table>
              <tr>
                    <td>Parameter</td>
                    <td>Value</td>
                </tr>
                <tr>
               <td>nomDeLaDame:</td>
               <td><input type="text" size="50" name="nomDeLaDame"></td>
           </tr>
                <tr>
                   <td></td>
                  <td align="right"> <input type="submit" value="Invoke" class="button"></td>
           </tr>
      </table>
    </form>
<span>
...
  • рядок 11: значення форми (тег form) будуть відправлені (атрибут method) на URL-адресу [ http://localhost:1906/WsHello/Service.asmx/DisBonjourALaDame] (атрибут action).
  • рядок 19: поле введення має назву nomDeLaDame (атрибут name).

Виклик веб-сервісу [/Service.asmx] дав нам змогу протестувати його методи та отримати базове уявлення про обмін даними між клієнтом і сервером.

12.2.2. Клієнтська частина

Клієнт вищезазначеного віддаленого веб-сервісу можна реалізувати за допомогою базового TCP/IP-клієнта. Ось, наприклад, діалог «клієнт — сервер», реалізований за допомогою клієнта putty, підключеного до віддаленого веб-сервісу (localhost,1906):

POST /WsHello/Service.asmx/DisBonjourALaDame HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: 23

HTTP/1.1 100 Continue
Server: ASP.NET Development Server/9.0.0.0
Date: Sat, 10 May 2008 08:36:41 GMT
Content-Length: 0

nomDeLaDame=Carla+Bruni
HTTP/1.1 200 OK
Server: ASP.NET Development Server/9.0.0.0
Date: Sat, 10 May 2008 08:36:47 GMT
X-AspNet-Version: 2.0.50727
Cache-Control: private, max-age=0
Content-Type: text/xml; charset=utf-8
Content-Length: 119
Connection: Close

<?xml version="1.0" encoding="utf-8"?>
<string xmlns="http://st.istia.univ-angers.fr">Bonjour Mme Carla Bruni</string>
  • рядки 1–5: повідомлення, надіслані клієнтом putty
  • рядок 1: команда POST
  • рядки 6–10: відповідь сервера. Вона означає, що клієнт може надсилати значення у форматі POST.
  • рядок 11: значення, надіслані у форматі param1=val1&param2=val2& .... Деякі символи мають бути такими, що допускаються в URL-адресі. Це те, що раніше називали кодованою URL-адресою. Тут форма має лише один параметр із назвою nomDeLaDame. Відправлене значення має загалом 23 символи. Цей розмір має бути вказаний у заголовку HTTP у рядку 4.
  • рядки 12–22: відповідь сервера
  • рядок 22: результат роботи веб-методу [DisBonjourALaDame].

У Visual C# за допомогою майстра можна створити клієнт для віддаленого веб-сервісу. Саме це ми й розглянемо зараз.

Вищезазначений рівень [1] реалізовано у проекті Visual Studio C# типу «Додаток Windows» під назвою ClientWsHello:

  • у [1], проект ClientWsHello у Visual C#
  • у [2], простір імен за замовчуванням для проекту буде Client (клацніть правою кнопкою миші на проекті / Properties / Application). Цей простір імен буде використовуватися для формування простору імен клієнта, який буде згенеровано.
  • у [3], клацніть правою кнопкою миші на проекті, щоб додати до нього посилання на віддалений веб-сервіс
  • у [4] вкажіть URI веб-сервісу, створеного раніше
  • у [4b] підключіть Visual C# до веб-сервісу, вказаного в [4]. Visual C# отримає опис веб-сервісу і, завдяки цьому опису, зможе згенерувати клієнт.
  • у [5], після отримання опису веб-сервісу Visual C# може відобразити його публічні методи
  • У полі [6] вкажіть простір імен для клієнта, який буде створено. Він буде додано до простору імен, визначеного в полі [2]. Таким чином, простір імен клієнта становитиме Client.WsHello.
  • У [6b] підтвердіть дії майстра.
  • У [7] у проекті з’являється посилання на веб-сервіс WsHello. Крім того, було створено файл конфігурації [app.config].
  • У [8] перегляньте всі файли проекту.
  • У [9] посилання на веб-сервіс WsHello містить різні файли, які ми не будемо детально описувати. Однак ми поглянемо на файл [Reference.cs], який є згенерованим кодом клієнта на C#:

namespace Client.WsHello {
...
    public partial class ServiceSoapClient : System.ServiceModel.ClientBase<Client.WsHello.ServiceSoap>, Client.WsHello.ServiceSoap {
        
        public ServiceSoapClient() {
        }
...        
        public string DisBonjourALaDame(string nomDeLaDame) {
            Client.WsHello.DisBonjourALaDameRequest inValue = new Client.WsHello.DisBonjourALaDameRequest();
            inValue.Body = new Client.WsHello.DisBonjourALaDameRequestBody();
            inValue.Body.nomDeLaDame = nomDeLaDame;
            Client.WsHello.DisBonjourALaDameResponse retVal = ((Client.WsHello.ServiceSoap)(this)).DisBonjourALaDame(inValue);
            return retVal.Body.DisBonjourALaDameResult;
        }
    }
}
  • рядок 1: простір імен згенерованого клієнта — Client.WsHello. Якщо ви хочете змінити цей простір імен, саме тут це потрібно зробити.
  • рядок 3: клас ServiceSoapClient — це клас згенерованого клієнта. Це проксі-клас у тому сенсі, що він приховує від Windows-додатка факт використання віддаленого веб-сервісу. Додаток Windows використовуватиме віддалений клас WsHello через локальний клас Client.WsHello.ServiceSoapClient. Щоб створити екземпляр клієнта, скористаємося конструктором у рядку 5:
Client.WsHello.ServiceSoapClient client=new Client.WsHello.ServiceSoapClient();
  • рядок 8: метод DisBonjourALaDame є клієнтським аналогом методу DisBonjourALaDame веб-сервісу. Додаток для Windows використовуватиме віддалений метод DisBonjourALaDame через локальний метод Client.WsHello.ServiceSoapClient.DisBonjourALaDame у такому вигляді:
string bonjour=client.DisBonjourALaDame("Carla Bruni");

Створений файл [app.config] має такий вигляд:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <bindings>
         ....
        </bindings>
        <client>
            <endpoint address="http://localhost:1906/WsHello/Service.asmx"... />
        </client>
    </system.serviceModel>
</configuration>

З цього файлу ми візьмемо лише рядок 8, який містить URI веб-сервісу. Якщо URI зміниться, клієнт для Windows не потребуватиме перекомпіляції. Достатньо змінити URI у файлі [app.config].

Повернемося до архітектури Windows-додатка, який ми хочемо створити:

Ми створили рівень [client] веб-сервісу. Наступним буде рівень [ui]:

тип
назва
роль
1
TextBox
textBoxNomDame
ім'я жінки
2
Button
buttonSalutations
для підключення до віддаленого веб-сервісу WsHello та виклику методу DisBonjourALaDame.
3
Мітка
labelBonjour
результат, повернутий веб-сервісом

Код форми [Form1.cs] такий:


using System;
using System.Windows.Forms;
using Client.WsHello;

namespace ClientSalutations {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private void buttonSalutations_Click(object sender, EventArgs e) {
            // пісочний годинник
            Cursor=Cursors.WaitCursor;
            // запит до веб-сервісу
            labelBonjour.Text = new ServiceSoapClient().DisBonjourALaDame(textBoxNomDame.Text.Trim());
            // звичайний курсор
            Cursor = Cursors.Arrow;
        }
    }
}
  • рядок 15: створюється екземпляр клієнта веб-сервісу. Він має тип Client.WsHello.ServiceSoapClient. Простір імен Client.WsHello оголошено у рядку 3. Викликано локальний метод ServiceSoapClient().DisBonjourALaDame. Відомо, що він, у свою чергу, звертається до однойменного віддаленого методу веб-сервісу.

12.3. Веб-сервіс арифметичних операцій

Ми створимо другий клієнт-серверний додаток, який знову матиме таку спрощену архітектуру:

Попередній веб-сервіс надавав лише один метод. Розглянемо веб-сервіс, який надаватиме 4 арифметичні операції:

  1. додати(a,b), що повертає a+b
  2. віднімати(a,b), що поверне a-b
  3. множити(a,b), що поверне a*b
  4. ділення(a,b), що поверне a/b

і який буде викликатися за допомогою такого графічного інтерфейсу:

  • у [1] операція, яку потрібно виконати
  • в [2,3]: операнди
  • в [4] — кнопка виклику веб-сервісу
  • в [5] — результат, повернутий веб-сервісом

12.3.1. Серверна частина

Ми створюємо проект типу веб-сервісу за допомогою Visual Web Developer:

  • у [1] — згенерований веб-додаток WsOperations
  • у [2] веб-додаток WsOperations було оновлено наступним чином:
  • веб-сторінка [Service.asmx] була перейменована на [Operations.asmx]
  • клас [Service.cs] було перейменовано на [Operations.cs]
  • файл [web.config] було видалено, щоб показати, що він не є обов’язковим.

Веб-сторінка [Service.asmx] містить такий рядок:


<%@ WebService Language="C#" CodeBehind="~/App_Code/Operations.cs" Class="Operations" %>

Веб-сервіс забезпечується наступним класом [Operations.cs]:


using System.Web.Services;

[WebService(Namespace = "http://st.istia.univ-angers.fr/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Operations : System.Web.Services.WebService
{
    [WebMethod]
    public double Ajouter(double a, double b)
    {
        return a + b;
    }

    [WebMethod]
    public double Soustraire(double a, double b)
    {
        return a - b;
    }

    [WebMethod]
    public double Multiplier(double a, double b)
    {
        return a * b;
    }

    [WebMethod]
    public double Diviser(double a, double b)
    {
        return a / b;
    }

}

Щоб опублікувати веб-сервіс, ми діємо відповідно до вказівок у [3]. У результаті ми отримуємо тестову сторінку з 4 методами веб-сервісу WsOperations:

Image

Пропонуємо читачеві протестувати ці 4 методи.

12.3.2. Клієнтська частина

За допомогою Visual C# ми створюємо додаток для Windows ClientWsOperations:

  • у [1], проект ClientWsOperations у Visual C#
  • у [2], простір імен за замовчуванням для проекту буде Client (клацніть правою кнопкою миші на проекті / Properties / Application). Цей простір імен буде використовуватися для побудови простору імен клієнта, який буде згенеровано.
  • у [3], клацніть правою кнопкою миші на проекті, щоб додати до нього посилання на існуючий веб-сервіс
  • у [4] введіть URI веб-сервісу, створеного раніше. Для цього потрібно подивитися, що відображається в полі адреси браузера, який відкриває тестову сторінку веб-сервісу.
  • у [4b] підключіть Visual C# до веб-сервісу, вказаного в [4]. Visual C# отримає опис веб-сервісу і, завдяки цьому опису, зможе згенерувати клієнт.
  • У [5], після отримання опису веб-сервісу Visual C# може відобразити його публічні методи
  • у [6] вкажіть простір імен для клієнта, який буде згенеровано. Він буде додано до простору імен, визначеного в [2]. Таким чином, простір імен клієнта буде Client.WsOperations.
  • У [6b] підтвердіть дії майстра.
  • У [7] у проєкті з’являється посилання на веб-сервіс WsOperations. Крім того, було створено файл конфігурації [app.config].

Нагадаємо, що згенерований клієнт має тип Client.WsOperations.OperationsSoapClient, де

  • Client.WsOperations — простір імен клієнта веб-сервісу
  • Operations — клас віддаленого веб-сервісу.

Хоча існує логічний спосіб побудови цього імені, часто простіше знайти його у файлі [Reference.cs], який за замовчуванням є прихованим. Його вміст такий:


namespace Client.WsOperations {
 ...
    public partial class OperationsSoapClient : System.ServiceModel.ClientBase<Client.WsOperations.OperationsSoap>, Client.WsOperations.OperationsSoap {
        
        public OperationsSoapClient() {
        }
...
        public double Ajouter(double a, double b) {
            ...
        }
        
        public double Soustraire(double a, double b) {
            ...
        }
        
        public double Multiplier(double a, double b) {
            ...
        }
        
        public double Diviser(double a, double b) {
            ...
        }
    }
}

До методів Ajouter, Soustraire, Multiplier, Diviser віддаленого веб-сервісу будуть доступні через однойменні методи проксі (рядки 8, 12, 16, 20) клієнта типу Client.WsOperations.OperationsSoapClient (рядок 3).

Залишилося створити графічний інтерфейс:

тип
назва
роль
1
ComboBox
comboBoxOperations
перелік арифметичних операцій
2
TextBox
textBoxA
число a
3
TextBox
textBoxB
число b
4
Кнопка
buttonExécuter
запитує віддалений веб-сервіс
5
Мітка
labelRésultat
результат операції

Код [Form1.cs] має такий вигляд:


using System;
using System.Windows.Forms;
using Client.WsOperations;

namespace ClientWsOperations {
    public partial class Form1 : Form {
        // таблиця операцій
        private string[] opérations = { "Ajouter", "Soustraire", "Multiplier", "Diviser" };
        // веб-сервіс для звернення
        private OperationsSoapClient opérateur = new OperationsSoapClient();

        // конструктор
        public Form1() {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e) {
            // заповнення комбінованого списку операцій
            comboBoxOperations.Items.AddRange(opérations);
            comboBoxOperations.SelectedIndex = 0;
        }

        private void buttonExécuter_Click(object sender, EventArgs e) {
            // перевірка параметрів a та b операції
            textBoxMessage.Text = "";
            bool erreur = false;
            Double a = 0;
            if (!Double.TryParse(textBoxA.Text, out a)) {
                textBoxMessage.Text += "Nombre a erroné...";
            }
            Double b = 0;
            if (!Double.TryParse(textBoxB.Text, out b)) {
                textBoxMessage.Text += String.Format("{0}Nombre b erroné...", Environment.NewLine);
            }
            if (erreur) {
                return;
            }
            // виконання операції
            Double c=0;
            try {
                switch (comboBoxOperations.SelectedItem.ToString()) {
                    case "Ajouter":
                        c=opérateur.Ajouter(a, b);
                        break;
                    case "Soustraire":
                        c=opérateur.Soustraire(a, b);
                        break;
                    case "Multiplier":
                        c=opérateur.Multiplier(a, b);
                        break;
                    case "Diviser":
                        c=opérateur.Diviser(a, b);
                        break;
                }
                // виведення результату
                labelRésultat.Text = c.ToString();
            } catch (Exception ex) {
                textBoxMessage.Text = ex.Message;
            }
        }
    }
}
  • рядок 3: простір імен клієнта віддаленого веб-сервісу
  • рядок 10: клієнт віддаленого веб-сервісу ініціалізується одночасно з формою
  • рядки 17–21: комбінація операцій заповнюється під час початкового завантаження форми
  • рядок 23: виконання операції, запитуваної користувачем
  • рядки 25–37: перевіряється, чи введені значення a та b дійсно є дійсними числами
  • рядки 41–54: оператор switch для виконання віддаленої операції, запитуваної користувачем
  • рядки 43, 46, 49, 52: запит надсилається до локального клієнта. Останній, не повідомляючи про це користувачеві, звертається до віддаленого веб-сервісу.

12.4. Веб-сервіс для розрахунку податків

Повернемося до вже добре відомої програми для розрахунку податку. Минулого разу, коли ми працювали з нею, ми перетворили її на віддалений TCP-сервер, до якого можна було звернутися через Інтернет. Тепер ми перетворюємо її на веб-сервіс.

Архітектура версії 8 була такою:

Архітектура версії 9 буде схожою:

Ця архітектура схожа на архітектуру версії 8, розглянуту в розділі 11.9.1, але в ній сервер і клієнт TCP замінені веб-сервісом та його проксі-клієнтом. Ми повністю перенесемо рівні [ui], [metier] та [dao] з версії 8.

12.4.1. Серверна частина

Ми створюємо проект типу веб-сервісу за допомогою Visual Web Developer:

  • у [1], веб-додаток WsImpot, згенерований
  • в [2], веб-додаток WsImpot було оновлено наступним чином:
    • веб-сторінка [Service.asmx] була перейменована на [ServiceImpot.asmx]
    • клас [Service.cs] було перейменовано на [ServiceImpot .cs]

Веб-сторінка [ServiceImpot.asmx] містить такий рядок:


<%@ WebService Language="C#" CodeBehind="~/App_Code/ServiceImpot.cs" Class="ServiceImpot" %>

Веб-сервіс забезпечується наступним класом [ServiceImpot.cs]:


using System.Web.Services;

[WebService(Namespace = "http://st.istia.univ-angers.fr/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class ServiceImpot : System.Web.Services.WebService
{

    [WebMethod]
    public int CalculerImpot(bool marié, int nbEnfants, int salaire)
    {
        return 0;
    }

}

Веб-сервіс надаватиме лише метод CalculerImpot із рядка 9.

Повернемося до архітектури «клієнт/сервер» версії 8:

Проєкт Visual Studio для сервера [1] мав такий вигляд:

  • у [1] — проект. У ньому містилися такі елементи:
    • [ServeurImpot.cs]: сервер TCP/IP для розрахунку податку у вигляді консольного додатка.
    • [dbimpots.sdf]: компактна база даних SQL Server версії 7, описана в розділі 9.8.5.
    • [App.config]: файл конфігурації програми.
  • У [2] папка [lib] містить необхідні для проекту файли DLL:
    • [ImpotsV7-dao]: шар [dao] версії 7
    • [ImpotsV7-metier]: шар [metier] версії 7
    • [antlr.runtime, CommonLogging, Spring.Core] для Spring
  • у [3], посилання на проект

Шари [metier] та [dao] цієї версії вже існують: саме вони використовуються у версіях 7 та 8. Вони мають вигляд DLL, який ми інтегруємо в проект таким чином:

  • у [1] папка [lib] із сервера версії 8 була скопійована в проєкт веб-сервісу версії 9.
  • у [2] ми змінюємо властивості сторінки, щоб додати DLL з папки [lib] та [4] до посилань проекту [3].

Після цієї операції ми маємо всі шари, необхідні для сервера [1], наведеного нижче:

Якщо елементи сервера [1], [serveur], [metier], [dao], [entites], [spring] дійсно присутні у проєкті Visual Studio, нам бракує елемента, який буде створювати їхні екземпляри під час запуску веб-додатку. У версії 8 головний клас із статичним методом [Main] виконував інстанціювання шарів за допомогою Spring. У веб-додатку класом, здатним виконувати аналогічну роботу, є клас, пов’язаний із файлом [Global.asax] :

  • у [1] додаємо новий елемент до веб-проєкту
  • у [2] вибираємо тип «Global Application Class»
  • у [3] — за замовчуванням пропонується ім’я для цього елемента
  • у [4] підтверджуємо додавання
  • у [5] новий елемент було інтегровано в проект

Давайте розглянемо вміст файлу [Global.asax]:


<%@ Application Language="C#" %>

<script runat="server">

    void Application_Start(object sender, EventArgs e) 
    {
        // Код, що виконується під час запуску програми
    }
    
    void Application_End(object sender, EventArgs e) 
    {
        // Код, що виконується під час завершення роботи програми
    }
        
    void Application_Error(object sender, EventArgs e) 
    { 
        // Код, що виконується при виникненні необробленої помилки
    }

    void Session_Start(object sender, EventArgs e) 
    {
        // Код, що виконується під час запуску нової сесії
    }

    void Session_End(object sender, EventArgs e) 
    {
        // Код, що виконується при завершенні сеансу. 
    }
       
</script>

Файл є поєднанням тегів, призначених для веб-сервера (рядки 1, 3, 30), та коду C#. Цей метод був єдиним, що використовувався у ASP — попереднику ASP.NET, який є сучасною технологією Microsoft для веб-програмування. У ASP.NET цей метод все ще можна використовувати, але він не є методом за замовчуванням. Методом за замовчуванням є так званий метод «CodeBehind», який ми вже зустрічали на сторінках веб-сервісів, наприклад, тут, у [ServiceImpot.asmx]:


<%@ WebService Language="C#" CodeBehind="~/App_Code/ServiceImpot.cs" Class="ServiceImpot" %>

Атрибут CodeBehind вказує, де знаходиться вихідний код сторінки [ServiceImpot.asmx]. Без цього атрибута вихідний код містився б на сторінці [ServiceImpot.asmx] із синтаксисом, аналогічним тому, що міститься в [Global.asax]. Ми не зберігатимемо файл [Global.asax] у тому вигляді, в якому він був згенерований, але його код дозволяє нам зрозуміти, для чого він призначений:

  • клас, пов’язаний із Global.asax, інстанціюється під час запуску додатка. Термін його існування збігається з терміном існування всього додатка. Конкретно кажучи, він зникає лише тоді, коли веб-сервер зупиняється.
  • Потім виконується метод Application_Start. Це єдиний раз, коли він буде виконаний. Тому його використовують для створення екземплярів об’єктів, спільних для всіх користувачів. Ці об’єкти розміщуються:
    • або у статичних полях класу, пов’язаного з Global.asax. Оскільки цей клас існує постійно, будь-який користувач може отримати інформацію з нього за допомогою запиту.
    • або в контейнері Application. Цей контейнер також створюється під час запуску додатка, і його час існування збігається з часом існування додатка.
      • щоб помістити дані в цей контейнер, пишемо Application["clé"]=значення;
      • щоб отримати їх, записуємо T значення=(T)Application["clé"]; де T — тип valeur.
  • Метод Session_Start виконується щоразу, коли новий користувач надсилає запит. Як розпізнати нового користувача? Кожен користувач (найчастіше — веб-браузер) після свого першого запиту отримує сесійний токен — унікальний для кожного користувача рядок символів. Далі користувач надсилає отриманий токен сеансу з кожним новим запитом, який він надсилає. Це дозволяє веб-серверу його розпізнати. У міру надходження різних запитів від одного й того самого користувача дані, що стосуються саме його, можуть зберігатися в контейнері Session:
    • щоб помістити дані в цей контейнер, записуємо Session["clé"]=значення;
    • щоб отримати їх, записуємо T значення=(T)Session["clé"]; де T — тип valeur.

Тривалість сеансу за замовчуванням обмежена 20 хвилинами бездіяльності користувача (c.a.d, тобто він не відправляв свій сесійний токен протягом 20 хвилин).

  • Метод Application_Error виконується, коли виняток, який не обробляється веб-додатком, передається на веб-сервер.
  • Інші методи використовуються рідше.

Після цих загальних відомостей, для чого нам може знадобитися Global.asax? Ми будемо використовувати його метод Application_Start для ініціалізації шарів [metier], [dao] та [entites], що містяться в DLL та [ImpotsV7-metier, ImpotsV7-dao]. Для їх інстанціювання ми використаємо Spring. Посилання на створені таким чином шари потім будуть збережені у статичних полях класу, пов’язаного з Global.asax.

На першому етапі ми виносимо код C# з Global.asax в окремий клас. Проєкт змінюється наступним чином:

У [1] файл [Global.asax] буде пов’язаний із класом [Global.cs] [2], маючи лише такий рядок:


<%@ Application Language="C#" Inherits="WsImpot.Global"%>

Атрибут Inherits="WsImpot.Global" вказує, що клас, пов'язаний із Global.asax, успадковує клас WsImpot.Global. Цей клас визначено у файлі [Global.cs] наступним чином:


using System;
using Metier;
using Spring.Context.Support;
namespace WsImpot
{
    public class Global : System.Web.HttpApplication
    {
        // бізнес-шар
        public static IImpotMetier Metier;

        // метод, що виконується під час запуску додатка
        private void Application_Start(object sender, EventArgs e)
        {
            // Інстанції шарів [metier] та [dao]
            Metier = ContextRegistry.GetContext().GetObject("metier") as IImpotMetier;
        }
    }
}
  • рядок 4: простір імен класу
  • рядок 6: клас Global. Йому можна дати будь-яку назву. Головне, щоб він походив від класу System.Web.HttpApplication.
  • рядок 9: публічне статичне поле, яке міститиме посилання на шар [metier].
  • рядок 12: метод Application_Start, який буде виконуватися під час запуску додатка.
  • рядок 15: Spring використовується для обробки файлу [web.config], у якому він знайде об’єкти, що підлягають інстанціюванню для створення шарів [metier] та [dao]. Немає жодної різниці між використанням Spring із файлом [App.config] у Windows-додатку та використанням Spring із файлом [web.config] у веб-додатку. Крім того, [web.config] та [App.config] мають однакову структуру. У рядку 15 посилання на шар [metier] зберігається у статичному полі рядка 9, щоб це посилання було доступним для всіх запитів усіх користувачів.

Файл [web.config] матиме такий вигляд:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <configSections>
    <sectionGroup name="spring">
      <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
      <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
    </sectionGroup>
  </configSections>

  <spring>
    <context>
      <resource uri="config://spring/objects" />
    </context>
    <objects xmlns="http://www.springframework.net">
      <object name="dao" type="Dao.DataBaseImpot, ImpotsV7-dao">
        <constructor-arg index="0" value="MySql.Data.MySqlClient"/>
        <constructor-arg index="1" value="Server=localhost;Database=bdimpots;Uid=admimpots;Pwd=mdpimpots;"/>
        <constructor-arg index="2" value="select limite, coeffr, coeffn from tranches"/>
      </object>
      <object name="metier" type="Metier.ImpotMetier, ImpotsV7-metier">
        <constructor-arg index="0" ref="dao"/>
      </object>
    </objects>
  </spring>
</configuration>

Це файл [App.config], який використовується у версії 7 програми та розглядається у розділі 9.8.4.

  • рядки 16–20: визначають шар [dao], що працює з базою даних MySQL5. Ця база даних була описана в розділі 9.8.1.
  • рядки 21–23: визначають шар [metier]

Повернемося до «пазлу» сервера:

Під час запуску додатка було створено екземпляри шарів [metier] та [dao]. Термін існування шарів збігається з терміном існування самого додатка. Коли створюється екземпляр веб-сервісу? Фактично — під час кожного запиту, що надходить до нього. Після завершення запиту об’єкт, який його обробив, видаляється. Отже, на перший погляд, веб-сервіс є безстатусним. Він не може зберігати інформацію між двома запитами у власних полях. Він може зберігати її в сесії користувача. Для цього методи, які він надає, повинні бути позначені спеціальним атрибутом:


    [WebMethod(EnableSession=true)]
    public int CalculerImpot(bool marié, int nbEnfants, int salaire)
....

У наведеному вище прикладі рядок 1 надає методу CalculerImpot доступ до контейнера Session, про який ми говорили раніше. У нашому додатку нам не доведеться використовувати цей атрибут. Отже, веб-сервіс WsImpot буде інстанціюватися при кожному запиті та не матиме стану.

Тепер ми можемо написати код [ServiceImpot.cs], який реалізує веб-сервіс:


using System.Web.Services;
using WsImpot;

[WebService(Namespace = "http://st.istia.univ-angers.fr/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class ServiceImpot : System.Web.Services.WebService
{

    [WebMethod]
    public int CalculerImpot(bool marié, int nbEnfants, int salaire)
    {
        return Global.Metier.CalculerImpot(marié, nbEnfants, salaire);
    }

}
  • рядок 10: єдиний метод веб-сервісу
  • рядок 12: використовується метод CalculerImpot з шару [metier]. Посилання на цей шар міститься у статичному полі Metier класу Global. Цей клас належить до простору імен WsImpot (рядок 2).

Ми готові запустити веб-сервіс. Перед цим необхідно запустити SGBD та MySQL5, щоб забезпечити доступ до бази даних bdimpots. Після цього ми запускаємо веб-сервіс [1]:

Після цього браузер відображає сторінку [2]. Ми переходимо за посиланням:

Ми присвоюємо значення кожному з трьох параметрів методу CalculerImpot і запускаємо метод. Отримуємо такий результат, який є правильним:

Image

12.4.2. Графічний клієнт для віддаленого веб-сервісу під Windows

Тепер, коли веб-сервіс написано, перейдемо до клієнта. Повернемося до архітектури клієнт-серверного додатка:

Нам потрібно написати клієнт [2]. Графічний інтерфейс буде ідентичним до того, що у версії 8:

Щоб написати частину [client] версії 9, ми візьмемо за основу частину [client] версії 8, а потім внесемо необхідні зміни. Ми дублюємо проект Visual Studio, розглянутий у розділі 11.9.4.1, перейменовуємо його на ClientWsImpot і завантажуємо у Visual Studio:

Рішення Visual Studio версії 8 складалося з 2 проектів:

  • проект [metier] [1], який був TCP-клієнтом TCP-сервера розрахунку податків
  • проект [ui] [2] — графічний інтерфейс.

Необхідно внести такі зміни:

  • проект [metier] відтепер має бути клієнтом веб-сервісу
  • проект [ui] повинен посилатися на DLL з нового шару [metier]
  • конфігурація шару [metier] у [App.config] має бути змінена.

12.4.2.1. Новий рівень [metier]

  • у [1], IImpotMetier є інтерфейсом шару [metier], а ImpotMetierTcp — його реалізацією клієнтом TCP
  • у [2] ми видаляємо реалізацію ImpotMetierTcp. Нам потрібно створити іншу реалізацію інтерфейсу IImpotMetier, яка буде клієнтом веб-сервісу.
  • У [3] ми призначаємо Client простір імен за замовчуванням для проєкту [metier]. DLL, що буде згенеровано, отримає назву [ImpotsV9-metier.dll].
  • у [4] ми створюємо посилання на веб-сервіс WsImpot.
  • у [5] ми налаштовуємо його та підтверджуємо.
  • у файлі [6] було створено посилання на веб-сервіс WsImpot та згенеровано файл [app.config].

У прихованому файлі [Reference.cs]:

  • простір імен — Client.WsImpot
  • клас-клієнт називається ServiceImpotSoapClient
  • вона має єдиний метод підпису:

        public int CalculerImpot(bool marié, int nbEnfants, int salaire) ;

Нам залишається реалізувати інтерфейс IImpotMetier:


namespace Metier {
    public interface IImpotMetier {
        int CalculerImpot(bool marié, int nbEnfants, int salaire);
    }
}

Ми реалізуємо його за допомогою наступного класу ImpotMetierWs:


using System.Net.Sockets;
using System.IO;
using Client.WsImpot;

namespace Metier {
    public class ImpotMetierWs : IImpotMetier {

        // клієнт віддаленого веб-сервісу
        private ServiceImpotSoapClient client = new ServiceImpotSoapClient();

        // розрахунок податку
        public int CalculerImpot(bool marié, int nbEnfants, int salaire) {
            return client.CalculerImpot(marié, nbEnfants, salaire);
        }

    }
}
  • рядок 6: клас ImpotMetierWs реалізує інтерфейс IImpotMetier.
  • рядок 9: під час створення екземпляра ImpotMetierWs поле client ініціалізується екземпляром клієнта веб-сервісу розрахунку податку.
  • рядок 12: єдиний метод інтерфейсу IImpotMetier, який потрібно реалізувати.
  • рядок 13: використовується метод CalculerImpot клієнта віддаленого веб-сервісу з розрахунку податків. У підсумку буде викликано метод CalculerImpot віддаленого веб-сервісу.

Можна згенерувати DLL для проекту:

  • у [1], проект [client] у кінцевому стані
  • у [2], створення файлу DLL для проекту
  • у [3], DLL та ImpotsV9-metier.dll знаходяться у папці /bin/Release проекту.

12.4.2.2. Новий шар [ui]

Рівень [client] клієнта вже написано. Залишилося написати рівень [ui]. Повернемося до проєкту Visual Studio:

  • у [1], проект [ui], створений на основі версії 8
  • у [2], DLL та ImpotsV8-metier зі старого шару [metier] замінено на DLL ImpotsV9-metier з нового шару
  • на [3], DLL та ImpotsV9-metier додано до посилань проекту.

Друга зміна стосується файлу [App.config]. Слід пам’ятати, що цей файл використовується Spring для створення екземпляра шару [metier]. Оскільки останній змінився, конфігурація [App.config] також має бути змінена. З іншого боку, файл [App.config] повинен мати конфігурацію, що дозволяє підключитися до віддаленого веб-сервісу розрахунку податків. Ця конфігурація була згенерована у файлі [app.config] проекту [metier] під час додавання до нього посилання на віддалений веб-сервіс.

Отже, файл [App.config] набуває такого вигляду:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>

    <spring>
        <context>
            <resource uri="config://spring/objects" />
        </context>
        <objects xmlns="http://www.springframework.net">
            <object name="metier" type="Metier.ImpotMetierWs, ImpotsV9-metier">
            </object>
        </objects>
    </spring>

    <!-- веб-сервіс -->
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="ServiceImpotSoap" closeTimeout="00:01:00" openTimeout="00:01:00"
                        receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false"
                        bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
                        maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                        messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
                        useDefaultWebProxy="true">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                            maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                    <security mode="None">
                        <transport clientCredentialType="None" proxyCredentialType="None"
                                realm="" />
                        <message clientCredentialType="UserName" algorithmSuite="Default" />
                    </security>
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
                    binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
                    contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
        </client>
    </system.serviceModel>
</configuration>
  • рядки 15–18: Spring створює лише один екземпляр об’єкта — шар [metier]
  • рядок 16: шар [metier] інстанціюється класом [Metier.ImpotMetierWs], який знаходиться в DLL ImpotsV9-metier.
  • рядки 22–46: конфігурація клієнта віддаленого веб-сервісу. Це скопійований та вставлений вміст файлу [app.config] із проєкту [metier].

Все готово. Запускаємо додаток за допомогою Ctrl-F5 (веб-сервіс має бути запущений, файли SGBD та MySQL5 мають бути запущені, порт у рядку 42 вище має бути правильним):

  

12.5. Веб-клієнт для веб-сервісу розрахунку податків

Повернемося до архітектури клієнт-серверного додатка, що щойно було написано:

Рівень [ui], наведений вище, був реалізований за допомогою графічного клієнта для Windows. Тепер ми реалізуємо його за допомогою веб-інтерфейсу:

 

Це важлива зміна для користувачів. Наразі наш клієнт-серверний додаток, версія 9, може обслуговувати декількох клієнтів одночасно. Це покращення порівняно з версією 8, де він обслуговував лише одного клієнта за раз. Обмеження полягає в тому, що користувачі, які бажають скористатися веб-сервісом розрахунку податків, повинні мати на своєму комп’ютері Windows-клієнт, який ми розробили. У цій новій версії, яку ми назвемо версією 10, користувачі зможуть отримати доступ до веб-сервісу розрахунку податків за допомогою свого веб-браузера.

У наведеній вище архітектурі:

  • серверна частина залишається незмінною. Вона така сама, як у версії 9.
  • на стороні клієнта рівень [client du service web] залишається незмінним. Він був інкапсульований у DLL та [ImpotsV9-metier]. Ми будемо повторно використовувати цей DLL.
  • Зрештою, єдина зміна полягає в заміні графічного інтерфейсу Windows на веб-інтерфейс.

Ми розглянемо нові поняття серверного веб-програмування. Оскільки метою цього документа не є навчання веб-програмуванню, ми спробуємо пояснити підхід, якого будемо дотримуватися, не вдаючись у подробиці. Тому цей розділ матиме дещо «магічний» характер. Однак нам здається цікавим застосувати цей підхід, щоб продемонструвати новий приклад багатошарової архітектури, в якій змінюється один із шарів.

Отже, архітектура версії 10 виглядає так:

У нас уже є всі шари, крім шару [web]. Щоб краще зрозуміти, що саме буде зроблено, нам потрібно детальніше розглянути архітектуру клієнта. Вона буде такою:

  • веб-користувач має у своєму браузері веб-форму
  • ця форма надсилається на веб-сервер 1, який передає її на обробку шару [web]
  • шар [web] потребуватиме послуг клієнта віддаленої веб-служби, інкапсульованого в [ImpotsV9-metier.dll].
  • клієнт віддаленої веб-служби буде взаємодіяти з веб-сервером 2, на якому розміщена віддалена веб-служба.
  • відповідь віддаленого веб-сервісу буде передана до веб-шару клієнта, який оформить її у вигляді сторінки, яку він надішле користувачеві.

Отже, наше завдання полягає в тому, щоб:

  • створити веб-форму, яку користувач побачить у своєму браузері
  • написати веб-додаток, який оброблятиме запит користувача та надсилатиме йому відповідь у вигляді нової веб-сторінки. Фактично це буде та сама форма, до якої ми додамо суму податку, що підлягає сплаті
  • написати «склеювальний код», завдяки якому все це працює разом.

Все це буде зроблено за допомогою нового веб-сайту, створеного за допомогою Visual Web Developer:

  • [1]: вибрати опцію «File / New Web Site»
  • [2]: вибрати тип додатка «ASP.NET Web Site»
  • [3]: вибрати мову програмування: C#
  • [4]: вказати папку, в якій буде створено проєкт
  • [5]: проект, створений у Visual Web Developer
    • [Default.aspx] — це веб-сторінка, яка називається «сторінкою за замовчуванням». Саме вона відображатиметься, якщо ввести URL-адресу http://.../ClientAspImpot без вказівки конкретного документа. Саме на цій сторінці міститиметься форма для розрахунку податку, яку користувач побачить у своєму браузері.
    • [Default.aspx.cs] — це клас, пов’язаний із цією сторінкою, який генеруватиме форму, що надсилається користувачеві, а потім оброблятиме її після того, як користувач її заповнить і підтвердить.
    • [web.config] — це файл конфігурації додатка. На відміну від попередніх випадків, ми його збережемо.

Повернемося до архітектури, яку нам потрібно побудувати:

  • [1] буде реалізовано за допомогою [Default.aspx]
  • [2] буде реалізовано за допомогою [Default.aspx.cs]
  • [3] буде реалізовано за допомогою DLL та [ImpotV9-metier]

Почнемо з реалізації шару [3]. Це передбачає кілька етапів:

  • у [1] папка [lib] графічного клієнта Windows версії 9 копіюється у папку веб-проєкту [ClientAspWsImpot]. Це робиться за допомогою провідника Windows. Щоб ця папка з’явилася у вирішенні Web Developer, потрібно оновити рішення за допомогою кнопки [2].
  • Потім додати їх до посилань проекту [3,4,5]. DLL-файли, на які є посилання, автоматично копіюються в папку /bin проекту [6].

Тепер ми маємо DLL-файли, необхідні для роботи Spring, а також реалізовано клієнтський рівень віддаленого веб-сервісу. Хоча код цього сервісу вже присутній, його ще потрібно налаштувати. У версії 9 він налаштовувався за допомогою такого файлу [App.config]:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>

    <spring>
        <context>
            <resource uri="config://spring/objects" />
        </context>
        <objects xmlns="http://www.springframework.net">
            <object name="metier" type="Metier.ImpotMetierWs, ImpotsV9-metier">
            </object>
        </objects>
    </spring>

    <!-- веб-сервіс -->
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
...
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
                    binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
                    contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
        </client>
    </system.serviceModel>
</configuration>

Ми беремо цю конфігурацію без змін і інтегруємо її у файл [web.config] наступним чином:


<?xml version="1.0"?>
<configuration>


    <configSections>
      <sectionGroup name="system.web.extensions"...>
...
      </sectionGroup>
      <!-- початок розділу Spring -->
        <sectionGroup name="spring">
          <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
          <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
      <!-- кінець розділу Spring -->
    </configSections>

  <!-- початок конфігурації Spring -->
  <spring>
    <context>
      <resource uri="config://spring/objects" />
    </context>
    <objects xmlns="http://www.springframework.net">
      <object name="metier" type="Metier.ImpotMetierWs, ImpotsV9-metier">
      </object>
    </objects>
  </spring>
  <!-- кінець конфігурації Spring -->

  <!-- початок конфігурації клієнта віддаленого веб-сервісу -->
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
...
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
                    binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
                    contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
    </client>
  </system.serviceModel>
  <!-- кінець конфігурації клієнта віддаленого веб-сервісу -->

   <!-- інші конфігурації, що вже присутні у згенерованому файлі web.config -->
...
</configuration>

Зверніть увагу, що у рядку 37 вказано порт віддаленої веб-служби. Цей порт може змінюватися, оскільки Visual Developer запускає веб-службу на випадковому порту.

Повернемося до архітектури веб-клієнта, яку нам потрібно створити:

  • [1] буде реалізовано за допомогою [Default.aspx]
  • [2] буде реалізовано за допомогою [Default.aspx.cs]
  • [3] було реалізовано за допомогою DLL [ImpotV9-metier]

Ми щойно реалізували рівень [3]. Переходимо до веб-інтерфейсу [1], реалізованого сторінкою [Default.aspx]. Двічі клацніть на сторінці [Default.aspx], щоб перейти в режим редагування.

Існує два способи створення веб-сторінки:

  • графічно, як у [2]. Для цього потрібно вибрати режим [Design] у [1]. Цю панель кнопок можна знайти внизу, у рядку стану редактора веб-сторінки.
  • за допомогою мови тегів, як у [3]. У цьому випадку слід вибрати режим [Source] у [1].

Режими [Design] та [Source] є двонаправленими: зміна, внесена в режимі [Design], перетворюється на зміну в режимі [Source] і навпаки. Нагадаємо, що веб-форма, яка має відображатися в браузері, має такий вигляд:

  • у [1], форма, що відображається у браузері
  • у [2] — компоненти, використані для її створення
  • [3] — сторінка проектування форми. Вона містить такі елементи:
    • рядок A — дві перемикальні кнопки з іменами RadioButtonOui та RadioButtonNon
    • рядок B — поле введення з назвою TextBoxEnfants та мітка з назвою LabelErreurEnfants
    • рядок C — поле введення з назвою TextBoxSalaire та мітка з назвою LabelErreurSalaire
    • рядок D — мітка з назвою LabelImpot
    • рядок E — дві кнопки з назвами ButtonCalculer та ButtonEffacer

Після розміщення компонента на робочій області відкривається доступ до його властивостей:

  • у [1] — доступ до властивостей компонента
  • у [2] — вікно властивостей компонента [LabelErreurEnfants ]
  • у [3], (ID) — це назва компонента
  • у [4] ми задали червоний колір символам мітки.

Недостатньо просто розмістити компоненти на формі та встановити їхні властивості. Необхідно також організувати їхнє розташування. У графічному інтерфейсі Windows це розташування є абсолютним: компонент перетягують туди, де потрібно. На веб-сторінці все інакше — складніше, але й потужніше. Цей аспект тут розглядатися не буде.

Вихідний код [Default.aspx], згенерований за цим проектом, має такий вигляд:


<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Calculer votre impôt</title>
</head>
<body bgcolor="#ffff99">
  <h2>
    Calculer votre impôt</h2>
  <form id="form1" runat="server">
  <asp:ScriptManager ID="ScriptManager2" runat="server" EnablePartialRendering="true" />
  <asp:UpdatePanel runat="server" ID="UpdatePanelPam">
    <ContentTemplate>
      <div>
      </div>
      <table>
        <tr>
          <td>
            Etes-vous marié(e)
          </td>
          <td>
            <asp:RadioButton ID="RadioButtonOui" runat="server" GroupName="statut" Text="Oui" />
            <asp:RadioButton ID="RadioButtonNon" runat="server" GroupName="statut" Text="Non"
              Checked="True" />
          </td>
        </tr>
        <tr>
          <td>
            Nombre d&#39;дочірні елементи
          </td>
          <td>
            <asp:TextBox ID="TextBoxEnfants" runat="server" Columns="3"></asp:TextBox>
          </td>
          <td>
            <asp:Label ID="LabelErreurEnfants" runat="server" ForeColor="#FF3300"></asp:Label>
          </td>
        </tr>
        <tr>
          <td>
            Salaire annuel
          </td>
          <td>
            <asp:TextBox ID="TextBoxSalaire" runat="server" Columns="8"></asp:TextBox>
          </td>
          <td>
            <asp:Label ID="LabelErreurSalaire" runat="server" ForeColor="#FF3300"></asp:Label>
          </td>
        </tr>
        <tr>
          <td>
            Impôt à payer
          </td>
          <td>
            <asp:Label ID="LabelImpot" runat="server" BackColor="#99CCFF"></asp:Label>
          </td>
        </tr>
      </table>
      <br />
      <table>
        <tr>
          <td>
            <asp:Button ID="ButtonCalculer" runat="server" Text="Calculer" OnClick="ButtonCalculer_Click" />
          </td>
          <td>
            <asp:Button ID="ButtonEffacer" runat="server" Text="Effacer" OnClick="ButtonEffacer_Click" />
          </td>
          <td>
            &nbsp;
          </td>
        </tr>
      </table>
      </div>
    </ContentTemplate>
  </asp:UpdatePanel>
  </form>
</body>
</html>

Компоненти форми можна впізнати за рядками 23, 24, 33, 36, 44, 47, 55, 63 та 66. Решта — це переважно форматування.

Повернемося до архітектури, яку нам потрібно побудувати:

  • [1] було реалізовано за допомогою [Default.aspx]
  • [2] буде реалізовано за допомогою [Default.aspx.cs]
  • [3] було реалізовано за допомогою DLL [ImpotV9-metier]

Рівні [1] та [3] тепер реалізовано. Нам залишається написати шар [2], який генерує форму, надсилає її користувачеві, обробляє її, коли користувач повертає її заповненою, використовує шар [3] для розрахунку податку, генерує веб-сторінку з відповіддю для користувача та надсилає її йому. Саме код [Default.aspx.cs] виконує всю цю роботу:


using System;
using WsImpot;

public partial class _Default : System.Web.UI.Page
{
    protected void ButtonCalculer_Click(object sender, EventArgs e)
    {
  ...
    }
    protected void ButtonEffacer_Click(object sender, EventArgs e)
    {
...
    }
}

Цей код дуже схожий на код класичної форми Windows. У цьому полягає головна перевага технології ASP.NET: немає розриву між моделлю програмування Windows та моделлю веб-програмування ASP.NET. Просто потрібно завжди пам’ятати про таку схему:

Коли в [1] користувач натисне кнопку [Calculer], буде виконано процедуру ButtonCalculer_Click із рядка 6 файлу [Default.aspx.cs]. Але тим часом:

  • значення заповненої форми будуть передані з браузера на веб-сервер за допомогою протоколу HTTP
  • сервер ASP.NET проаналізує запит і перенаправить його на сторінку [Default.aspx]
  • сторінка [Default.aspx] буде ініціалізована.
  • її компоненти (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire, LabelErreurEnfants, LabelErreurSalaire, LabelImpot) будуть ініціалізовані значеннями, які вони мали під час первинного надсилання форми до браузера за допомогою механізму під назвою «ViewState».
  • передані значення будуть присвоєні відповідним компонентам (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire). Отже, якщо користувач вказав 2 як кількість дітей, отримаємо TextBoxEnfants.Text="2".
  • якщо сторінка [Default.aspx] має метод [Page_Load], то він буде виконаний
  • метод [ButtonCalculer_Click] у рядку 6 буде виконано, якщо натиснуто кнопку [Calculer]
  • метод [ButtonEffacer_Click] у рядку 10 буде виконано, якщо було натиснуто кнопку [Effacer]

Між моментом, коли користувач створює подію у своєму браузері, та моментом, коли вона обробляється в [Default.aspx.cs], існує значна складність. Вона прихована, і під час написання обробників подій веб-сторінки можна робити вигляд, ніби її не існує. Але ніколи не слід забувати, що між подією та її обробником лежить мережа, а отже, не можна обробляти такі події миші, як Mouse_Move, які спричиняють ресурсомісткі обміни даними між клієнтом і сервером...

Код обробників кліків на кнопках [Calculer] та [Effacer] є таким, який би ми написали для класичного додатка Windows:


protected void ButtonCalculer_Click(object sender, EventArgs e)
    {
        // перевірка даних
        int nbEnfants;
        bool erreur = false;
        if (!int.TryParse(TextBoxEnfants.Text.Trim(), out nbEnfants) || nbEnfants < 0)
        {
            LabelErreurEnfants.Text = "Valeur incorrecte...";
            erreur = true;
        }
        int salaire;
        if (!int.TryParse(TextBoxSalaire.Text.Trim(), out salaire) || salaire < 0)
        {
            LabelErreurSalaire.Text = "Valeur incorrecte...";
            erreur = true;
        }
        // помилка?
        if (erreur) return;
        // видаляємо можливі помилки
        LabelErreurEnfants.Text = "";
        LabelErreurSalaire.Text = "";
        // сімейний стан
        bool marié = RadioButtonOui.Checked;
        // розрахунок податку
        try
        {
            LabelImpot.Text = String.Format("{0} euros",Global.Metier.CalculerImpot(marié, nbEnfants, salaire));
        }
        catch (Exception ex)
        {
            LabelImpot.Text = ex.Message;
        }
    }
  • щоб зрозуміти цей код, потрібно знати,
    • що на початку виконання форма [Default.aspx] має вигляд, у якому її заповнив користувач. Отже, поля (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire) містять значення, введені користувачем.
    • і що після завершення її виконання користувачеві буде повернуто ту саму сторінку [Default.aspx]. Це відбувається автоматично.

Отже, процедура ButtonCalculer_Click повинна на основі поточних значень полів (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire) встановити значення всіх полів (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire, LabelErreurEnfants, LabelErreurSalaire, LabelImpot) нової сторінки [Default.aspx], яка буде відправлена користувачеві.

У цьому коді немає особливих складнощів. Пояснення потребує лише рядок 27. У ньому використовується метод CalculerImpot для поля Global.Metier, яке ще не зустрічалося. Ми повернемося до цього пізніше.

Метод ButtonEffacer_Click виглядає наступним чином:


    protected void ButtonEffacer_Click(object sender, EventArgs e)
    {
        // очищення форми
        TextBoxEnfants.Text = "";
        TextBoxSalaire.Text = "";
        LabelImpot.Text = "";
        LabelErreurEnfants.Text = "";
        LabelErreurSalaire.Text = "";
}

Повернімося до архітектури, яку ми маємо побудувати:

  • [1] було реалізовано за допомогою [Default.aspx]
  • [2] було реалізовано за допомогою [Default.aspx.cs]
  • [3] було реалізовано за допомогою DLL [ImpotV9-metier]

Нам залишається об'єднати ці три шари. По суті, це означає:

  • створити екземпляр шару [3] під час запуску програми
  • розміщення посилання на неї в місці, звідки веб-сторінка [Default.aspx.cs] зможе отримувати її щоразу, коли вона буде ініціалізована і коли їй буде потрібно обчислити податок.

Це не нова проблема. Вона вже виникала під час побудови віддаленого веб-сервісу та розглядалася в розділі 12.4.1. Відомо, що рішення полягає в:

  • створити файл [Global.asax], пов’язаний із класом [Global.cs]
  • інстанціювати шар [3] у методі Application_Start класу [Global.cs]
  • вставити посилання на шар [3] у статичне поле класу [Global.cs], оскільки термін існування цього класу збігається з терміном існування додатка.

Отже, наш веб-проект розвивається наступним чином:

  • до [1], файл [Global.asax]
  • у [2], відповідний код [Global.cs]. Папка [App_Code], у якій знаходиться цей файл, за замовчуванням відсутня у веб-рішенні. Створіть її за допомогою [3].

Файл Global.asax має такий вигляд:


<%@ Application Language="C#" Inherits="WsImpot.Global"%>

Код [Global.cs] має такий вигляд:


using System;
using Metier;
using Spring.Context.Support;
namespace WsImpot
{
    public class Global : System.Web.HttpApplication
    {
        // бізнес-шар
        public static IImpotMetier Metier;

        // метод, що виконується під час запуску програми
        private void Application_Start(object sender, EventArgs e)
        {
            // інстанції шарів [metier] та [dao]
            Metier = ContextRegistry.GetContext().GetObject("metier") as IImpotMetier;
        }
    }
}
  • рядок 6: клас називається Global і входить до простору імен WsImpot (рядок 4). Отже, її повна назва — WsImpot.Global, і саме цю назву потрібно вказати в атрибуті Inherits класу Global.asax.
  • рядок 6: відомо, що клас, пов’язаний із Global.asax, обов’язково має походити від класу System.Web.HttpApplication.
  • рядок 12: метод Application_Start, що виконується під час запуску веб-додатка.
  • рядок 15: створюється екземпляр шару [metier] (шар [3] додатка, що розробляється) за допомогою Spring та наступної конфігурації в [web.config]:

  <!-- об'єкти Spring -->
  <spring>
    <context>
      <resource uri="config://spring/objects" />
    </context>
    <objects xmlns="http://www.springframework.net">
      <object name="metier" type="Metier.ImpotMetierWS, ImpotsV9-metier">
      </object>
    </objects>
</spring>

Клас [Metier.ImpotMetierWS] із рядка (g) вище знаходиться у [ImpotsV9-metier.dll].

Посилання на створений шар [metier] вноситься у статичне поле рядка 9. Саме це поле використовується у рядку 27 процедури ButtonCalculer_Click:


LabelImpot.Text = String.Format("{0} euros",Global.Metier.CalculerImpot(marié, nbEnfants, salaire));

Ми готові до тестування. Необхідно запустити SGBD MySQL5, віддалений веб-сервіс, і записати порт, на якому він працює:

  

Після цього потрібно перевірити, чи у файлі [web.config] веб-клієнта вказано правильний порт віддаленого веб-сервісу:

Після цього веб-клієнт віддаленого веб-сервісу можна запустити за допомогою комбінації клавіш Ctrl-F5:

  

12.6. Java-консольний клієнт для веб-сервісу розрахунку податків

Щоб продемонструвати, що веб-сервіси доступні для клієнтів, написаних на будь-якій мові програмування, ми напишемо простий консольний Java-клієнт. Архітектура клієнт-серверної програми буде такою:

  • клієнт [1] буде написаний на Java
  • сервер [2] написаний на C#

Спочатку ми змінимо одну деталь у нашому веб-сервісі для розрахунку податків. Його поточне визначення у файлі [ServiceImpot.cs] виглядає так:


...
public class ServiceImpot : System.Web.Services.WebService
{

    [WebMethod]
    public int CalculerImpot(bool marié, int nbEnfants, int salaire)
    {
        return Global.Metier.CalculerImpot(marié, nbEnfants, salaire);
    }

}

Тестування показало, що акцент у параметрі marié у рядках 6 та 8 може стати причиною проблем із взаємодією між Java та C#. Ми застосуємо таке нове визначення:


...
public class ServiceImpot : System.Web.Services.WebService
{

    [WebMethod]
    public int CalculerImpot(bool marie, int nbEnfants, int salaire)
    {
        return Global.Metier.CalculerImpot(marie, nbEnfants, salaire);
    }

}

Цей сервіс буде розміщено в новому проєкті Web Developer під назвою WsImpotsSansAccents. URL-адреса веб-сервісу тоді матиме вигляд [/WsImpotSansAccents/ServiceImpot.asmx].

Image

Для написання Java-клієнта ми використаємо IDE NetBeans [http://www.netbeans.org/]:

  • у [1] створіть новий проєкт
  • у [2,3], виберіть проект Java типу «Java Application».
  • в [4], перейдіть до наступного кроку
  • у [5], надати назву проекту
  • у [6] вкажіть папку, в якій для проекту буде створено підпапку з його назвою
  • у [7] вкажіть ім’я класу, який міститиме метод main, що виконується під час запуску програми
  • у [8], завершити роботу майстра
  • в [9]: згенерований Java-проект
  • [10]: клацніть правою кнопкою миші на проекті, щоб згенерувати клієнт веб-сервісу для розрахунку податку
  • в [11], URL-адреса файлу, що описує веб-сервіс розрахунку податків:

http://localhost:1089/WsImpotSansAccents/ServiceImpot.asmx?WSDL

Цей URL-адресу належить службі [ServiceImpot.asmx], до якої додається параметр ?WSDL. Документ, розташований за цією URL-адресою, описує мовою XML, що саме може робити служба [15]. Це стандартний елемент веб-служби.

  • у [12] — пакет (еквівалент простору імен у C#), у який слід помістити класи, що будуть згенеровані
  • у [13] залиште значення за замовчуванням
  • у полі [14] завершіть роботу майстра
  • У [16] імпортований веб-сервіс було інтегровано в проект Java. Він підтримує два протоколи зв’язку: Soap та Soap12.
  • У [17] — клас [Main], у якому ми будемо використовувати згенерований клієнт
  • у [18] ми вставимо код у метод [main]. Поставте курсор у місце, куди потрібно вставити код, клацніть правою кнопкою миші та виберіть опцію [19]
  • в [20], вкажіть, що ви хочете згенерувати код виклику функції CalculerImpot віддаленої служби розрахунку податків, а потім натисніть «ОК».

Код, згенерований у [Main], має такий вигляд:

public class Main {

    public static void main(String[] args) {
         // TODO — тут розміщується код логіки додатка
      try { // Виклик операції веб-сервісу
        wsimpot.ServiceImpot service = new wsimpot.ServiceImpot();
        wsimpot.ServiceImpotSoap port = service.getServiceImpotSoap();
         // TODO ініціалізація аргументів операції WS тут
        boolean marie = false;
        int nbEnfants = 0;
        int salaire = 0;
         // TODO обробка результату тут
        int result = port.calculerImpot(marie, nbEnfants, salaire);
        System.out.println("Result = "+result);
      } catch (Exception ex) {
         // TODO обробка користувацьких винятків тут
      }
    }
}

Згенерований код показує, як викликати функцію CalculerImpot віддаленої служби розрахунку податків. Якщо провести паралель із тим, що ми розглядали в C#, змінна port у рядку 7 є еквівалентом клієнта, що використовується в C#. Ми не будемо далі коментувати цей код. Ми переробляємо його наступним чином:

import wsimpot.ServiceImpot;
public class Main {
    public static void main(String[] args) {
      try {
         // викликається функція CalculerImpot веб-сервісу
        System.out.println(String.format("Montant à payer : %d euros", new ServiceImpot().getServiceImpotSoap().calculerImpot(true, 2, 60000)));
      } catch (Exception ex) {
        System.out.println(String.format("L'erreur suivante s'est produite %s",ex.getMessage()));
      }
    }
}
  • рядок 1: імпортуємо клас ServiceImpot, який представляє клієнт, згенерований майстром.
  • рядок 6: ми викликаємо віддалений метод CalculerImpot, дотримуючись процедури, вказаної у коді, згенерованому в main.

Результати, отримані в консолі під час виконання (F6), такі:

init:
deps-jar:
wsimport-init:
wsimport-client-check-ServiceImpot.asmx:
wsimport-client-ServiceImpot.asmx:
wsimport-client-generate:
wsimport-client-compile:
Compiling 1 source file to C:\data\2007-2008\netbeans\ClientNetbeansPourServiceImpotDotNet\build\classes
compile:
run:
Montant à payer : 4282 euros
BUILD SUCCESSFUL (total time: 7 seconds)