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-код тестової форми:
- рядок 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):
- рядки 1–5: повідомлення, надіслані клієнтом putty
- рядок 1: команда POST
- рядки 6–10: відповідь сервера. Вона означає, що клієнт може надсилати значення у форматі POST.
- рядок 11: значення, надіслані у форматі param1=val1¶m2=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:
- рядок 8: метод DisBonjourALaDame є клієнтським аналогом методу DisBonjourALaDame веб-сервісу. Додаток для Windows використовуватиме віддалений метод DisBonjourALaDame через локальний метод Client.WsHello.ServiceSoapClient.DisBonjourALaDame у такому вигляді:
Створений файл [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 арифметичні операції:
- додати(a,b), що повертає a+b
- віднімати(a,b), що поверне a-b
- множити(a,b), що поверне a*b
- ділення(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:

Пропонуємо читачеві протестувати ці 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 і запускаємо метод. Отримуємо такий результат, який є правильним:

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'дочірні елементи
</td>
<td>
<asp:TextBox ID="TextBoxEnfants" runat="server" Columns="3"></asp:TextBox>
</td>
<td>
<asp:Label ID="LabelErreurEnfants" runat="server" ForeColor="#FF3300"></asp:Label>
</td>
</tr>
<tr>
<td>
Salaire annuel
</td>
<td>
<asp:TextBox ID="TextBoxSalaire" runat="server" Columns="8"></asp:TextBox>
</td>
<td>
<asp:Label ID="LabelErreurSalaire" runat="server" ForeColor="#FF3300"></asp:Label>
</td>
</tr>
<tr>
<td>
Impôt à payer
</td>
<td>
<asp:Label ID="LabelImpot" runat="server" BackColor="#99CCFF"></asp:Label>
</td>
</tr>
</table>
<br />
<table>
<tr>
<td>
<asp:Button ID="ButtonCalculer" runat="server" Text="Calculer" OnClick="ButtonCalculer_Click" />
</td>
<td>
<asp:Button ID="ButtonEffacer" runat="server" Text="Effacer" OnClick="ButtonEffacer_Click" />
</td>
<td>
</td>
</tr>
</table>
</div>
</ContentTemplate>
</asp:UpdatePanel>
</form>
</body>
</html>
Компоненти форми можна впізнати за рядками 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].

Для написання 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], має такий вигляд:
Згенерований код показує, як викликати функцію CalculerImpot віддаленої служби розрахунку податків. Якщо провести паралель із тим, що ми розглядали в C#, змінна port у рядку 7 є еквівалентом клієнта, що використовується в C#. Ми не будемо далі коментувати цей код. Ми переробляємо його наступним чином:
- рядок 1: імпортуємо клас ServiceImpot, який представляє клієнт, згенерований майстром.
- рядок 6: ми викликаємо віддалений метод CalculerImpot, дотримуючись процедури, вказаної у коді, згенерованому в main.
Результати, отримані в консолі під час виконання (F6), такі:



































































