7. Серверні компоненти ASP — 1
7.1. Introduction
У цьому розділі ми описуємо технологію, рекомендовану в ASP.NET для побудови користувацького інтерфейсу. Ми знаємо, що обробка сторінки .aspx веб-сервером складається з двох чітко розмежованих етапів:
- спочатку виконується контролер сторінки. Він складається з коду, розташованого або безпосередньо на самій сторінці .aspx (рішення WebMatrix), або в окремому файлі (рішення Visual Studio.NET).
- Потім виконується код візуалізації сторінки .aspx, який перетворюється на код HTML, що надсилається клієнту.

ASP.NET пропонує три бібліотеки тегів для написання коду оформлення сторінки:
- класичні теги HTML. Саме їх ми використовували дотепер.
- серверні теги HTML
- теги webforms
Незалежно від того, яка бібліотека тегів використовується, роль контролера сторінки залишається незмінною. Він повинен обчислювати значення динамічних параметрів, що з’являються у коді оформлення. Дотепер ці динамічні параметри були простими: це були об’єкти типу [String]. Отже, якщо у коді оформлення є тег <%=nom%>:
- контролер сторінки оголошує змінну [nom] типу [String] і обчислює її значення
- коли контролер сторінки завершив свою роботу і виконується код представлення для генерації відповіді HTML, тег <%=nom%> замінюється значенням, обчисленим кодом контролера
Відомо, що поділ на контролер і представлення є довільним і що на одній сторінці можна поєднувати код контролера та код представлення. Ми пояснили, чому цей метод не рекомендується, і надалі будемо дотримуватися поділу на контролер і представлення.
Теги [HTML serveur] та [WebForms] дають змогу вводити в код представлення об’єкти, складніші за простий об’єкт [String]. Іноді це має реальну користь. Розглянемо приклад форми зі списком. Цей список має відображатися клієнту за допомогою HTML-коду, який виглядає приблизно так:
<select name="uneListe" size="3">
<option value="opt1">option1</option>
<option value="opt2">option2</option>
<option value="opt3" selected>option3</option>
</select>
Вміст списку та опція, яку потрібно вибрати, є динамічними елементами, тому їх має генерувати контролер сторінки. Ми вже стикалися з цією проблемою і вирішили її, додавши до коду шаблону тег
Цей тег буде замінено значенням [String] змінної [uneListeHTML]. Це значення, обчислене контролером, має відповідати коду HTML зі списку c.a.d. «<select name=..>...</select>». Це не надто складно, і це виглядає як елегантне рішення, яке дозволяє уникнути розміщення коду генерації безпосередньо в презентаційній частині сторінки. У цьому випадку туди довелося б вставляти цикл із перевірками, що значно «забруднило» б її. Проте цей метод має один недолік. Розділення сторінки на контролер і презентацію також слугує для розмежування двох сфер компетенції:
- сферу компетенції розробника .NET, який відповідає за контролер сторінки,
- сферу компетенції графічного дизайнера, який відповідає за презентаційну частину сторінки
Тут видно, що генерацію коду HTML списку перенесено до контролера. Графічний дизайнер може захотіти вплинути на цей код HTML, щоб змінити «візуальний» вигляд списку. Він буде змушений працювати в частині [contrôleur] і, отже, вийти за межі своєї сфери компетенції, що несе ризик випадкового внесення помилок у код.
Бібліотеки серверних тегів вирішують цю проблему. Вони пропонують об’єкт, що представляє список HTML. Так, бібліотека [WebForms] пропонує такий тег:
Цей тег представляє об’єкт типу [ListBox], яким може керувати контролер сторінки. Цей об’єкт має властивості для представлення різних опцій зі списку HTML та позначення обраної опції. Отже, контролер сторінки присвоїть цим властивостям відповідні значення. Під час виконання частини, що відповідає за відображення, тег
буде замінена кодом HTML, що представляє об’єкти [uneListe] та c.a.d. код «<select ..>...</select>». Наразі немає принципової різниці з попереднім методом, окрім того, що це об’єктно-орієнтований підхід до кодування, що є цікавим. Повернімося до нашого графічного дизайнера, якому потрібно змінити «зовнішній вигляд» списку. Серверні теги мають атрибути стилю (BackColor, Bordercolor, BorderWidth, ...), які дозволяють задати візуальний вигляд відповідного об’єкта HTML. Таким чином, можна написати:
<asp:ListBox id="ListBox1" runat="server" BackColor="#ffff99"></asp:ListBox></P>
Перевага полягає в тому, що графічний дизайнер залишається в коді представлення, щоб внести ці зміни. Це безперечна перевага порівняно з попереднім методом. Отже, бібліотеки серверних тегів спрощують побудову частини сторінок, що відповідає за представлення, яку в попередніх розділах ми називали користувацьким інтерфейсом. Мета цього розділу — ознайомити з ними. Ми побачимо, що іноді вони пропонують складні об’єкти, такі як календарі або таблиці, пов’язані з джерелами даних. Вони є розширюваними, c.a.d, тобто користувач може створити власну бібліотеку тегів. Таким чином, він може створити тег, що генерує банер на сторінці. Усі сторінки, що використовують цей тег, матимуть однаковий банер.
Генерація HTML, виконана для тегу, адаптується до типу браузера клієнта. Коли браузер надсилає запит до веб-сервера, він включає серед своїх заголовків HTTP заголовок [User-Agent: xx], де [xx] ідентифікує клієнта. Ось приклад:
Завдяки цій інформації веб-сервер може визначити можливості клієнта, зокрема тип коду HTML, з яким він може працювати. Адже з часом з’явилося кілька версій мови HTML. Сучасні браузери підтримують найновіші версії цієї мови, чого не можуть зробити старіші браузери. Залежно від заголовка HTTP [User-Agent:], який клієнт надіслав серверу, сервер надішле йому версію HTML, яку той зможе зрозуміти. Це цікава та корисна ідея, оскільки розробник не мусить турбуватися про тип браузера клієнта, який використовує його додаток.
Нарешті, вдосконалені версії IDE, такі як Visual Studio.NET, WebMatrix тощо, дають змогу створювати веб-інтерфейс у стилі Windows. Ці інструменти, хоча й не є обов’язковими, проте надають розробнику вирішальну допомогу. Розробник створює веб-інтерфейс за допомогою графічних компонентів, які розміщує на цьому інтерфейсі. Він має прямий доступ до властивостей кожного з компонентів інтерфейсу, які може налаштовувати на свій розсуд. Ці властивості будуть перетворені в коді HTML, що визначає вигляд інтерфейсу, на атрибути тегу <asp:> відповідного компонента. Перевага для розробника полягає в тому, що йому не потрібно запам’ятовувати ані список, ані синтаксис атрибутів кожного тегу. Це значна перевага, коли ви не досконало знаєте бібліотеки серверних тегів, що пропонуються ASP.NET. Коли цей синтаксис освоєно, деякі розробники можуть віддати перевагу безпосередньому кодуванню тегів у коді візуалізації сторінки, оминаючи етап графічного проектування. У такому разі IDE більше не потрібний. Достатньо простого текстового редактора. Залежно від способу роботи акцент робиться або на компонентах (використання IDE), або на тегах (використання текстового редактора). Ці два терміни є еквівалентними. Компонент — це об’єкт, яким керуватиме код управління сторінкою. IDE надає нам доступ до його властивостей на етапі проектування. Значення, задані для цих властивостей, негайно перетворюються на атрибути тегу компонента в коді візуалізації. На етапі виконання код управління сторінки буде опрацьовувати компонент і присвоювати значення деяким його властивостям. Код представлення, у свою чергу, згенерує код компонента HTML, використовуючи, з одного боку, атрибути, задані на етапі проектування для відповідного серверного тегу, а з іншого — значення властивостей компонента, обчислені кодом управління.
7.2. Контекст виконання прикладів
Ми проілюструємо проектування веб-інтерфейсів на основі серверних компонентів за допомогою програм, контекст виконання яких здебільшого буде таким:
- веб-додаток складатиметься з єдиної сторінки P, що містить форму F,
- клієнт надсилатиме свій перший запит безпосередньо на цю сторінку P. Це полягатиме у запиті URL-адреси сторінки P за допомогою браузера. Отже, на цю URL-адресу P буде надіслано запит GET. Сервер надасть сторінку P і, відповідно, форму F, що міститься на ній,
- користувач заповнить її та надішле, c.a.d. тобто виконає дію, яка змусить браузер надіслати форму F на сервер. Операція POST браузера завжди буде спрямована на сторінку P. Сервер знову надасть сторінку P із формою F, зміст якої міг бути змінений внаслідок дій користувача.
- Потім повторяться кроки 2 і 3.
Це цілком особливий процес виконання, поза яким деякі концепції, викладені нижче, перестають працювати. Ми більше не перебуваємо в контексті архітектури MVC, в якій багатосторінковий додаток керується певною сторінкою, яку ми назвали «контролером додатка». У такій архітектурі POST форм націлені на контролер, а не на самі форми. Однак ми побачимо, що створення форми з серверними компонентами передбачає, що ця форма надсилається сама собі.
7.3. Компонент Label
7.3.1. Використання
Тег <asp:label> дозволяє вставляти динамічний текст у код представлення сторінки. Отже, він не виконує нічого більше, ніж тег <%=variable%>, який ми використовували досі. Вивчення цього першого тегу дозволить нам розібратися в механізмі роботи серверних тегів. Створимо сторінку з контрольною частиною [form1.aspx.vb] та частиною відображення [form1.aspx]. Мета — відобразити час:

Ця проблема вже розглядалася в розділі 2, і читачеві пропонується звернутися до нього, якщо він бажає дізнатися, як її було вирішено. Код візуалізації [form1.aspx] має такий вигляд:
<%@ page src="form1.aspx.vb" inherits="form1" AutoEventWireup="false" %>
<HTML>
<HEAD>
<title>Webforms</title>
</HEAD>
<body>
<asp:Label Runat="server" ID="lblHeure" />
</body>
</HTML>
Ми вводимо тег <asp:label>. У бібліотеках тегів атрибут [runat="server"] є обов'язковим. Атрибут ID ідентифікує компонент. Контролер повинен посилатися на нього за цим ідентифікатором. Код контролера [form1.aspx.vb] такий:
Imports System.Web.UI.WebControls
Public Class form1
Inherits System.Web.UI.Page
Protected lblHeure As Label
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Init
' зберігає поточний запит у request.txt у папці сторінки
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
' встановлює час у lblHeure
lblHeure.Text = "Il est " + Date.Now.ToString("T")
End Sub
End Class
Контролер повинен присвоїти значення об’єкту [lblHeure] типу [System.Web.UI.WebControls.Label]. Усі об’єкти, що відображаються тегами <asp:>, належать до простору імен [System.Web.UI.WebControls]. Тому цей простір імен можна імпортувати системно:
Imports System.Web.UI.WebControls
Об’єкт [Label] має різні властивості, серед яких властивість [Text], що представляє текст, який буде відображатися відповідним тегом <asp:label>. Тут ми задаємо в цій властивості поточний час. Ми робимо це у процедурі [Form_Load] контролера, яка виконується систематично. У процедурі [Form_Init], яка також завжди виконується, але перед процедурою [Form_Load], ми зберігаємо запит клієнта у файлі [request.txt] у папці додатка. Ми матимемо нагоду розглянути цей файл, щоб зрозуміти деякі аспекти роботи сторінок, що використовують серверні теги.
Об’єкт [Label] має численні властивості, методи та події. Читачеві пропонується ознайомитися з документацією щодо класу [Label], щоб дізнатися про них. Далі ми будемо діяти саме так. Для кожного тегу ми наводимо лише ті кілька властивостей, які нам потрібні.
7.3.2. Тестування
Ми розміщуємо файли (form1.aspx, form1.aspx.vb) у папці <application-path> і запускаємо Cassini з параметрами (<application-path>,/form1). Потім ми запитуємо URL-адресу [http://localhost/form1/form1.aspx]. Отримуємо такий результат:

Код HTML, отриманий браузером, має такий вигляд:
<HTML>
<HEAD>
<title>Webforms</title>
</HEAD>
<body>
<span id="lblHeure">Il est 19:39:37</span>
</body>
</HTML>
Видно, що тег сервера
<asp:Label Runat="server" ID="lblHeure" />
був перетворений на такий код HTML:
Саме властивість [Text] об’єкта [lblHeure] було розміщено між тегами <span> та </span>. Запит, зроблений клієнтом і збережений у [request.txt], має такий вигляд:
GET /form1/form1.aspx HTTP/1.1
Cache-Control: max-age=0
Connection: keep-alive
Keep-Alive: 300
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
Все цілком нормально.
7.3.3. Створення додатка за допомогою WebMatrix
Ми створили код для відображення сторінки [form1.aspx] вручну. Цей метод можна використовувати, якщо ви знаєте теги. У такому разі для створення інтерфейсу користувача достатньо простого текстового редактора. На початку часто потрібний інструмент графічного дизайну в поєднанні з автоматичним генеруванням коду, оскільки ви ще не знаєте синтаксису необхідних тегів. Тепер ми створюємо той самий додаток за допомогою інструменту WebMatrix. Після запуску WebMatrix ми обираємо опцію [File/New File]:

Ми створюємо сторінку ASP.NET під назвою [form2.aspx]. Після підтвердження попереднього майстра ми отримуємо вікно редагування сторінки [form2.aspx]:


Нагадаємо, що WebMatrix об’єднує код управління сторінкою та код її візуального оформлення в один файл, у даному випадку — [form2.aspx]. На вкладці [All] відображається вміст цього текстового файлу. Вже зараз можна помітити, що він не порожній:

Недоліком такого роду інструментів є те, що вони часто генерують зайвий код. Так сталося й у цьому випадку: файл WebMatrix згенерував тег <form>, хоча ми не будемо створювати форму... Крім того, можна помітити, що в документі відсутній тег <title>. Ми негайно виправимо ці дві проблеми, щоб отримати таку нову версію:

Те, що ми називаємо кодом контролера, буде вставлено між тегами <script> та </script>, що забезпечує принаймні візуальне розділення двох типів коду: контрольного та презентаційного. Повертаємося на вкладку [Design], щоб намалювати наш інтерфейс. Список компонентів доступний у вікні інструментів ліворуч від вікна проектування:

Вікно інструментів надає доступ до двох типів компонентів:
- компоненти [WebControls], які перетворюються на теги <asp:>
- компоненти [HTML Elements], які перетворюються на класичні теги HTML. Однак до атрибутів тегу HTML можна додати атрибут [runat="server"]. У цьому випадку тег HTML та його атрибути доступні контролеру через об’єкт, властивості якого відповідають властивостям тегу HTML, який він представляє. Раніше ці теги називалися серверними тегами HTML.
Двічі клацнімо на компоненті [Label] у списку елементів управління [WebControls]. На вкладці [Design] ми отримаємо такий результат:

На вкладці [All] код змінився на такий:
<%@ Page Language="VB" %>
<html>
<head>
<title>webforms</title>
</head>
<body>
<asp:Label id="Label1" runat="server">Label</asp:Label>
</body>
</html>
Перш за все можна помітити, що тег <script> зник. Був згенерований тег <asp:label>. Він має ім'я [Label1] та значення [Label]. Повернімося на вкладку [Design], щоб змінити ці два значення. Клацнімо один раз на компоненті [Label], щоб у правому нижньому куті з’явилося вікно властивостей цього компонента:

Читачеві пропонується переглянути властивості об’єкта [Label]. Нас тут цікавлять дві:
- Text: це текст, який має відображати мітка — вводимо порожній рядок (c.a.d. нічого)
- ID: це його ідентифікатор — ми вказуємо lblHeure
Вкладка [Design] виглядає так:

а код [All] стає таким:
<%@ Page Language="VB" %>
<html>
<head>
<title>webforms</title>
</head>
<body>
<asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>
Частина сторінки, присвячена оформленню, завершена. Залишилося написати код управління, який відповідає за введення часу у властивість [Text] об’єкта [lblHeure]. У вкладці [All] додаємо такий код:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
' встановлює час у lblHeure
lblHeure.Text = "Il est " + Date.Now.ToString("T")
End Sub
</script>
<html>
<head>
<title>webforms</title>
</head>
<body>
<asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>
Слід зауважити, що в коді контролера об’єкт [lblHeure] не оголошується, як це робилося раніше:
Protected lblHeure As New System.Web.UI.WebControls.Label
Дійсно, всі серверні компоненти <asp:> у частині представлення оголошуються неявно в частині коду контролера. Тому їхнє оголошення призводить до помилки компіляції, яка вказує, що об’єкт уже оголошено. Ми готові до виконання. Вибираємо опцію [View/Start] або ярлик [F5]. Cassini автоматично запускається з такими параметрами:

Ми приймаємо ці значення. Системний браузер за замовчуванням автоматично запускається, щоб завантажити URL-адресу [http://localhost/form2.aspx]. Ми отримуємо такий результат:

Надалі ми будемо переважно використовувати інструмент WebMatrix, щоб полегшити створення та тестування коротких програм, які ми будемо писати.
7.4. Компонент Literal
7.4.1. Використання
Тег <asp:literal> дозволяє вставляти динамічний текст у код представлення сторінки, подібно до тегу <asp:label>. Його головним атрибутом є [Text], який представляє текст, що буде вставлений без змін у потік HTML сторінки. Цього тегу достатньо, якщо ви не плануєте форматувати текст, який хочете вставити у потік HTML. Дійсно, якщо клас [Label] дозволяє форматувати текст за допомогою таких атрибутів, як [BorderColor, BorderWidth, Font, ...], то клас [Literal] не має жодного з цих атрибутів. Читач може повністю скористатися попереднім прикладом, замінивши компонент [Label] на компонент [Literal].
7.5. Компонент Button
7.5.1. Використання
Тег <asp:Button> дозволяє вставити у форму кнопку типу [Submit], яка забезпечує управління подіями, подібне до того, що зустрічається у додатках Windows. Саме цей аспект ми й хочемо детальніше розглянути тут. Створюємо таку сторінку [form3.aspx]:

Ця сторінка, створена за допомогою WebMatrix, має три компоненти:
№ | назва | тип | властивості | роль |
1 | Button | text=Кнопка1 | кнопка «Відправити» | |
2 | Button | text=Кнопка2 | кнопка «Відправити» | |
3 | Мітка | text= | інформаційне повідомлення |
Код, згенерований WebMatrix для цієї частини, є таким:
<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form runat="server">
<p>
<asp:Button id="Button1" runat="server" Text="Bouton1"></asp:Button>
<asp:Button id="Button2" runat="server" Text="Bouton2"></asp:Button>
</p>
<p>
<asp:Label id="lblInfo" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
Ми бачимо компоненти [Button] та [Label], які використовувалися під час графічного оформлення сторінки, у тегах <asp:>. Зверніть увагу на тег <form runat="server">, який було згенеровано автоматично. Це серверний тег HTML, c.a.d — класичний тег HTML, який, однак, представлений об’єктом, яким може керувати контролер. Код тегу <form> буде згенеровано на основі значення, яке контролер присвоїть цьому об’єкту.
Додамо в частину коду, що відповідає контролеру, процедуру [Page_Init], яка обробляє подію [Init] на сторінці. У ній розмістимо код, який зберігає запит клієнта у файлі [request.txt]. Це знадобиться нам для розуміння механізму роботи кнопок.
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' зберігає поточний запит у request.txt у файлі сторінки
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
</script>
Слід зауважити, що ми не розмістили клаузулу [Handles MyBase.Init] після оголошення процедури [Page_Init]. Справа в тому, що подія [Init] об’єкта [Page] має обробник за замовчуванням, який називається [Page_Init]. Якщо використовувати це ім’я обробника, клаузула [Handles Page.Init] стає зайвою. Однак її наявність не спричиняє помилки.
7.5.2. Тестування
Ми запускаємо додаток під WebMatrix за допомогою [F5]. Ми отримуємо таку сторінку:

Код HTML, отриманий браузером, має такий вигляд:
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />
<p>
<input type="submit" name="Button1" value="Bouton1" id="Button1" />
<input type="submit" name="Button2" value="Bouton2" id="Button2" />
</p>
<p>
<span id="lblInfo"></span>
</p>
</form>
</body>
</html>
Звернімо увагу на наступні моменти:
- тег <form runat="server"> перетворився на тег HTML
Було виправлено два атрибути: [method="post"] та [action="form3.aspx"]. Чи можна присвоїти цим атрибутам різні значення? Ми спробуємо прояснити це питання трохи далі. Тут варто зауважити, що форма буде відправлена на URL-адресу [form3.aspx]. Також було задано ще два атрибути [name, id]. У більшості випадків вони ігноруються. Однак якщо сторінка містить код JavaScript, що виконується на стороні браузера, атрибут [name] тегу <form> є корисним.
- Теги <asp:button> стали тегами HTML для кнопок [submit]. Отже, натискання на будь-яку з цих кнопок спричинить «відправлення» форми [_ctl10] на URL-адресу [form3.aspx].
- тег <asp:label> перетворився на тег HTML <span>
- було згенеровано приховане поле [__VIEWSTATE] із дивним значенням:
Це поле у закодованій формі відображає стан форми, надісланої клієнту. Цей стан відображає значення всіх її компонентів. Оскільки [__VIEWSTATE] є частиною форми, його значення буде надіслано разом з рештою даних на сервер. Це дозволить серверу дізнатися, який компонент форми змінив своє значення, і, можливо, прийняти відповідні рішення. Ці рішення матимуть вигляд подій типу «значення такого-то TextBox змінилося».
7.5.3. Запити клієнта
Отримавши сторінку [form3.aspx] у браузері, завантажимо її ще раз, а потім розглянемо запит, який браузер надіслав для її отримання. Нагадаємо, що наш додаток зберігає його у файлі [request.txt] у папці додатка:
GET /form3.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
Перед нами класичний GET. Давайте натиснемо на кнопку [Bouton1] на сторінці в браузері. На перший погляд, нічого не відбувається. Проте ми знаємо, що форма була відправлена. Про це свідчить код HTML на сторінці. Це підтверджується новим вмістом [request.txt]:
POST /form3.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Content-Length: 80
Content-Type: application/x-www-form-urlencoded
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
Referer: http://localhost/form3.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__VIEWSTATE=dDwxNTY0NjIwMjUwOzs%2B2mcnJczeuvF2PEfvmtv7uiUhWUw%3D&Button1=Bouton1
Перший заголовок HTTP чітко вказує, що клієнт здійснив запит POST на URL-адресу [/form3.aspx]. Останній рядок показує відправлені значення:
- значення прихованого поля __VIEWSTATE
- значення кнопки, на яку натиснули
Якщо ми натиснемо на [Bouton2], значення, надіслані браузером, будуть такими:
Значення прихованого поля залишається незмінним, але відправлено саме значення [Button2]. Таким чином, сервер може визначити, яка кнопка була використана. Він використає цю інформацію для запуску події, яку сторінка зможе обробити після її завантаження.
7.5.4. Обробка події Click об’єкта Button
Нагадаємо, як працює сторінка .aspx. Вона є об’єктом, похідним від класу [Page]. Назвемо похідний клас [unePage]. Коли сервер отримує запит на таку сторінку, об’єкт типу [unePage] створюється за допомогою оператора new unePage(...). Потім сервер генерує дві події з іменами [Init] та [Load] у зазначеному порядку. Об’єкт [unePage] може обробляти їх, надаючи обробники подій [Page_Init] та [Page_Load]. Далі будуть згенеровані інші події. Ми ще повернемося до цього пізніше. Якщо запит клієнта — це POST, сервер згенерує подію [Click] для кнопки, яка викликала цю подію POST. Якщо клас [unePage] передбачив обробник для цієї події, він буде викликаний. Розглянемо цей механізм на прикладі WebMatrix. На вкладці [Design] класу [form3.aspx] двічі клацніть на кнопці [Bouton1]. Після цього ми автоматично потрапляємо на вкладку [Code], у тіло процедури з назвою [Button1_Click]. Щоб краще зрозуміти, перейдемо на вкладку [All] і розглянемо весь код. Були внесені такі зміни:
<%@ Page Language="VB" %>
<script runat="server">
...
Sub Button1_Click(sender As Object, e As EventArgs)
End Sub
</script>
<html>
...
<body>
<form runat="server">
...
<asp:Button id="Button1" onclick="Button1_Click" runat="server" Text="Bouton1"></asp:Button>
</form>
</body>
</html>
До тегу <asp:Button> [Button1] додано новий атрибут [onclick="Button1_Click"]. Цей атрибут вказує на процедуру, яка обробляє подію [Click] для об’єкта [Button1], у даному випадку — процедуру [Button1_Click]. Залишилося лише написати цю процедуру:
Sub Button1_Click(sender As Object, e As EventArgs)
' натискання кнопки 1
lblInfo.Text="Vous avez cliqué sur [Bouton1]"
End Sub
Ця процедура вставляє інформаційне повідомлення в мітку [lblInfo]. Ми робимо те саме для кнопки [Bouton2], щоб отримати таку нову сторінку [form3.aspx]:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' збереження поточного запиту в request.txt у папці сторінки
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
Sub Button1_Click(sender As Object, e As EventArgs)
' натискання кнопки 1
lblInfo.Text="Vous avez cliqué sur [Bouton1]"
End Sub
Sub Button2_Click(sender As Object, e As EventArgs)
' натискання кнопки 2
lblInfo.Text="Vous avez cliqué sur [Bouton2]"
End Sub
</script>
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form runat="server">
<p>
<asp:Button id="Button1" onclick="Button1_Click" runat="server" Text="Bouton1"></asp:Button>
<asp:Button id="Button2" onclick="Button2_Click" runat="server" Text="Bouton2" BorderStyle="None"></asp:Button>
</p>
<p>
<asp:Label id="lblInfo" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
Ми запускаємо виконання за допомогою [F5], щоб отримати таку сторінку:

Якщо ми подивимося на отриманий код HTML, то побачимо, що він не змінився порівняно з кодом попередньої версії сторінки:
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />
<p>
<input type="submit" name="Button1" value="Bouton1" id="Button1" />
<input type="submit" name="Button2" value="Bouton2" id="Button2" />
</p>
<p>
<span id="lblInfo"></span>
</p>
</form>
</body>
</html>
Якщо натиснути на [Bouton1], отримаємо таку відповідь:

Код HTML, отриманий у цій відповіді, виглядає так:
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwO3Q8O2w8aTwxPjs+O2w8dDw7bDxpPDU+Oz47bDx0PHA8cDxsPFRleHQ7PjtsPFZvdXMgYXZleiBjbGlxdcOpIHN1ciBbQm91dG9uMV07Pj47Pjs7Pjs+Pjs+Pjs+4oO98Vd244kj0lPMXReWOwJ1WW0=" />
<p>
<input type="submit" name="Button1" value="Bouton1" id="Button1" />
<input type="submit" name="Button2" value="Bouton2" id="Button2" />
</p>
<p>
<span id="lblInfo">Vous avez cliqué sur [Bouton1]</span>
</p>
</form>
</body>
</html>
Ми бачимо, що значення прихованого поля [__VIEWSTATE] змінилося. Це відображає зміну значення компонента [lblInfo].
7.5.5. Події життєвого циклу додатка ASP.NET
У документації ASP.NET наведено перелік подій, що генеруються сервером протягом життєвого циклу додатка:
- під час отримання додатком найпершого запиту буде згенерована подія [Start] для об’єкта [HttpApplication] додатка. Цю подію можна обробити за допомогою процедури [Application_Start] у файлі [global.asax] додатка.
Далі відбуватиметься серія подій, які повторюватимуться для кожного отриманого запиту:
- якщо у запиті не надіслано токен сеансу, запускається новий сеанс і генерується подія [Start] для об’єкта [Session], пов’язаного із запитом. Цю подію можна обробити за допомогою процедури [Session_Start] у файлі [global.asax] додатка.
- Сервер генерує подію [BeginRequest] для об’єкта [HttpApplication]. Її можна обробити за допомогою процедури [Application_BeginRequest] із файлу [global.asax] додатка.
- Сервер завантажує сторінку, запитану у запиті. Він створить екземпляр об’єкта [Page], а потім згенерує дві події для цього об’єкта: [Init], а потім [Load]. Ці дві події можуть оброблятися процедурами [Page_Init] та [Page_Load] на сторінці.
- На основі отриманих відправлених значень сервер згенерує інші події: [TextChanged] для компонента [TextBox], значення якого змінилося; [CheckedChanged] для перемикача, значення якого змінилося; [SelectedIndexChanged] для списку, у якому змінився вибраний елемент, ... Ми матимемо нагоду згадати основні події для кожного з серверних компонентів, які ми будемо презентувати. Кожна подія E на об’єкті з іменем O може оброблятися процедурою з іменем O_E.
- Порядок обробки вищезазначених подій не гарантується. Тому обробники не повинні робити жодних припущень щодо цього порядку. Однак гарантується, що подія [Click] для кнопки, яка спричинила подію POST, обробляється останньою.
- Як тільки сторінка буде готова, сервер надішле її клієнту. Перед цим він генерує подію [PreRender], яку може обробляти процедура [Page_PreRender] на цій сторінці.
- Після відправлення відповіді HTML клієнту сторінка буде вивантажена з пам’яті. При цьому генеруються дві події: [Unload] та [Disposed]. Сторінка може використовувати ці події для звільнення ресурсів.
Додаток також може отримувати події поза межами клієнтського запиту:
- подія [End] для об’єкта [Session] додатка виникає, коли сесія завершується. Це може статися за явною вимогою коду сторінки або через те, що термін дії сесії закінчився. Процедура [Session_End] у файлі [global.asax] обробляє цю подію. Зазвичай у ній звільняються ресурси, отримані в [Session_Start].
- Подія [End] на об’єкті [HttpApplication] додатка виникає під час завершення роботи додатка. Зокрема, це відбувається під час зупинки веб-сервера. Процедура [Application_End] у файлі [global.asax] обробляє цю подію. Зазвичай у ній звільняються ресурси, отримані в [Application_Start].
Слід звернути увагу на наступні моменти:
- попередня модель подій базується на класичному обміні даними між клієнтом і сервером HTTP. Це добре видно при розгляді обмінюваних заголовків HTTP.
- обробка попередніх подій завжди відбувається на стороні сервера. Натискання кнопки, звичайно, може оброблятися серверним скриптом JavaScript. Але в цьому випадку це не є серверною подією, і ми маємо справу з технологією, незалежною від ASP.NET.
У який момент обробляються події — чи то на стороні сервера (події, пов’язані з серверними компонентами), чи то на стороні браузера за допомогою скриптів JavaScript?
Візьмемо для прикладу випадаючий список. Коли користувач змінює вибраний у ньому елемент, подія (зміна вибраного елемента) може оброблятися або ні, а якщо обробляється, то це може відбуватися в різні моменти часу.
- Якщо ми хочемо обробити його негайно, є два варіанти:
- воно може оброблятися браузером за допомогою скрипту JavaScript. У цьому випадку сервер не бере участі. Щоб це було можливо, сторінка має мати можливість відтворюватися з використанням значень, що містяться на ній.
- воно може оброблятися сервером. Для цього є лише один варіант: форма має бути надіслана на сервер для обробки. Отже, маємо операцію [submit]. Ми побачимо, що в цьому випадку використовується серверний компонент із назвою [DropDownList], а його атрибут [AutoPostBack] встановлюється на [true]. Це означає, що у разі зміни елемента, вибраного у випадаючому списку, форма має бути негайно відправлена на сервер. У цьому випадку сервер генерує для об’єкта [DropDownList] код HTML, пов’язаний із функцією JavaScript, яка відповідає за виконання [submit], щойно відбудеться подія «зміна вибраного елемента». Цей [submit] відправить форму на сервер, і в ній будуть розміщені приховані поля, що вказують на те, що [post] виник внаслідок зміни вибору у випадаючому списку. Тоді сервер згенерує подію [SelectedIndexChanged], яку сторінка зможе обробити.
- Якщо потрібно обробити його, але не відразу, атрибут [AutoPostBack] серверного компонента [DropDownList] встановлюється на [false]. У цьому випадку сервер генерує для об’єкта [DropDownList] стандартний код HTML для списку <select> без пов’язаної функції JavaScript. Тоді нічого не відбувається, коли користувач змінює вибір у випадаючому списку. Однак коли він підтвердить форму, наприклад, за допомогою кнопки [submit], сервер зможе розпізнати, що відбулася зміна вибору. Ми вже бачили, що форма, надіслана на сервер, містила приховане поле [__VIEWSTATE], яке у закодованій формі відображало стан усіх елементів надісланої форми. Коли сервер отримує нову форму, надіслану клієнтом, він зможе перевірити, чи змінився вибраний елемент у випадаючому списку. Якщо так, він згенерує подію [SelectedIndexChanged], яку сторінка зможе обробити. Щоб відрізнити цей механізм від попереднього, деякі автори зазначають, що подія «зміна вибору» була «закешована» в браузері в момент її виникнення. Сервер оброблятиме її лише тоді, коли браузер надішле йому форму, що часто відбувається після натискання кнопки [submit].
- Нарешті, якщо не потрібно обробляти подію, то встановлюємо атрибут [AutoPostBack] серверного компонента [DropDownList] на [false] і не пишемо обробник його події [SelectedIndexChanged].
Зрозумівши механізм обробки подій, розробник не буде створювати веб-додаток так само, як додаток для Windows. Дійсно, якщо зміна вибору в комбінованому списку додатка для Windows може бути використана для негайної зміни зовнішнього вигляду форми, в якій він знаходиться, то у веб-додатку будуть більше вагатися з негайною обробкою цього події, якщо це передбачає «post» форми на сервер, а отже, обмін даними між клієнтом і сервером. Саме тому властивість [AutoPostBack] серверних компонентів за замовчуванням встановлено на [false]. Крім того, механізм [AutoPostBack], який базується на скриптах JavaScript, що автоматично генеруються веб-сервером у формі, надісланій клієнту, можна використовувати лише в тому випадку, якщо є впевненість, що браузер клієнта дозволив виконання скриптів JavaScript у своєму браузері. Тому форми часто будуються таким чином:
- серверні компоненти форми мають властивість від [AutoPostBack] до [false]
- форма має одну або кілька кнопок, які відповідають за виконання [POST] форми
- у коді контролера сторінки пишуться обробники лише тих подій, які потрібно обробляти, найчастіше — події [Click] при натисканні однієї з кнопок.
7.6. Компонент TextBox
7.6.1. Використання
Тег <asp:TextBox> дозволяє вставити поле введення даних у код відображення сторінки. Ми створюємо сторінку [form4.aspx], щоб отримати таке відображення:

4321Ця сторінка, створена за допомогою WebMatrix, містить такі компоненти:
№ | назва | тип | властивості | роль |
1 | TextBox | AutoPostback=true Text= | поле введення | |
2 | TextBox | AutoPostback=false Text= | поле введення | |
3 | Label | text= | інформаційне повідомлення про вміст [TextBox1] | |
3 | Мітка | text= | інформаційне повідомлення про вміст [TextBox2] |
Код, згенерований WebMatrix для цієї частини, є таким:
<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form runat="server">
<p>
Texte 1 :
<asp:TextBox id="TextBox1" runat="server" AutoPostBack="True"></asp:TextBox>
</p>
<p>
Texte 2 :
<asp:TextBox id="TextBox2" runat="server"></asp:TextBox>
</p>
<p>
<asp:Label id="lblInfo1" runat="server"></asp:Label>
</p>
<p>
<asp:Label id="lblInfo2" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
На вкладці [Design] двічі клацніть на компоненті [TextBox1]. Після цього генерується каркас обробника події [TextChanged] для цього об’єкта (вкладка [All]):
<%@ Page Language="VB" %>
<script runat="server">
Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
End Sub
</script>
<html>
...
<body>
...
<asp:TextBox id="TextBox1" runat="server" AutoPostBack="True" OnTextChanged="TextBox1_TextChanged"></asp:TextBox>
</p>
....
</form>
</body>
</html>
Атрибут [OnTextChanged="TextBox1_TextChanged"] було додано до тегу <asp:TextBox id="TextBox1">, щоб вказати обробник події [TextChanged] на [TextBox1]. Саме процедуру [TextBox1_Changed] ми зараз і пишемо.
Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
' зміна тексту
lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
End Sub
У процедурі ми записуємо в мітку [lblInfo1] повідомлення, що сигналізує про подію та вказує вміст [TextBox1]. Те саме робимо для [TextBox2]. Ми також вказуємо час, щоб краще відстежувати обробку подій. Остаточний код [form4.aspx] має такий вигляд:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' збереження поточного запиту в request.txt у папці сторінки
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
' зміна тексту
lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
End Sub
Sub TextBox2_TextChanged(sender As Object, e As EventArgs)
' зміна тексту
lblInfo2.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox2]. Texte 2=["+textbox2.Text+"]"
End Sub
</script>
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form runat="server">
<p>
Texte 1 :
<asp:TextBox id="TextBox1" runat="server" AutoPostBack="True" OnTextChanged="TextBox1_TextChanged"></asp:TextBox>
</p>
<p>
Texte 2 :
<asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged"></asp:TextBox>
</p>
<p>
<asp:Label id="lblInfo1" runat="server"></asp:Label>
</p>
<p>
<asp:Label id="lblInfo2" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
Ми додали процедуру [Page_Init] для збереження запиту клієнта, як у попередньому прикладі.
7.6.2. Тестування
Ми запускаємо додаток під номером WebMatrix за допомогою [F5]. Ми отримуємо таку сторінку:

Код HTML, отриманий браузером, має такий вигляд:
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">
<input type="hidden" name="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE" value="dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u+/OrTD" />
<script language="javascript">
<!--
function __doPostBack(eventTarget, eventArgument) {
var theform = document._ctl0;
theform.__EVENTTARGET.value = eventTarget;
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
</script>
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
</form>
</body>
</html>
У цьому коді є багато елементів, які були автоматично згенеровані сервером. Звернімо увагу на наступні моменти:
- є три приховані поля: [__VIEWSTATE], з яким ми вже стикалися, [__EventTarget] та [__EventArgument]. Ці два останні поля використовуються для обробки події браузера «change» у полі введення [TextBox1]
- серверні теги <asp:textbox> породили теги HTML <input type="text" ...>, які відповідають полям введення
- серверний тег <asp:textbox id="TextBox1" AutoPostBack="true" ...> перетворився на тег <input type="text" ...> з атрибутом [onchange="__doPostBack('TextBox1','')"]. Цей атрибут вказує, що у разі зміни вмісту [TextBox1] має бути виконана функція JavaScript [_doPostBack(...)]. Вона має такий вигляд:
<script language="javascript">
<!--
function __doPostBack(eventTarget, eventArgument) {
var theform = document._ctl0;
theform.__EVENTTARGET.value = eventTarget;
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
</script>
Що робить наведена вище функція? Вона присвоює значення кожному з двох прихованих полів [__EventTarget] та [__EventArgument], а потім відправляє форму. Таким чином, форма надсилається на сервер. Це і є ефект [AutoPostBack]. Подія браузера «change» викликає виконання коду «__doPostBack('TextBox1','')». Звідси випливає, що у відправленій формі приховане поле [__EventTarget] матиме значення 'TextBox1', а приховане поле [__EventArgument] — значення ''. Це дозволить серверу визначити, який саме компонент спричинив появу POST.
- Серверний тег <asp:textbox id="TextBox2"...> перетворився на звичайний тег <input type="text" ...> через те, що його атрибут [AutoPostBack] не був встановлений на значення [true].
- Тег <form> вказує, що форма буде відправлена на [form4.aspx]:
Давайте проведемо наш перший тест. Введемо текст у перше поле введення:

потім перемістимо курсор у друге поле введення. Відразу ж ми отримаємо нову сторінку:

Що сталося? Коли курсор покинув перше поле введення, браузер перевірив, чи змінився його вміст. Так і сталося. Тому браузер згенерував подію [change] для поля HTML [TextBox1]. Тоді ми побачили, що виконалася функція JavaScript, яка відправила форму на [form4.aspx]. Отже, ця сторінка була перезавантажена сервером. Значення, надіслані формою, дозволили серверу, у свою чергу, визначити, що вміст серверного тегу [TextBox1] змінився. Отже, процедура [TextBox1_Changed] була виконана на стороні сервера. Вона розмістила повідомлення в мітці [lblInfo1]. Після завершення цієї процедури [form4.aspx] було надіслано до браузера. Ось чому тепер у [lblInfo1] міститься текст. Однак може викликати подив наявність даних у полі введення [TextBox1]. Дійсно, жодна процедура, що виконується на стороні сервера, не присвоює цьому полю значення. Тут ми маємо справу із загальним механізмом роботи веб-форм ASP.NET: сервер повертає форму в тому стані, в якому її отримав. Для цього він знову присвоює компонентам значення, які були відправлені для них клієнтом. Для деяких компонентів клієнт не надсилає жодного значення. Це стосується, наприклад, компонентів <asp:label>, які перетворюються на теги <span> HTML. Нагадаємо, що форма має приховане поле [__VIEWSTATE], яке відображає стан форми під час її надсилання клієнту. Цей стан є сумою станів усіх компонентів форми, включаючи компоненти <asp:label>, якщо такі є. Оскільки приховане поле [__VIEWSTATE] надсилається браузером клієнта, сервер може відновити попередній стан усіх компонентів форми. Йому залишається лише змінити ті компоненти, значення яких було змінено POST.
Тепер розглянемо у [request.txt] запит, який був зроблений браузером:
POST /form4.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Content-Length: 137
Content-Type: application/x-www-form-urlencoded
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
Referer: http://localhost/form4.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u%2B%2FOrTD&TextBox1=premier+texte&TextBox2=
Чітко видно код POST, а також надіслані параметри. Повернімося до нашого браузера та введімо текст у друге поле введення:

Повернімося до поля введення № 1, щоб ввести новий текст: цього разу нічого не відбувається. Чому? Тому що поле введення [TextBox2] не має властивості [AutoPostBack] у [true], а тег <input type="text"...> не обробляє подію [Change], як це видно з її коду HTML:
Отже, при виході з поля введення № 2 жодна подія не відбувається. Тепер введемо новий текст у поле № 1:

Вийдемо з поля введення № 1. Відразу ж виявляється подія [Change] для цього поля, і форма надсилається на сервер, який у відповідь повертає таку сторінку:

Що сталося? По-перше, браузер відправив форму. Про це свідчить запит клієнта, збережений у [request.txt]:
POST /form4.aspx HTTP/1.1
....
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTt0PDtsPGk8MT47PjtsPHQ8O2w8aTwxPjtpPDU%2BOz47bDx0PHA8cDxsPFRleHQ7PjtsPHByZW1pZXIgdGV4dGU7Pj47Pjs7Pjt0PHA8cDxsPFRleHQ7PjtsPDE4OjQyOjI5OiBldnQgW1RleHRDaGFuZ2VkXSBzdXIgW1RleHRCb3gxXS4gVGV4dGUgMT1bcHJlbWllciB0ZXh0ZV07Pj47Pjs7Pjs%2BPjs%2BPjs%2BxLOermpUUUz5rTAa%2FFsjda6lVmo%3D&TextBox1=troisi%C3%A8me+texte&TextBox2=second+texte
Сервер спочатку відновлює попередні значення компонентів за допомогою прихованого поля [__VIEWSTATE], яке йому надіслав клієнт. Завдяки відправленим полям [TextBox1] та [TextBox2] він присвоїть компонентам [TextBox1] та [TextBox2] значення, які були йому надіслані. Саме завдяки цьому механізму клієнт отримає форму в тому вигляді, в якому вона була відправлена. Потім, знову ж таки завдяки відправленим полям [__VIEWSTATE], [TextBox1] та [TextBox2], сервер виявить, що значення полів введення [TextBox1] та [TextBox2] змінилися. Тому він згенерує події [TextChanged] для цих двох об’єктів. Будуть виконані процедури [TextBox1_TextChanged] та [TextBox2_TextChanged], а мітки [labelInfo1] та [labelInfo2] отримають нові значення. Потім так змінена сторінка [form4.aspx] повертається клієнту.
Тепер ми знову змінюємо поле введення № 1:

Коли ми виводимо курсор з поля введення № 1, у браузері відбувається подія [Change]. Далі відбувається вже описана послідовність подій (запит браузера до сервера, ..., надсилання відповіді сервера). Ми отримуємо таку відповідь:

З огляду на час, що відображається для кожного з повідомлень, бачимо, що на сервері була виконана лише процедура [TextBox1_Changed]. Процедура [TextBox2_TextChanged] не була виконана саме тому, що значення [TextBox2] не змінилося. Нарешті, введемо новий текст у поле № 2:

Потім наведемо курсор на поле № 1, а потім знову на поле № 2. Сторінка не змінюється. Чому? Оскільки ми не змінюємо значення поля № 1, подія браузера [Change] не відбувається, коли ми залишаємо це поле. Як наслідок, форма не надсилається на сервер. Тому на сторінці нічого не змінюється. Саме те, що вміст [lblInfo2] не змінюється, свідчить про відсутність POST. Якби він існував, сервер виявив би, що вміст [TextBox2] змінився, і мав би відобразити цей факт у [lblInfo2].
З цього прикладу можна зробити висновок, що немає сенсу привласнювати властивість [AutoPostBack] об’єкту [TextBox] у [true]. У більшості випадків це спричиняє зайвий обмін даними між клієнтом і сервером.
7.6.3. Роль поля __VIEWSTATE
Ми бачили, що сервер систематично додає приховане поле з назвою __VIEWSTATE у форму, яку він генерує. Ми зазначали, що це поле відображає стан форми і що, якщо повернути серверу це приховане поле, він зможе відтворити попереднє значення форми. Стан форми — це сукупність станів її компонентів. Кожен із них має властивість [EnableViewState] із булевим значенням, що вказує, чи слід розміщувати стан компонента у прихованому полі [__VIEWSATE]. За замовчуванням це властивість має значення [true], завдяки чому стан усіх компонентів форми записується в [__VIEWSTATE]. Іноді це небажано.
Проведемо кілька експериментів, щоб краще зрозуміти роль властивості [EnableViewState]. Встановимо для обох полів введення значення цієї властивості як [false]:
...
<asp:TextBox id="TextBox1" runat="server" OnTextChanged="TextBox1_TextChanged" AutoPostBack="True" EnableViewState="False"></asp:TextBox>
...
<asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged" EnableViewState="False"></asp:TextBox>
...
Тепер запустимо додаток і введемо перший текст у поле № 1, а потім перейдемо до поля № 2. Після цього на сервер надсилається запит POST, і ми отримуємо таку відповідь:

Введемо текст у поле № 2, змінимо текст у полі № 1, а потім повернемося до поля № 2 (у такому порядку). На сервер надсилається новий запит POST, і ми отримуємо таку відповідь:

Поки що все як і раніше. Тепер змінимо вміст поля № 1, а потім перейдемо до поля № 2. Здійснюється новий запит POST. Відповідь сервера така:

Цього разу відбулася зміна. Сервер виявив подію [TextChanged] у полі № 2, оскільки час [lblInfo2] було змінено. Однак жодних змін не відбувалося. Це пояснюється властивістю [EnableViewState=false] об’єкта [TextBox2]. Вона призводить до того, що сервер не вніс у поле [__VIEWSTATE] форми попередній стан об’єкта [TextBox2]. Це рівнозначно тому, що йому було присвоєно порожній рядок як попередній стан. Коли відбувся POST внаслідок зміни вмісту [TextBox1], сервер порівняв поточне значення [TextBox2], яке дорівнювало [deux], із його попереднім значенням (порожнім рядком). На підставі цього він дійшов висновку, що значення [TextBox2] змінилося, і згенерував подію [TextChanged] для [TextBox2]. Цю поведінку можна перевірити, вставивши порожній рядок у [TextBox2]. Згідно з тим, що щойно було пояснено, сервер не повинен генерувати подію [TextChanged] для [TextBox2]. Спробуємо:

Саме так і сталося. Час та вміст [lblInfo2] свідчать про те, що процедура [TextBox2_TextChanged] не була виконана. З огляду на це, розглянемо властивість [EnableViewState] для чотирьох компонентів форми:
ми хочемо зберегти стан цього компонента, щоб сервер знав, чи змінився він, чи ні | |
те саме | |
ми не хочемо зберігати стан цього компонента. Ми хочемо, щоб текст перераховувався при кожному новому POST. Якщо він не перераховується, він повинен бути порожнім. Все це добре досягається за допомогою [EnableViewState=false] | |
те саме |
Наша презентаційна сторінка виглядає наступним чином:
...
<asp:TextBox id="TextBox1" runat="server" OnTextChanged="TextBox1_TextChanged" AutoPostBack="True"></asp:TextBox>
...
<asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged"></asp:TextBox>
....
<asp:Label id="lblInfo1" runat="server" enableviewstate="False"></asp:Label>
...
<asp:Label id="lblInfo2" runat="server" enableviewstate="False"></asp:Label>
...
Повторімо ту саму серію тестів, що й раніше. Там, де ми отримали екран
version 1

, тепер отримуємо:
version 2

На цьому етапі ми змінювали вміст поля 1, не змінюючи вміст поля 2, завдяки чому процедура [TextBox2_TextChanged] не виконувалася на стороні сервера, що означало, що поле [lblInfo2] не отримувало нового значення. Тому воно відображається з попереднім значенням. У версії 1 [EnableViewState=true] цим попереднім значенням було введене значення. У версії 2 [EnableViewState=false] цим попереднім значенням є порожній рядок.
Іноді немає сенсу зберігати попередній стан компонентів. Замість того, щоб вказувати [EnableViewState=false] для кожного з них, можна вказати, що сторінка не повинна зберігати свій стан. Це робиться за допомогою директиви [Page] у коді представлення:
У цьому випадку, незалежно від значення властивості [EnableViewState], стан компонента не записується у приховане поле [__VIEWSTATE]. Тоді все відбувається так, ніби його попередній стан — це порожній рядок.
Тепер скористаємося клієнтом [curl], щоб проілюструвати інші механізми. Спочатку запитуємо URL-адресу [http://localhost/form4.aspx]:
dos>curl --include --url http://localhost/form4.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Sun, 04 Apr 2004 17:51:14 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 1077
Connection: Close
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">
<input type="hidden" name="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE" value="dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u+/OrTD" />
<script language="javascript">
<!--
function __doPostBack(eventTarget, eventArgument) {
var theform = document._ctl0;
theform.__EVENTTARGET.value = eventTarget;
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
</script>
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
</form>
</body>
</html>
Ми отримуємо від сервера код HTML з [form4.aspx]. Він не відрізняється від того, який отримав браузер. Нагадаємо, що запит, який зробив браузер під час відправлення форми, був таким:
POST /form4.aspx HTTP/1.1
Connection: keep-alive
...
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316
__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u%2B%2FOrTD&TextBox1=premier+texte&TextBox2=
Зробимо те саме з POST, але без надсилання поля [__VIEWSTATE]:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=перший+текст --data TextBox2=
...................
<p>
Texte 1 :
<input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1">19:57:48: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
..............
Слід звернути увагу на наступні моменти:
- сервер виявив подію [TextChanged] на [TextBox1], оскільки він згенерував текст [lblInfo1]. Відсутність [__VIEWSTATE] не завадила йому. У разі його відсутності сервер припускає, що попереднє значення поля введення — це порожній рядок.
- Він зміг перенести текст, відправлений для [TextBox1], в атрибут [value] тегу [TextBox1], щоб поле [TextBox1] знову з’явилося із введеним значенням. Для цього йому не потрібен [__VIEWSTATE], а лише значення, відправлене для [TextBox1]
Тепер повторімо той самий запит, не змінюючи нічого. Ми отримаємо таку нову відповідь:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=перший+текст --data TextBox2=
<p>
Texte 1 :
<input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1">20:05:47: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
Через відсутність [__VIEWSTATE] сервер не зміг визначити, що значення поля [TextBox1] не змінилося. Тому він поводиться так, ніби попереднє значення було порожнім рядком. Тому він згенерував тут подію [TextChanged] на основі [TextBox1]. Давайте ще раз відтворимо той самий запит, але цього разу з порожнім полем [TextBox1] і непорожнім полем [TextBox2]:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox2=другий+текст --data TextBox1=
......
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" value="second texte" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2">20:11:54: evt [TextChanged] sur [TextBox2]. Texte 2=[second texte]</span>
</p>
......
У разі відсутності [__VIEWSTATE] попереднє значення [TextBox1] було визнано порожнім рядком. Оскільки значення, записане в [TextBox1], також є порожнім рядком, подія [TextChanged] щодо [TextBox1] не була згенерована. Процедура [TextBox1_TextChanged] не була виконана, тому поле [lblInfo1] не отримало нового значення. Відомо, що в такому випадку компонент зберігає своє попереднє значення. Однак у даному випадку це не так: [lblInfo1] втратив своє попереднє значення. Це сталося тому, що це значення шукається в [__VIEWSTATE]. Оскільки це поле відсутнє, [lblInfo1] було присвоєно порожній рядок. Щодо [TextBox2], сервер порівняв його значення, що було відправлено ([second texte]), з його попереднім значенням. Оскільки [__VIEWSTATE] відсутнє, це попереднє значення дорівнює порожньому рядку. Оскільки значення, що було відправлено, для [TextBox2] відрізняється від порожнього рядка, було згенеровано подію [TextChanged] щодо [TextBox2]. Процедура [TextBox2_TextChanged] була виконана, і поле [lblInfo2] отримало нове значення.
Можна задатися питанням, чи дійсно потрібні параметри [__EVENTTARGET] та [__EVENTARGUMENT]. Якщо не надсилати ці параметри, сервер не знатиме, яка подія спричинила [submit]. Спробуємо:
dos>curl --include --url http://localhost/form4.aspx --data TextBox2=другий+текст --data TextBox1=перший+текст
..............................
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
</form>
.....................
Видно, що жодна подія [TextChanged] не була оброблена. Крім того, поля, що були відправлені, [TextBox1] та [TextBox2], не відновлюють своїх значень, що були відправлені. Фактично все відбувається так, ніби було виконано GET. Все повертається до норми, якщо серед відправлених полів є поле [__EVENTTARGET], навіть якщо воно не має значення:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET= --data TextBox2=другий+текст --data TextBox1=перший+текст
.......
<p>
Texte 1 :
<input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" value="second texte" id="TextBox2" />
</p>
<p>
<span id="lblInfo1">20:34:14: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
</p>
<p>
<span id="lblInfo2">20:34:14: evt [TextChanged] sur [TextBox2]. Texte 2=[second texte]</span>
</p>
........
7.6.4. Інші властивості компонента TextBox
Серверний компонент [TextBox] також дозволяє генерувати теги HTML <input type="password"..> та <texarea>..</textarea>, c.a.d. Ці теги відповідають, відповідно, полю захищеного введення та полю багаторядкового введення. Це генерування контролюється властивістю [TextMode] компонента [TextBox]. Вона може приймати три можливі значення:
значення | згенерований тег HTML |
<input type="text" ...> | |
<textarea>...</textarea> | |
<input type="password ...> |
Розглянемо використання цих властивостей на прикладі [form5.aspx]:

№ | назва | тип | властивості | роль |
1 | Кнопка | кнопка [submit] — служить для додавання вмісту [TextBox1] до вмісту [TextBox2] | ||
2 | TextBox | TextMode=Password Text= | захищене поле введення | |
3 | TextBox | TextMode=Багаторядковий Text= | об'єднує записи, зроблені в [TextBox1] |
Властивість [EnableViewState] сторінки встановлюється на [false]. На стороні сервера ми обробляємо подію натискання кнопки [btnAjouter]:
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' додаємо вміст [textBox1] до вмісту [TextBox2]
textbox2.text=textbox2.text + textbox1.text+controlchars.crlf
End Sub
Щоб зрозуміти цей код, слід згадати, як обробляється POST у формі. Спочатку виконуються процедури [Page_Init] та [Page_Load]. Потім виконуються всі процедури подій, що знаходяться в кеші. Нарешті виконується процедура, що обробляє подію, яка викликала [POST], у даному випадку — процедура [btnAjouter_Click]. Під час виконання обробників подій усі компоненти сторінки, що мають значення в POST, приймають це значення. Решта компонентів повертаються до своїх попередніх значень, якщо їхня властивість [EnableViewState] мала значення [true], або до своїх значень за дизайном, якщо їхня властивість [EnableViewState] мала значення [false]. У цьому випадку значення полів [TextBox1] та [TextBox2] стануть частиною поля POST, сформованого клієнтом. Також у наведеному вище коді поле [textbox1.text] матиме значення, відправлене клієнтом, так само як і поле [textbox2.text]. Процедура [btnAjouter_Click] записує в поле [TextBox2] значення, відправлене для [TextBox2], додане до значення, відправленого для [TextBox1], додане до позначки кінця рядка [ControlChars.CrLf], визначеним у просторі імен [Microsoft.VisualBasic]. Імпортувати цей простір не потрібно, оскільки веб-сервер імпортує його за замовчуванням.
Остаточний код [form5.aspx] має такий вигляд:
<%@ Page Language="VB" EnableViewState="False" %>
<script runat="server">
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' додаємо вміст [textBox1] до вмісту [TextBox2]
textbox2.text+=textbox1.text+controlchars.crlf
End Sub
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:Button id="btnAjouter" onclick="btnAjouter_Click" runat="server" Text="Ajouter" EnableViewState="False"></asp:Button>
<asp:TextBox id="TextBox1" runat="server" TextMode="Password" Width="353px" EnableViewState="False"></asp:TextBox>
</p>
<p>
<asp:TextBox id="TextBox2" runat="server" TextMode="MultiLine" Width="419px" Height="121px" EnableViewState="False"></asp:TextBox>
</p>
</form>
</body>
</html>
Трохи вище наведено знімок екрана з прикладом виконання.
7.7. Компонент DropDownList
Тег <asp:DropDownList> дозволяє вставити випадаючий список у код оформлення сторінки. Ми створюємо сторінку [form6.aspx], щоб отримати таке оформлення:

№ | назва | тип | властивості | роль |
1 | DropDownList | AutoPostback=true EnableViewState=true | випадаючий список | |
2 | Мітка | EnableViewState=false | інформаційне повідомлення |
Згенерований код для відображення має такий вигляд:
Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:DropDownList id="DropDownList1" runat="server" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
</p>
<p>
<asp:Label id="lblInfo" runat="server" enableviewstate="False"></asp:Label>
</p>
</form>
</body>
</html>
Наразі випадаючий список не містить жодного елемента. Ми заповнимо його у процедурі [Page_Load]. Для цього нам потрібно знати деякі властивості та методи класу [DropDownList]:
колекція типу [ListItemCollection] елементів випадаючого списку. Елементи цієї колекції мають тип [ListItem]. | |
кількість елементів колекції [Items] | |
елемент № i списку — типу [ListItem] | |
щоб додати новий елемент [ListItem] до колекції [Items] | |
щоб видалити всі елементи з колекції [Items] | |
для видалення елемента № i з колекції [Items] | |
1-й елемент [ListItem] колекції [Items], властивість якого [Selected] має значення «true» | |
номер елемента [SelectedItem] у колекції [Items] |
Елементи колекції [Items] класу [DropDownList] мають тип [ListItem]. Кожен елемент [ListItem] породжує тег HTML <option>:
Опишемо деякі властивості та методи класу [ListItem]:
конструктор — створює елемент [ListItem] із властивостями [texte] та [value]. Елемент ListItem(T,V) породжує тег HTML <option value="V">T</option>. Отже, клас [ListITem] дозволяє описувати елементи списку HTML | |
булевого типу. Якщо значення «true», відповідна опція списку HTML матиме атрибут [selected="selected"]. Цей атрибут вказує браузеру, що відповідний елемент повинен відображатися як вибраний у списку HTML | |
текст T опції HTML <option value="V" [selected="selected"]>T</option> | |
значення V атрибута [Value] опції HTML <option value="V" [selected="selected"]>T</option> |
Ми маємо достатньо інформації, щоб вписати в процедуру [Page_Load] на сторінці код для заповнення випадаючого списку [DropDownList1]:
Sub Page_Load(sender As Object, e As EventArgs)
' заповнюємо комбінацію, якщо це перший виклик
if not IsPostBack then
dim valeurs() as String = {"1","2","3","4"}
dim textes() as String = {"un","deux","trois","quatre"}
dim i as integer
for i=0 to valeurs.length-1
DropDownList1.Items.Add(new ListItem(textes(i),valeurs(i)))
next
end if
end sub
Після ініціалізації компонента [DropDownList1] його переклад HTML буде таким:
<select name="DropDownList1" id="DropDownList1" onchange="__doPostBack('DropDownList1','')" language="javascript">
<option value="1">un</option>
<option value="2">deux</option>
<option value="3">trois</option>
<option value="4">quatre</option>
</select>
Ми знаємо, що процедура [Page_Load] виконується під час кожного запуску сторінки [form6.aspx]. Вона викликається вперше процедурою GET, а потім — процедурою POST щоразу, коли користувач вибирає новий елемент у випадаючому списку. Чи потрібно щоразу виконувати код заповнення цього списку в [Page_Load]? Відповідь залежить від атрибута [EnableViewState] компонента [DropDownList1]. Якщо цей атрибут має значення «true», то стан компонента [DropDownList1] зберігатиметься протягом усіх запитів у прихованому полі [__VIEWSTATE]. Цей стан включає дві речі:
- список усіх значень випадаючого списку
- значення елемента, вибраного в цьому списку
Тоді виникає спокуса встановити властивість [EnableViewState] компонента [DropDownList1] на значення [true], щоб уникнути необхідності перераховувати значення, які потрібно внести до списку. Проблема полягає в тому, що процедура [Page_Load] виконується щоразу, коли запитується сторінка [form6.aspx], тому ці значення все одно будуть обчислюватися. Об’єкт [Page], екземпляром якого є [form6.aspx], має атрибут [IsPostBack] із булевим значенням. Якщо цей атрибут має значення «true», це означає, що сторінка викликається POST. Якщо значення «false», це означає, що сторінка була викликана об’єктом GET. У нашій системі взаємодії «клієнт-сервер» клієнт завжди запитує у сервера одну й ту саму сторінку [form6.aspx]. Уперше він запитує її з GET, а в наступні рази — з POST. Звідси випливає, що властивість [IsPostBack] може допомогти нам виявити перший запит клієнта GET. Значення списку, що розгортається, генеруються лише під час цього першого виклику. У наступних запитах ці значення будуть генеруватися механізмом [VIEWSTATE]. В інших ситуаціях вміст списку може змінюватися від запиту до запиту і тому повинен перераховуватися для кожного з них. У цьому випадку атрибут [EnableViewState] цього списку слід встановити на [false], щоб уникнути непотрібного подвійного обчислення вмісту списку, за винятком випадків, коли потрібно знати елементи, раніше вибрані у списку, оскільки ця інформація зберігається в [VIEWSTATE].
Атрибут [AutoPostBack] списку [DropDownList1] було встановлено на «true». Це означає, що браузер відправить форму, щойно виявить подію «зміна вибраного елемента» у випадаючому списку. Сервер, у свою чергу, завдяки [VIEWSTATE] та надісланим значенням виявить, що елемент, вибраний у компоненті [DropDownList1], змінився. Після цього він запустить подію [SelectedIndexChanged] у цьому компоненті. Ми обробимо її за допомогою такої процедури:
Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
' зміна вибору
lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
" valeur=" + dropdownlist1.selecteditem.value + _
" index="+ dropdownlist1.selectedindex.tostring
End Sub
Під час виконання цієї процедури об’єкт [DropDownList1] відновив свої елементи типу [ListItem] завдяки [VIEWSTATE]. Крім того, один із них типу [ListItem] має атрибут [Selected] із значенням «true» — саме той, значення якого було відправлено браузером. Доступ до цього елемента можна отримати кількома способами:
— це перший елемент [ListItem] у списку, атрибут [Selected] якого має значення «true» | |
відповідає частині [texte] тегу HTML елемента <option value="...">текст</option>, обраного користувачем | |
відповідає частині [value] тегу HTML елемента <option value="...">текст</option>, обраного користувачем | |
номер у колекції [DropDownList1.Items] першого елемента [ListItem], атрибут [Selected] якого має значення «true» |
Остаточний код [form6.aspx] має такий вигляд:
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
' заповнюємо комбінацію, якщо це перший виклик
if not IsPostBack then
dim valeurs() as String = {"1","2","3","4"}
dim textes() as String = {"un","deux","trois","quatre"}
dim i as integer
for i=0 to valeurs.length-1
DropDownList1.Items.Add(new ListItem(textes(i),valeurs(i)))
next
end if
end sub
Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
' зміна вибору
lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
" valeur=" + dropdownlist1.selecteditem.value + _
" index="+ dropdownlist1.selectedindex.tostring
End Sub
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:DropDownList id="DropDownList1" runat="server" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
</p>
<p>
<asp:Label id="lblInfo" runat="server" enableviewstate="False"></asp:Label>
</p>
</form>
</body>
</html>
7.8. Компонент ListBox
Тег <asp:ListBox> дозволяє вставити список у код оформлення сторінки. Ми створюємо [form7.aspx], щоб отримати таке оформлення:

№ | назва | тип | властивості | роль |
1 | TextBox | EnableViewState=false | поле введення | |
2 | Кнопка | кнопка [submit], яка переносить у Список 1 вміст поля txtSaisie, якщо воно не порожнє. | ||
3 | ListBox | EnableViewState=true SelectionMode=Single | список значень з одинарним вибором | |
4 | ListBox | EnableViewState=true SelectionMode=Multiple | список значень для множинного вибору | |
5 | Кнопка | кнопка [submit], яка передає у [liste 2] вибраний елемент із [liste 1] | ||
6 | Кнопка | кнопка [submit], яка переносить у [liste 1] вибрані елементи з [liste 2] |
Згенерований код відображення має такий вигляд:
<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
Tapez un texte pour l'inclure dans Liste 1 :
<asp:TextBox id="txtSaisie" runat="server" EnableViewState="False"></asp:TextBox>
</p>
<p>
<asp:Button id="btnAjouter" onclick="btnAjouter_Click" runat="server" Text="Ajouter"></asp:Button>
</p>
<p>
<table>
<tbody>
<tr>
<td>
<p align="center">
Liste 1
</p>
</td>
<td>
</td>
<td>
<p align="center">
Liste 2
</p>
</td>
</tr>
<tr>
<td>
<asp:ListBox id="ListBox1" runat="server"></asp:ListBox>
</td>
<td>
<p>
<asp:Button id="btn1vers2" onclick="btn1vers2_Click" runat="server" Text="-->"></asp:Button>
</p>
<p>
<asp:Button id="btn2vers1" onclick="btn2vers1_Click" runat="server" Text="<--"></asp:Button>
</p>
</td>
<td>
<p>
<asp:ListBox id="ListBox2" runat="server" SelectionMode="Multiple"></asp:ListBox>
</p>
</td>
</tr>
<tr>
<td>
<p align="center">
<asp:Button id="btnRaz1" onclick="btnRaz1_Click" runat="server" Text="Effacer"></asp:Button>
</p>
</td>
<td>
</td>
<td>
<p align="center">
<asp:Button id="btnRaz2" onclick="btnRaz2_Click" runat="server" Text="Effacer"></asp:Button>
</p>
</td>
</tr>
</tbody>
</table>
</p>
</form>
</body>
</html>
Клас [ListBox] походить від того самого класу [ListControl], що й клас [DropDownList], який ми розглядали раніше. У ньому містяться всі властивості та методи, розглянуті для класу [DropDownList], оскільки вони насправді належали класу [ListControl]. З’являється нова властивість:
визначає режим вибору зі списку HTML <select>, який буде згенеровано на основі цього компонента. Якщо SelectionMode=Single, то можна буде вибрати лише один елемент. Якщо SelectionMode=Multiple, то можна буде вибрати кілька елементів. Для цього атрибут [multiple="multiple"] буде згенеровано в тезі <select> списку HTML. |
Обробимо події. Клік на кнопку [Ajouter] оброблятиметься наступною процедурою [btnAjouter_Click]:
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' додавання до списку 1
dim texte as string=txtSaisie.text.trim
if texte<> "" then ListBox1.Items.Add(New ListItem(texte))
' очищення txtSaisie
txtSaisie.text=""
End Sub
Якщо текст, введений у [txtSaisie], не є порожнім або білим рядком, до списку [ListBox1] додається новий елемент. Ми знаємо, що потрібно додати елемент типу [ListItem]. Раніше ми використовували конструктор [ListItem(T as String, V as String)] для виконання аналогічної задачі. Такий елемент породжує тег HTML [<option value="V">T</option>]. Тут ми використовуємо конструктор [ListItem(T as String)], який створює теги HTML, [<option value="T">T</option>] та c.a.d. Текст [T] з опції береться для формування значення опції. Після додавання вмісту [txtSaisie] до списку [ListBox1] поле [txTSaisie] очищується.
Натискання на кнопки [Effacer] оброблятиметься за допомогою таких процедур:
Sub btnRaz1_Click(sender As Object, e As EventArgs)
' очищення списку 1
ListBox1.Items.Clear
End Sub
Sub btnRaz2_Click(sender As Object, e As EventArgs)
' очищення списку 2
ListBox2.Items.Clear
End Sub
Натискання на кнопки переходу між списками обробляються за допомогою таких процедур:
Sub btn1vers2_Click(sender As Object, e As EventArgs)
' перенесення вибраного елемента зі списку 1 до списку 2
transfert(ListBox1,ListBox2)
End Sub
Sub btn2vers1_Click(sender As Object, e As EventArgs)
' перенесення вибраного елемента зі списку 2 до списку 1
transfert(ListBox2,ListBox1)
End Sub
sub transfert(l1 as listbox, l2 as listbox)
' перенесення вибраних елементів зі списку 1 до списку 2
' щось потрібно зробити?
if l1.selectedindex=-1 then return
dim i as integer
' почнемо з кінця
for i=l1.items.count-1 to 0 step -1
' вибрано?
if l1.items(i).selected then
' більше не виділено
l1.items(i).selected=false
' перенести в l2
l2.items.add(l1.items(i))
' видалення з l1
l1.items.removeAt(i)
end if
next
end sub
Оскільки обидві кнопки виконують одну й ту саму функцію — перенесення елементів із одного списку в інший — можна звести все до єдиної процедури перенесення з двома параметрами:
- l1 типу [ListBox] — це вихідний список
- l2 типу [ListBox] — це список призначення
Спочатку перевіряємо, чи дійсно у списку l1 вибрано хоча б один елемент; якщо ні, то нічого робити не потрібно. Для цього перевіряємо властивість [l1.selectedindex], яка представляє номер першого вибраного елемента у списку. Якщо таких елементів немає, його значення дорівнює -1. Якщо у списку l1 вибрано хоча б один елемент, виконується перенесення до списку l2. Для цього пробігається весь список елементів l1 і для кожного з них перевіряється, чи має його атрибут [selected] значення «true». Якщо так, його атрибут [selected] змінюється на [false], потім він копіюється до списку l2 і, нарешті, видаляється зі списку l1. Це видалення спричиняє перенумерацію елементів списку l1. Саме тому список елементів l1 обходиться у зворотному напрямку. Якщо обходити його у прямому напрямку й видалити елемент № 10, елемент № 11 стане № 10, а № 12 — елементом № 11. Після обробки елемента № 10 наш цикл, що проходить у прямому напрямку, перейде до обробки елемента № 11, який, згідно з тим, що ми щойно пояснили, є колишнім № 12. Той елемент, який мав № 11 і тепер має № 10, випадає з поля зору. Проходячи елементи списку l1 у зворотному напрямку, ми уникаємо цієї проблеми.
7.9. Компоненти CheckBox, RadioButton
Теги <asp:RadioButton> та <asp:CheckBox> дозволяють вставляти відповідно перемикач та прапорець у код оформлення сторінки. Ми створюємо сторінку [form8.aspx], щоб отримати таке оформлення:

№ | назва | тип | властивості | роль |
1 | RadioButton | RadioButton1.Checked=true RadioButton1.Text=1 RadioButton2.Checked=false RadioButton2.Text=2 RadioButton3.Checked=false RadioButton3.Text=3 для 3 кнопок: GroupName=radio | перемикачі | |
2 | CheckBox | Checked=false для всіх CheckBoxA.Text=A CheckBoxB.Text=B CheckBoxC.Text=C | поля для позначення | |
3 | Кнопка | кнопка [submit] | ||
4 | ListBox | Список інформації |
Щоб браузер обробляв ці три перемикачі як взаємовиключні, їх потрібно об'єднати в групу перемикачів. Це робиться за допомогою атрибута [GroupName] класу [RadioButton]. У цьому додатку не потрібно зберігати стан сторінки. Тому ми додаємо до сторінки атрибут [EnableViewState="false"]. Код для відображення виглядає так:
<html>
<head>
</head>
<body>
<form id="frmControls" runat="server">
<h3>Cases à cocher
</h3>
<p>
<asp:RadioButton id="RadioButton1" runat="server" Checked="True" EnableViewState="False" GroupName="radio" Text="1"></asp:RadioButton>
<asp:RadioButton id="RadioButton2" runat="server" EnableViewState="False" GroupName="radio" Text="2"></asp:RadioButton>
<asp:RadioButton id="RadioButton3" runat="server" EnableViewState="False" GroupName="radio" Text="3"></asp:RadioButton>
</p>
<p>
<asp:CheckBox id="CheckBoxA" runat="server" EnableViewState="False" Text="A"></asp:CheckBox>
<asp:CheckBox id="CheckBoxB" runat="server" EnableViewState="False" Text="B"></asp:CheckBox>
<asp:CheckBox id="CheckBoxC" runat="server" EnableViewState="False" Text="C"></asp:CheckBox>
</p>
<p>
<asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
<asp:Button id="btnTree" onclick="btnTree_Click" runat="server" Text="Contrôles"></asp:Button>
</p>
<p>
<asp:ListBox id="lstInfos" runat="server" EnableViewState="False" Rows="6" Height="131px"></asp:ListBox>
</p>
</form>
</body>
</html>
Нам потрібно написати процедуру [btnEnvoyer_Click] для обробки події [Click] на цій кнопці. Стан перемикача або прапорця визначається його атрибутом [Checked] — це логічне значення, яке дорівнює «true», якщо прапорець встановлено, і «false» в іншому випадку. Отже, нам достатньо записати у списку [lstInfos] значення атрибута [Checked] для шести перемикачів та прапорців. Оскільки в цьому немає особливих труднощів, ми трохи поекспериментуємо:
<script runat="server">
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' інформація розміщується у списку
for each c as control in FindControl("frmControls").controls
' чи є цей елемент управління похідним від CheckBox
if TypeOf(c) is CheckBox then
lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
end if
next
End Sub
</script>
Сторінку можна розглядати як деревоподібну структуру елементів управління. У нашому прикладі сторінка містить текст та серверні елементи управління. Текст розглядається як окремий елемент управління під назвою [LiteralControl]. Кожен фрагмент тексту створює такий елемент управління, навіть послідовність пробілів між двома елементами управління. Кожен елемент управління має атрибут ID, який його ідентифікує. Саме цей атрибут ID з’являється у тегах:
Якщо не брати до уваги елементи управління [LiteralControl], на розглянутій сторінці є такі елементи управління:
- [HtmlForm] — це форма [ID=frmControls]. Вона, у свою чергу, є контейнером елементів управління. Вона містить такі елементи управління:
-- [ID=RadioButton1] типу [RadioButton]
-- [ID=RadioButton2] типу [RadioButton]
-- [ID=RadioButton3] типу [RadioButton]
-- [ID=CheckBoxA] типу [CheckBox]
-- [ID=CheckBoxA] типу [CheckBox]
-- [ID=CheckBoxA] типу [CheckBox]
-- [ID=btnEnvoyer] типу [Button]
Елемент управління має такі властивості:
повертає набір дочірніх елементів управління з [Control], якщо такі є | |
повертає елемент управління, ідентифікований за ID, що знаходиться в корені дерева дочірніх елементів управління [Control]. У наведеному вище прикладі: Page.FindControl("frmControls") позначає контейнер [HtmlForm]. Щоб отримати доступ до перемикача [RadioButton1], потрібно написати Page.FindControl("frmControls").FindControl("RadioButton1") | |
ідентифікатор [Control] |
Повернемося до коду процедури [btnEnvoyer_Click]:
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' інформація розміщується у списку
for each c as control in FindControl("frmControls").controls
' чи є цей елемент управління похідним від CheckBox?
if TypeOf(c) is CheckBox then
lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
end if
next
End Sub
Ми хочемо відобразити стан перемикачів та прапорців, що знаходяться у формі. Ми обходимо всі елементи управління цієї форми. Якщо поточний елемент управління є типом, похідним від [CheckBox], ми відображаємо його властивість [Checked]. Оскільки клас [RadioButton] є похідним від класу [CheckBox], перевірка діє для обох типів елементів управління. На знімку екрана, наведеному вище, показано приклад виконання.
7.10. Компоненти CheckBoxList, RadioButtonList
Іноді потрібно, щоб користувач обирав між значеннями, які невідомі на момент розробки сторінки. Ці варіанти беруться з конфігураційного файлу, бази даних тощо і стають відомими лише під час виконання. Існують рішення цієї проблеми, і ми з ними стикалися. Список з одним вибором добре підходить, коли користувач може зробити лише один вибір, а список з множинним вибором — коли він може зробити кілька виборів. З естетичної точки зору, а також якщо кількість варіантів вибору невелика, можна використовувати перемикачі замість списку з одинарним вибором або прапорці замість списку з множинним вибором. Це можливо за допомогою компонентів [CheckBoxList] та [RadioButtonList].
Класи [CheckBoxList] та [RadioButtonList] походять від того самого класу [ListControl], що й класи [DropDownList] та [ListBox], розглянуті раніше. Тому ми знову зустрінемо деякі властивості та методи, розглянуті для цих класів, а саме ті, що насправді належали класу [ListControl].
колекція типу [ListItemCollection], що містить елементи випадаючого списку. Елементи цієї колекції мають тип [ListItem]. | |
кількість елементів колекції [Items] | |
елемент № i зі списку — типу [ListItem] | |
щоб додати новий елемент [ListItem] до колекції [Items] | |
щоб видалити всі елементи з колекції [Items] | |
для видалення елемента № i з колекції [Items] | |
1-й елемент [ListItem] колекції [Items], властивість якого [Selected] має значення «true» | |
номер попереднього елемента в колекції [Items] |
Деякі властивості є специфічними для класів [CheckBoxList] та [RadioButtonList]:
[horizontal] або [vertical] для горизонтальних або вертикальних списків. |
Елементи колекції [Items] мають тип [ListItem]. Кожен елемент [ListItem] генеруватиме різний тег залежно від того, чи йдеться про об’єкт [CheckBoxList] чи [RadioButtonList]:
Або
Ми опишемо деякі властивості та методи класу [ListItem]:
конструктор — створює елемент [ListItem] із властивостями text та value. Елемент ListItem(T,V) генерує тег HTML <input type="checkbox" value="V">T або <input type="radio" value="V">T залежно від випадку. | |
булеве значення. Якщо значення «true», відповідна опція зі списку HTML матиме атрибут [selected="selected"]. Цей атрибут вказує браузеру, що відповідний елемент має відображатися як вибраний у списку HTML | |
текст T опції HTML <input type=".." value="V" [selected="selected"]>T | |
значення атрибута Value опції HTML <input type=".." value="V" [selected="selected"]>T |
Ми пропонуємо створити таку сторінку [form8b.aspx]:

№ | назва | тип | властивості | роль |
1 | RadioButtonList | EnableViewState=true RepeatDirection=horizontal | список перемикачів | |
2 | CheckBoxList | EnableViewState=true RepeatDirection=horizontal | список прапорців | |
3 | Кнопка | кнопка [submit], яка відображає в [4] список елементів, вибраних у обох списках | ||
4 | ListBox | EnableViewState=false | список значень |
Код для відображення сторінки такий:
<%@ Page Language="VB" autoeventwireup="false" %>
<script runat="server">
...
</script>
<html>
<head>
</head>
<body>
<form id="frmControls" runat="server">
<h3>Listes de cases à cocher
</h3>
<p>
<asp:RadioButtonList id="RadioButtonList1" runat="server" RepeatDirection="Horizontal"></asp:RadioButtonList>
</p>
<p>
<asp:CheckBoxList id="CheckBoxList1" runat="server" RepeatDirection="Horizontal"></asp:CheckBoxList>
</p>
<p>
<asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
</p>
<p>
<asp:ListBox id="lstInfos" runat="server" EnableViewState="False" Rows="6"></asp:ListBox>
</p>
</form>
</body>
</html>
Контрольний код такий:
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs) handles MyBase.Load
' списки заповнюються, якщо функція викликається вперше
if not IsPostBack then
' тексти для списку RadioButton
dim textesRadio() as String = {"1","2","3","4"}
' тексти для списку CheckBox
dim textesCheckBox() as String = {"un","deux","trois","quatre"}
' заповнення списку з перемикачами
dim i as integer
for i=0 to textesRadio.length-1
RadioButtonList1.Items.Add(new ListItem(textesRadio(i)))
next
' вибір елемента № 1
RadioButtonList1.SelectedIndex=1
' заповнення списку з прапорцями
for i=0 to textesCheckBox.length-1
CheckBoxList1.Items.Add(new ListItem(textesCheckBox(i)))
next
end if
end sub
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' внесення інформації у список lstinfos
affiche(RadioButtonList1)
affiche(CheckBoxList1)
End Sub
sub affiche(l1 as ListControl)
' відображення значень вибраних елементів з l1
' що робити?
if l1.selectedindex=-1 then return
dim i as integer
' починаємо з кінця
for i= 0 to l1.items.count-1
' вибрано?
if l1.items(i).selected then
lstInfos.Items.Add("["+TypeName(l1)+"] ["+l1.items(i).text+"] sélectionné")
end if
next
end sub
</script>
У процедурі [Page_Load], яка виконується під час кожного завантаження сторінки, ініціалізуються обидва списки. Щоб уникнути їхньої ініціалізації щоразу, використовується властивість [IsPostBack], яка забезпечує ініціалізацію лише під час першого завантаження. Наступного разу списки будуть автоматично оновлені за допомогою механізму [VIEWSTATE]. Після відображення сторінки користувач ставить галочки у відповідних полях і натискає кнопку [Envoyer]. Після цього значення з форми надсилаються до самої форми. Після виконання [Page_Load] виконується процедура [btnEnvoyer_Click]. Вона викликає процедуру [affiche] для заповнення списку [lstInfos]. Ця процедура отримує як параметр об’єкт типу [ListControl], що дозволяє передавати їй як об’єкт [RadioButtonList], так і об’єкт [CheckBoxList] — класи, похідні від [ListControl]. Список [lstInfos] може мати значення атрибута від [EnableViewState] до [false], оскільки його стан не потрібно зберігати між різними запитами.
7.11. Компоненти Panel, LinkButton
Тег <asp:panel> дозволяє вставити контейнер елементів управління на сторінку. Перевага контейнера полягає в тому, що деякі його властивості застосовуються до всіх елементів управління, які він містить. Це стосується його властивості [Visible]. Ця властивість існує для кожного серверного елемента управління. Якщо контейнер має властивість [Visible=false], кожен з його елементів керування буде керуватися власною властивістю [Visible]. Якщо він має властивість [Visible=false], то контейнер і все, що він містить, не відображається. Це може бути простіше, ніж керувати властивістю [Visible] для кожного з елементів управління в контейнері.
Тег <asp:LinkButton> дозволяє вставити посилання в код розмітки сторінки. Вона виконує функцію, аналогічну кнопці [Button]. Фактично вона викликає POST на стороні клієнта за допомогою пов’язаної з нею функції JavaScript. Ми створюємо сторінку [form9.aspx], щоб отримати таке оформлення:

№ | назва | тип | властивості | роль |
1 | Панель | EnableViewState=true | контейнер елементів управління | |
2 | ListBox | EnableViewState=true | список із трьох значень | |
3 | LinkButton | EnableViewState=false | посилання для приховування контейнера |
Коли контейнер приховано, з’являється нове посилання:

№ | ім'я | тип | властивості | роль |
4 | LinkButton | EnableViewState=false | посилання для відображення контейнера |
Код для відображення сторінки такий:
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:Panel id="Panel1" runat="server" BorderStyle="Ridge" BorderWidth="1px">
<p>
Conteneur
</p>
<p>
<asp:ListBox id="ListBox1" runat="server">
<asp:ListItem Value="1">un</asp:ListItem>
<asp:ListItem Value="2">deux</asp:ListItem>
<asp:ListItem Value="3" Selected="True">trois</asp:ListItem>
</asp:ListBox>
</p>
</asp:Panel>
</p>
<p>
<asp:LinkButton id="lnkVoir" onclick="lnkVoir_Click" runat="server">Voir le conteneur</asp:LinkButton>
</p>
<p>
<asp:LinkButton id="lnkCacher" onclick="lnkCacher_Click" runat="server">Cacher le conteneur</asp:LinkButton>
</p>
</form>
</body>
</html>
Зауважимо, що цей код ініціалізує список [ListBox1] трьома значеннями. Обробники подій [Clic] для обох посилань є такими:
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
...
end sub
Sub lnkVoir_Click(sender As Object, e As EventArgs)
' відображає контейнер 1
panel1.Visible=true
' змінити посилання
lnkVoir.visible=false
lnkCacher.visible=true
End Sub
Sub lnkCacher_Click(sender As Object, e As EventArgs)
' приховати контейнер 1
panel1.Visible=false
' змінює посилання
lnkVoir.visible=true
lnkCacher.visible=false
End Sub
</script>
Ми використаємо процедуру [Page_Load] для ініціалізації форми. Це буде зроблено під час першого запиту (IsPostBack=false):
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
' вперше
if not IsPostBack then
' відображається контейнер
lnkVoir_Click(nothing,nothing)
end if
end sub
.....
</script>
7.12. Продовження...
У попередніх параграфах було представлено низку серверних компонентів. Щоразу розглядалися лише деякі їхні властивості. Щоб глибше вивчити ці компоненти, читач може скористатися різними способами:
- ознайомитися з властивостями компонента за допомогою IDE, наприклад WebMatrix. Цей файл, а саме, розкриває основні властивості компонентів, що використовуються у формі
- ознайомитися з документацією .NET, щоб дізнатися про всі класи, що відповідають кожному з серверних компонентів. Це найкращий спосіб для повного освоєння компонента. У ній ви знайдете дерево класів, що ведуть до компонентів, а також властивості, методи, конструктори та події кожного з них. Крім того, документація іноді містить приклади.
У цьому розділі ми використовували техніку «все в одному» ([WebMatrix]), c.a.d, за якою код візуалізації та код управління сторінки розміщуються в одному файлі. Загалом ми не рекомендуємо цей метод, а ратуємо за так званий метод [codebehind], який використовувався раніше і передбачає розміщення цих двох кодів у двох окремих файлах. Нагадаємо, що перевага такого розділення полягає в тому, що код управління можна скомпілювати без необхідності запуску веб-додатка. Крім того, наші приклади, як ми пояснювали на початку розділу, мали цілком особливий характер: вони складалися з єдиної сторінки, яка була формою, що обмінювалася між клієнтом і сервером у послідовних циклах запит-відповідь, причому перший запит клієнта був GET, а наступні — POST.
7.13. Серверні компоненти та контролер додатка
У попередніх розділах ми створили кілька веб-додатків. Усі вони були побудовані за архітектурою MVC (Модель-Вигляд-Контролер), яка розділяє додаток на чітко відокремлені блоки та полегшує його обслуговування. Тоді ми створювали наші користувацькі інтерфейси за допомогою стандартних тегів HTML. З огляду на те, що ми щойно розглянули, цілком природно, що тепер ми хочемо використовувати серверні компоненти. Повернімося до проблеми, яку ми вже детально вивчали, а саме до розрахунку податку. Її архітектура MVC була такою:

Додаток має два види: [formulaire.aspx] та [erreurs.aspx]. Вид [formulaire.aspx] відображається, коли URL-адреса [main.aspx] запитується вперше:

Користувач заповнює форму:

і натискає кнопку [Calculer], щоб отримати таку відповідь:

У додатку MVC кожен запит повинен проходити через контролер, у даному випадку [main.aspx]. Це означає, що коли форма [formulaire.aspx] була заповнена користувачем, її потрібно відправити на [main.aspx], а не на [formulaire.aspx]. Це просто неможливо, якщо ми створюємо користувацький інтерфейс [formulaire.aspx] із використанням серверних компонентів ASP. Щоб у цьому переконатися, створимо форму [formtest.aspx] із компонентом <asp:button>:
<%@ Page Language="VB" EnableViewState="false"%>
<html>
<head>
<title>test</title>
</head>
<body>
<form action="main.aspx" runat="server">
<p>
<asp:Button id="btnTest" runat="server" EnableViewState="false" Text="Test"></asp:Button>
</p>
</form>
</body>
</html>
Зверніть увагу на атрибут [action="main.aspx"] тегу <form..>. Запустимо цю програму. На сторінці відображається лише одна кнопка:

Розглянемо код HTML, надісланий сервером:
<html>
<head>
<title>test</title>
</head>
<body>
<form name="_ctl0" method="post" action="formtest.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwtNTMwNzcxMzI0Ozs+" />
<p>
<input type="submit" name="btnTest" value="Test" id="btnTest" />
</p>
</form>
</body>
</html>
Бачимо, що POST у формі має за ціль саму форму [action="formtest.aspx"], тоді як ми вказали в [formtest.aspx] тег HTML сервера:
Атрибут [runat="server"] тегу <form> нав'язується нам через використання серверних компонентів. Якщо не вказати цей атрибут, виникає помилка компіляції. Коли його вказано, атрибут [action] тегу <form> ігнорується. Сервер завжди генерує атрибут [action], який вказує на саму форму. Звідси випливає, що в додатку MVC не можна використовувати форми, побудовані за допомогою тегу <form ... runat="server">. Однак цей тег є необхідним для всіх серверних компонентів ASP, які отримують введені користувачем дані. Іншими словами, у додатку MVC не можна використовувати серверні форми ASP. Це велике відкриття. Адже однією з сильних сторін маркетингової стратегії ASP.NET є те, що веб-додаток можна створювати так само, як і додаток для Windows. Це правда, якщо наш додаток не відповідає архітектурі MVC, але ще більш правдиво в іншому випадку. Однак архітектура MVC видається фундаментальним поняттям сучасної веб-розробки, яке важко ігнорувати.
Архітектуру MVC можна використовувати разом із формами на основі компонентів ASP для додатків із невеликою кількістю різних переглядів завдяки такому прийомі:
- додаток складається з однієї сторінки, яка виконує роль контролера
- види відображаються на цій сторінці у вигляді різних контейнерів — по одному контейнеру на кожен вид. Щоб відобразити певний вид, потрібно зробити видимим його контейнер і приховати інші
Це елегантне рішення, яке ми зараз реалізуємо в декількох прикладах
7.14. Приклади додатків MVC із серверними компонентами ASP
7.14.1. Приклад 1
У цьому першому прикладі ми реалізуємо серверні компоненти, які ми вже представили. Сторінка [form10.aspx] матиме такий вигляд:
![]() | ![]() |
На знімку екрана ліворуч вище показано форму, яку бачить клієнт. Він заповнює її та підтверджує за допомогою [Envoyer]. Сервер повертає йому сторінку зі списком введених значень (скріншот праворуч). За допомогою посилання користувач може повернутися до форми. Він бачить її в тому вигляді, в якому підтвердив. Код відображення [form10.aspx] такий:
<html>
<head>
<title>Exemple</title> <script language="javascript">
function effacer(){
alert("Vous avez cliqué sur [Effacer]")
}
</script>
</head>
<body>
<p>
Gestion d'un formulaire
</p>
<p>
<hr />
</p>
<form runat="server">
<p>
<asp:Panel id="panelinfo" runat="server" EnableViewState="False">
<p>
Liste des valeurs obtenues
</p>
<p>
<asp:ListBox id="lstInfos" runat="server" EnableViewState="False"></asp:ListBox>
</p>
<p>
<asp:LinkButton id="LinkButton1" onclick="LinkButton1_Click" runat="server">Retour au formulaire</asp:LinkButton>
</p>
<p>
<hr />
</p>
</asp:Panel>
</p>
<p>
<asp:Panel id="panelform" runat="server" >
<table>
<tbody>
<tr>
<td>
Etes-vous marié(e)</td>
<td>
<asp:RadioButton id="rdOui" runat="server" GroupName="rdmarie"></asp:RadioButton>
Oui<asp:RadioButton id="rdNon" runat="server" GroupName="rdmarie" Checked="True"></asp:RadioButton>
Non</td>
</tr>
<tr>
<td>
Cases à cocher</td>
<td>
<asp:CheckBox id="chk1" runat="server"></asp:CheckBox>
1<asp:CheckBox id="chk2" runat="server"></asp:CheckBox>
2<asp:CheckBox id="chk3" runat="server"></asp:CheckBox>
3</td>
</tr>
<tr>
<td>
Champ de saisie</td>
<td>
<asp:TextBox id="txtSaisie" runat="server" MaxLength="20" Columns="20"></asp:TextBox>
</td>
</tr>
<tr>
<td>
Mot de passe</td>
<td>
<asp:TextBox id="txtmdp" runat="server" MaxLength="10" Columns="10" TextMode="Password"></asp:TextBox>
</td>
</tr>
<tr>
<td>
Boîte de saisie</td>
<td>
<asp:TextBox id="txtArea" runat="server" Columns="20" TextMode="MultiLine" Rows="3"></asp:TextBox>
</td>
</tr>
<tr>
<td>
Liste déroulante</td>
<td>
<asp:DropDownList id="cmbValeurs" runat="server"></asp:DropDownList>
</td>
</tr>
<tr>
<td>
Liste à choix unique</td>
<td>
<asp:ListBox id="lstSimple" runat="server"></asp:ListBox>
<asp:Button id="btnRazSimple" onclick="btnRazSimple_Click" runat="server" EnableViewState="False" Text="Raz"></asp:Button>
</td>
</tr>
<tr>
<td>
Liste à choix multiple</td>
<td>
<asp:ListBox id="lstMultiple" runat="server" SelectionMode="Multiple"></asp:ListBox>
<asp:Button id="razMultiple" onclick="razMultiple_Click" runat="server" EnableViewState="False" Text="Raz"></asp:Button>
</td>
</tr>
<tr>
<td>
Champ caché</td>
<td>
<asp:Label id="lblSecret" runat="server" visible="False"></asp:Label></td>
</tr>
<tr>
<td>
Bouton simple</td>
<td>
<input id="btnEffacer" onclick="effacer()" type="button" value="Effacer" /></td>
</tr>
<tr>
<td>
Bouton [reset]</td>
<td>
<input id="btnReset" type="reset" value="Rétablir" /></td>
</tr>
<tr>
<td>
Bouton [submit]</td>
<td>
<asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" EnableViewState="False" Text="Envoyer"></asp:Button>
</td>
</tr>
</tbody>
</table>
</asp:Panel>
</p>
</form>
</body>
</html>
Сторінка має два контейнери, по одному для кожного виду: [panelform] для виду «форма», [panelinfo] для виду «інформація». Список компонентів контейнера [panelForm] такий:
назва | тип | властивості | роль |
Панель | EnableViewState=true | вигляд форми | |
RadioButton | EnableViewState=true GroupName=rdmarie | перемикачі | |
CheckBox | EnableViewState=true | прапорці | |
TextBox | EnableViewState=true | поле введення | |
TextBox | EnableViewState=true | захищене поле введення | |
TextBox | EnableViewState=true | багаторядкове поле введення | |
DropDownList | EnableViewState=true | випадаючий список | |
ListBox | EnableViewState=true SelectionMode=Single | список з одинарним вибором | |
Кнопка | EnableViewState=false | скасовує вибір усіх елементів lstSimple | |
ListBox | EnableViewState=true SelectionMode=Multiple | список із можливістю множинного вибору | |
Кнопка | EnableViewState=false | скасувати вибір усіх елементів lstMultiple | |
Мітка | EnableViewState=true Visible=false | приховане поле | |
HTML стандарт | відображає попередження | ||
Кнопка | EnableViewState=false | кнопка [submit] у формі | |
HTML стандарт | кнопка [reset] у формі |
Тут важливу роль відіграє [VIEWSTATE] для компонентів. Усі компоненти, крім кнопок, повинні мати властивість [EnableViewState=true]. Щоб зрозуміти причину цього, слід згадати, як працює додаток. Припустимо, що поле [txtSaisie] має властивість [EnableViewState=false]:
- клієнт вперше запитує сторінку [form10.aspx]. Він отримує форму
- заповнює його та відправляє, натиснувши кнопку [Envoyer]. Поля введення даних надсилаються, і сервер присвоює серверним компонентам надіслане значення або їхній стан [VIEWSTATE], якщо такий стан був. Таким чином, полю [txtSaisie] присвоюється значення, введене користувачем. Також на цьому етапі його стан [VIEWSTATE] не має значення. У результаті операції надсилається представлення [informations] — фактично це все та ж сторінка [form10.aspx], але з іншим відображеним контейнером.
- Користувач переглядає цей новий вигляд і використовує посилання [Retour au formulaire], щоб повернутися до нього. Тоді виконується перехід POST до [form10.aspx]. У цьому випадку надсилається щонайбільше одне значення: значення, вибране користувачем у списку інформації, яке надалі не використовується. У будь-якому разі поле [txtSaisie] не надсилається.
- Сервер отримує POST і присвоює серверним компонентам відправлене значення або їхній стан [VIEWSTATE], якщо такий стан був. У даному випадку [txtNom] не має відправленого значення. Якщо його атрибут [EnableViewState] дорівнює [false], йому буде присвоєно порожній рядок. Оскільки потрібно, щоб він мав значення, введене користувачем, він повинен мати властивість [EnableViewState=true].
Контейнер [panelinfo] має такі елементи управління:
ім’я | тип | властивості | роль |
Панель | EnableViewState=false | перегляд інформації | |
ListBox | EnableViewState=false | перелік інформації, що підсумовує значення, введені користувачем | |
LinkButton | EnableViewState=false | посилання для повернення до форми |
Під час тестування, якщо подивитися на код HTML, згенерований вищенаведеним кодом представлення, можна здивуватися коду, згенерованому для прихованого поля [lblSecret]:
Компонент [lblSecret] не перетворюється на код HTML, оскільки він має властивість [Visible=false]. Однак, оскільки він має властивість [EnableViewState=true], його значення все одно буде збережено у прихованому полі [__VIEWSATE]. Отже, ми зможемо його отримати, як покажуть тести.
Залишилося написати обробники подій. У [Page_Load] ми ініціалізуємо форму:
Sub page_Load(sender As Object, e As EventArgs)
' вперше ініціалізуються елементи
' наступні рази ці елементи відновлюють свої значення за допомогою VIEWSTATE
if IsPostBack then return
' ініціалізація форми
' панель інформації не відображається
panelinfo.visible=false
' панель форми відображена
panelform.visible=true
' перемикачі
rdNon.Checked=true
' прапорці
chk2.Checked=true
' поле введення
txtSaisie.Text="qqs mots"
' поле для пароля
txtMdp.Text="ceciestsecret"
' поле введення
txtArea.Text="ligne"+ControlChars.CrLf+"ligne2"+ControlChars.CrLf
' комбінований список
dim i as integer
for i=1 to 4
cmbValeurs.Items.Add(new ListItem("choix"+i.ToString,i.ToString))
next
cmbValeurs.SelectedIndex=1
' список з одним вибором
for i=1 to 7
lstSimple.Items.Add(new ListItem("simple"+i.ToString,i.ToString))
next
lstSimple.SelectedIndex=0
' список з можливістю множинного вибору
for i=1 to 10
lstMultiple.Items.Add(new ListItem("multiple"+i.ToString,i.ToString))
next
lstMultiple.Items(0).Selected=true
lstMultiple.Items(2).Selected=true
' приховане поле
lblSecret.Text="secret"
End Sub
Натискання на кнопки [lstRazSimple] та [lstMultiple]:
Sub btnRazSimple_Click(sender As Object, e As EventArgs)
' очищення простого списку
lstSimple.SelectedIndex=-1
End Sub
Sub razMultiple_Click(sender As Object, e As EventArgs)
' очищення списку з множинним вибором
lstMultiple.SelectedIndex=-1
End Sub
Натисніть на кнопку [Envoyer]:
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' панель інформації відображається, а панель форми прихована
panelinfo.Visible=true
panelform.visible=false
' отримуються надіслані значення та заносяться до lstInfos
' перемикачі
dim info as string="état marital : "+iif(rdoui.checked,"marié"," non marié")
affiche(info)
' прапорці
info=" cases cochées : "+iif(chk1.checked,"1 oui","1 non")+","+ _
iif(chk2.checked,"2 oui","2 non")+","+iif(chk3.checked,"3 oui","3 non")
affiche(info)
' поле введення
affiche("champ de saisie : " + txtSaisie.Text.Trim)
' пароль
affiche("mot de passe : " + txtMdp.Text.Trim)
' поле введення
dim lignes() as String
lignes=new Regex("\r\n").Split(txtArea.Text.Trim)
dim i as integer
for i=0 to lignes.length-1
lignes(i)="["+lignes(i).Trim+"]"
next
affiche("Boîte de saisie : " + String.Join(",",lignes))
' комбінований список
affiche("éléments sélectionnés dans combo : "+selection(cmbValeurs))
' простий список
affiche("éléments sélectionnés dans liste simple : "+selection(lstSimple))
' багатоелементний список
affiche("éléments sélectionnés dans liste multiple : "+selection(lstMultiple))
' приховане поле
affiche ("Champ caché : " + lblSecret.Text)
End Sub
sub affiche(msg as String)
' відображає повідомлення в lstInfos
lstInfos.Items.Add(msg)
end sub
function selection(liste as ListControl) as string
' пробігається по елементах списку
' щоб знайти ті, які виділені
dim i as integer
dim info as string=""
for i=0 to liste.Items.Count-1
if liste.Items(i).Selected then info+="[" + liste.Items(i).Text + "]"
next
return info
end function
І нарешті, натисніть на посилання [Retour vers le formulaire ]:
Sub LinkButton1_Click(sender As Object, e As EventArgs)
' відображається форма та приховується інформаційна панель
panelform.visible=true
panelinfo.visible=false
End Sub
7.14.2. Приклад 2
Тут ми повертаємося до програми, яку вже розглядали раніше, з використанням стандартних форм HTML. Ця програма дозволяє проводити моделювання розрахунків податків. Вона базується на класі [impot], про який ми тут не будемо згадувати. Цьому класу потрібні дані, які він отримує з джерела даних OLEDB. Для прикладу ми використаємо джерело ACCESS.
7.14.2.1. Структура MVC додатка
Структура MVC додатка має такий вигляд:

Ці три подання будуть вбудовані в код відображення контролера [main.aspx] у вигляді контейнерів. Отже, цей додаток має єдину сторінку [main.aspx].
7.14.2.2. Види веб-додатку
Вигляд [formulaire] — це форма введення даних, що дозволяє розрахувати податок для користувача:

Користувач заповнює форму:

Він використовує кнопку [Envoyer], щоб ініціювати розрахунок свого податку. Він отримує наступний вигляд [simulations]:

Він повертається до форми за посиланням вище. Форма відображається в тому стані, в якому він її заповнив. Він може припуститися помилок під час введення даних:

Про них йому повідомляє вікно [erreurs]:

Користувач повертається до форми за посиланням вище. Форма відображається у тому стані, в якому він її заповнив. Він може виконати нові симуляції:

Тоді він отримує вікно [simulations] із ще одним моделюванням:

Нарешті, якщо джерело даних недоступне, про це повідомляється користувачеві у вікні [erreurs]:

7.14.2.3. Код для відображення додатка
Нагадаємо, що сторінка [main.aspx] об’єднує всі перегляди. Це єдина форма з трьома контейнерами:
- [panelform] для подання [formulaire]
- [panelerreurs] для подання [erreurs]
- [panelsimulations] для подання [simulations]
Ми повертаємося до розділення коду представлення та коду управління у два окремі файли. Перший буде у [main.aspx], а другий — у [main.aspx.vb]. Код [main.aspx] виглядає наступним чином:
<%@ page codebehind="main.aspx.vb" inherits="vs.main" AutoEventWireUp="false" %>
<HTML>
<HEAD>
<title>Calcul d'impôt </title>
</HEAD>
<body>
<P>Calcul de votre impôt</P>
<HR width="100%" SIZE="1">
<FORM id="Form1" runat="server">
<asp:panel id="panelform" Runat="server">
<TABLE id="Table1" cellSpacing="1" cellPadding="1" border="0">
<TR>
<TD height="19">Etes-vous marié(e)</TD>
<TD height="19">
<asp:RadioButton id="rdOui" runat="server" GroupName="rdMarie"></asp:RadioButton>Oui
<asp:RadioButton id="rdNon" runat="server" GroupName="rdMarie" Checked="True"></asp:RadioButton>Non</TD>
</TR>
<TR>
<TD>Nombre d'enfants</TD>
<TD>
<asp:TextBox id="txtEnfants" runat="server" MaxLength="3" Columns="3"></asp:TextBox></TD>
</TR>
<TR>
<TD>Salaire annuel (euro)</TD>
<TD>
<asp:TextBox id="txtSalaire" runat="server" MaxLength="10" Columns="10"></asp:TextBox></TD>
</TR>
</TABLE>
<P>
<asp:Button id="btnCalculer" runat="server" Text="Calculer"></asp:Button>
<asp:Button id="btnEffacer" runat="server" Text="Effacer"></asp:Button></P>
</asp:panel>
<asp:panel id="panelerreurs" runat="server">
<P>Les erreurs suivantes se sont produites :</P>
<P>
<asp:Literal id="erreursHTML" runat="server"></asp:Literal></P>
<P></P>
<asp:LinkButton id="lnkForm1" runat="server">Retour au formulaire</asp:LinkButton>
</asp:panel>
<asp:panel id="panelsimulations" runat="server">
<P>
<TABLE>
<TR>
<TH>
Marié</TH>
<TH>
Enfants</TH>
<TH>
Salaire annuel</TH>
<TH>
Impôt à payer (euro)</TH></TR>
<asp:Literal id="simulationsHTML" runat="server"></asp:Literal></TABLE>
<asp:LinkButton id="lnkForm2" runat="server">Retour au formulaire</asp:LinkButton></P>
</asp:panel>
</FORM>
</body>
</HTML>
Ми виділили три контейнери. Зверніть увагу, що всі вони знаходяться в тезі <form runat="server">. Це обов’язково, оскільки, щоб скористатися перевагами серверних компонентів, їх потрібно розміщувати саме в такій тезі. Важливо зрозуміти, що тут ми маємо єдину форму, яку обмінюватимуться клієнт і веб-сервер. Отже, ми маємо конфігурацію, яка використовується у всьому цьому розділі, присвяченому серверним компонентам. Розглянемо детальніше компоненти кожного контейнера:
Контейнер [panelform]:
ім’я | тип | властивості | роль |
Панель | EnableViewState=true | погляд на форму | |
RadioButton | EnableViewState=true GroupName=rdmarie | перемикачі | |
TextBox | EnableViewState=true | кількість дітей | |
TextBox | EnableViewState=true | річний оклад | |
Кнопка | кнопка [submit] у формі — запускає розрахунок податку | ||
Button | Кнопка [submit] у формі — очищає форму |
Контейнер [panelerreurs]:
назва | тип | властивості | роль |
Панель | EnableViewState=true | перегляд помилок | |
LinkButton | EnableViewState=true | посилання на форму | |
Літерал | код HTML зі списку помилок |
Контейнер [panelsimulations]:
назва | тип | властивості | роль |
Панель | EnableViewState=true | перегляд симуляцій | |
LinkButton | EnableViewState=true | посилання на форму | |
Літерал | код HTML зі списку моделювань у таблиці HTML |
7.14.2.4. Код управління додатком
Код контролю додатка розміщений у файлах [global.asax.vb] та [main.aspx.vb]. Файл [global.asax] визначено наступним чином:
Файл [global.asax.vb] має такий вигляд:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Collections
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' створюється об’єкт імпорту
Dim objImpot As impot
Try
objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
' додаємо об’єкт до програми
Application("objImpot") = objImpot
' помилок немає
Application("erreur") = False
Catch ex As Exception
'сталася помилка, її фіксують у додатку
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' початок сеансу — створюється порожній список симуляцій
Session.Item("simulations") = New ArrayList
End Sub
End Class
Під час запуску програми (перший запит до програми) виконується процедура [Application_Start]. Вона намагається створити об’єкт типу [impot], беручи дані з джерела OLEDB. Якщо читач забув, де саме було визначено цей клас, йому слід ознайомитися з розділом 5. Створення об’єкта [impot] може завершитися невдачею, якщо джерело даних недоступне. У цьому випадку помилка зберігається в додатку, щоб усі наступні запити знали, що цей об’єкт не вдалося правильно ініціалізувати. Якщо створення проходить успішно, створений об’єкт [impot] також зберігається в додатку. Він використовуватиметься всіма запитами на розрахунок податку. Коли клієнт надсилає свій перший запит, процедура [Application_Start] створює для нього сесію. Ця сесія призначена для збереження різних моделювань розрахунку податку, які він буде виконувати. Вони будуть збережені в об’єкті [ArrayList], пов’язаному з ключем сесії «simulations». Під час запуску сесії цей ключ пов’язується з порожнім об’єктом [ArrayList]. Інформація, необхідна для роботи програми, розміщується в її конфігураційному файлі [wenConfig]:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="chaineConnexion" value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\webforms\vs\impots5\impots.mdb" />
</appSettings>
</configuration>
Ключ [chaineConnexion] позначає ланцюг підключення до джерела OLEDB. Інша частина контрольного коду міститься в [main.aspx.vb]:
Imports System.Collections
Imports Microsoft.VisualBasic
Imports st.istia.univangers.fr
Imports System
Public Class main
Inherits System.Web.UI.Page
Protected WithEvents rdOui As System.Web.UI.WebControls.RadioButton
Protected WithEvents rdNon As System.Web.UI.WebControls.RadioButton
Protected WithEvents txtEnfants As System.Web.UI.WebControls.TextBox
Protected WithEvents txtSalaire As System.Web.UI.WebControls.TextBox
Protected WithEvents btnCalculer As System.Web.UI.WebControls.Button
Protected WithEvents btnEffacer As System.Web.UI.WebControls.Button
Protected WithEvents panelform As System.Web.UI.WebControls.Panel
Protected WithEvents lnkForm1 As System.Web.UI.WebControls.LinkButton
Protected WithEvents lnkForm2 As System.Web.UI.WebControls.LinkButton
Protected WithEvents panelerreurs As System.Web.UI.WebControls.Panel
Protected WithEvents panelsimulations As System.Web.UI.WebControls.Panel
Protected WithEvents simulationsHTML As System.Web.UI.WebControls.Literal
Protected WithEvents erreursHTML As System.Web.UI.WebControls.Literal
' локальні змінні
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
End Sub
Private Sub afficheFormulaire()
...
End Sub
Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
...
End Sub
Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
...
End Sub
Private Function checkData() As ArrayList
...
End Function
Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
....
End Sub
Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
...
End Sub
Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
...
End Sub
Private Sub razForm()
...
End Sub
End Class
Le premier événement traité par le code est [Page_Load] :
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' спочатку перевіряємо стан програми
If CType(Application("erreur"), Boolean) Then
' додаток не зміг ініціалізуватися
' виводимо вікно помилок
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
afficheErreurs(erreurs, "")
Exit Sub
End If
' помилок немає — при першому запиті відображається форма
If Not IsPostBack Then afficheFormulaire()
End Sub
Нагадаємо, що під час виконання процедури [Page_Load] на клієнті POST усі компоненти форми мають значення: або значення, надіслане клієнтом (якщо таке є), або попереднє значення компонента, отримане за допомогою [VIEWSTATE]. У цій формі всі компоненти мають властивість [EnableViewState=true]. Перш ніж розпочати обробку запиту, ми переконуємося, що додаток вдалося правильно ініціалізувати. Якщо це не так, ми відображаємо представлення [erreurs] за допомогою процедури [afficheErreurs]. Якщо це перший запит (IsPostBack=false), ми відображаємо представлення [formulaire] за допомогою [afficheFormulaire].
Процедура, що відображає вигляд [erreurs], виглядає наступним чином:
Private Sub afficheErreurs(ByRef erreurs As ArrayList, ByRef lien As String)
' відображається контейнер помилок
panelerreurs.Visible = True
Dim i As Integer
erreursHTML.Text = ""
For i = 0 To erreurs.Count - 1
erreursHTML.Text += "<li>" + erreurs(i).ToString + "</li>" + ControlChars.CrLf
Next
lnkForm1.Text = lien
' інші контейнери приховані
panelform.Visible = False
panelsimulations.Visible = False
End Sub
Процедура має два параметри:
- список повідомлень про помилки у [erreurs]
- текст посилання у [lien]
Код HTML, який потрібно згенерувати для списку помилок, розміщується в літералі [erreursHTML]. Текст посилання розміщується у властивості [Text] об’єкта [LinkButton] подання.
Процедура, що відображає представлення [formulaire], виглядає наступним чином:
Private Sub afficheFormulaire()
' відображається форма
panelform.Visible = True
' інші контейнери приховані
panelerreurs.Visible = False
panelsimulations.Visible = False
End Sub
Ця процедура лише робить контейнер [panelform] видимим. Компоненти відображаються зі своїми поточними або попередніми значеннями (VIEWSTATE).
Коли користувач натискає кнопку [Calculer] у вікні [formulaire], виконується перехід від POST до [main.aspx]. Виконується процедура [Page_Load], а потім — процедура [btnCalculer_Click]:
Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
' перевіряється правильність введених даних
Dim erreurs As ArrayList = checkData()
' якщо є помилки, про це повідомляють
If erreurs.Count <> 0 Then
' відображається сторінка помилок
afficheErreurs(erreurs, "Retour au formulaire")
Exit Sub
End If
' помилок немає — обчислюється податок
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
rdOui.Checked, CType(txtEnfants.Text, Integer), CType(txtSalaire.Text, Long))
' результат додається до існуючих моделювань
Dim simulation() As String = New String() {CType(IIf(rdOui.Checked, "oui", "non"), String), _
txtEnfants.Text.Trim, txtSalaire.Text.Trim, impot.ToString}
' результат додається до існуючих моделювань
Dim simulations As ArrayList = CType(Session.Item("simulations"), ArrayList)
simulations.Add(simulation)
' моделювання додається до сесії та контексту
Session.Item("simulations") = simulations
' відображається сторінка результатів
afficheSimulations(simulations, "Retour au formulaire")
End Sub
Процедура починається з перевірки правильності заповнення полів форми за допомогою процедури [checkData], яка повертає список [ArrayList] з повідомленнями про помилки. Якщо список не порожній, то відображається вікно [erreurs] і процедура завершується. Якщо введені дані є правильними, сума податку розраховується за допомогою об’єкта типу [impot], який було збережено в додатку під час його запуску. Ця нова симуляція додається до списку вже виконаних симуляцій і зберігається в сесії.
Функція [CheckData] перевіряє правильність даних. Вона повертає список [ArrayList] з повідомленнями про помилки, який є порожнім, якщо дані є правильними:
Private Function checkData() As ArrayList
' спочатку помилок немає
Dim erreurs As New ArrayList
' кількість дітей
Try
Dim nbEnfants As Integer = CType(txtEnfants.Text, Integer)
If nbEnfants < 0 Then Throw New Exception
Catch
erreurs.Add("Le nombre d'enfants est incorrect")
End Try
' зарплата
Try
Dim salaire As Long = CType(txtSalaire.Text, Long)
If salaire < 0 Then Throw New Exception
Catch
erreurs.Add("Le salaire annuel est incorrect")
End Try
' виводиться список помилок
Return erreurs
End Function
Нарешті, погляд [simulations] відображається за допомогою наступної процедури [afficheSimulations]:
Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
' відображається вікно «Моделювання»
panelsimulations.Visible = True
' інші контейнери приховані
panelerreurs.Visible = False
panelform.Visible = False
' вміст вікна «Симуляції»
' кожна симуляція — це масив із 4 елементів типу string
Dim simulation() As String
Dim i, j As Integer
simulationsHTML.Text = ""
For i = 0 To simulations.Count - 1
simulation = CType(simulations(i), String())
simulationsHTML.Text += "<tr>"
For j = 0 To simulation.Length - 1
simulationsHTML.Text += "<td>" + simulation(j) + "</td>"
Next
simulationsHTML.Text += "</tr>" + ControlChars.CrLf
Next
' посилання
lnkForm2.Text = lien
End Sub
Процедура має два параметри:
- список симуляцій у [simulations]
- текст посилання в [lien]
Код HTML, який потрібно згенерувати для списку симуляцій, розміщується в літералі [simulationsHTML]. Текст посилання розміщується у властивості [Text] об’єкта [LinkButton] у поданні.
Коли користувач натискає кнопку [Effacer] у вікні [formulaire], виконується процедура [btnEffacer_click] (завжди після [Page_Load]):
Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
' відображає порожню форму
razForm()
afficheFormulaire()
End Sub
Private Sub razForm()
' очищає форму
rdOui.Checked = False
rdNon.Checked = True
txtEnfants.Text = ""
txtSalaire.Text = ""
End Sub
Наведений вище код є достатньо простим, щоб не потребувати коментарів. Залишається лише обробити кліки на посиланнях у видах [erreurs] та [simulations]:
Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
' відображає форму
afficheFormulaire()
End Sub
Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
' відображає форму
afficheFormulaire()
End Sub
Обидві процедури просто відображають вікно [formulaire]. Ми знаємо, що поля цього вікна отримають значення, яке є або значенням, що було для них відправлено, або їхнім попереднім значенням. Оскільки в даному випадку POST клієнта не надсилає жодних значень для полів форми, вони відновлять свої попередні значення. Отже, форма відображається зі значеннями, введеними користувачем. Ми пам’ятаємо, що у версії без серверних компонентів ми виконували цю операцію відновлення самостійно.
7.14.2.5. Тестування
Усі файли, необхідні для роботи додатка, розміщуються в папці <application-path>: ![]() | Папка [bin] містить DLL-файл із класами [impot], [impotsData], [impotsOLEDB], необхідними для роботи програми: |
Читач може, за бажанням, ще раз ознайомитися з розділом 5, де пояснюється, як створити вищезазначений файл [impot.dll]. Після цього запускається сервер Cassini з параметрами (<application-path>,/impots5). За допомогою браузера запитується URL-адреса [http://impots5/main.aspx]:

Якщо перейменувати файл ACCESS [impots.mdb] на [impots1.mdb], з’явиться така сторінка:

7.14.3. Приклад 3
На цих двох прикладах ми показали, що можна створювати веб-додатки, які відповідають архітектурі MVC, використовуючи серверні компоненти. Останній приклад демонструє, що рішення з використанням серверних компонентів є простішим, ніж рішення зі стандартними тегами HTML. Наші два приклади містили лише одну сторінку з кількома видами в межах однієї сторінки. Можна реалізувати архітектуру MVC з кількома серверними формами ASP, якщо те, що вони відправляють значення форми на саму себе, не створює проблем. Це дуже часто трапляється у додатках з меню. Розглянемо такий приклад:

Ми зібрали на одній сторінці посилання на додатки, які ми написали до цього часу. Цей тип додатка добре підходить для архітектури MVC. Просто тепер є не один, а кілька контролерів.

Контролер [main.aspx] виконує роль головного контролера. Саме його викликають посилання на головній сторінці додатка. Він зможе виконати операції, спільні для всіх можливих дій, а потім запустить конкретну дію, пов’язану з використаним посиланням. Після цього він передасть управління одному з допоміжних контролерів — тому, який відповідає за виконання цієї дії. З цього моменту обмін даними відбувається між клієнтом і цим конкретним контролером. Головний контролер [main.aspx] більше не залучається. Отже, ми більше не перебуваємо в рамках MVC з єдиним контролером, який фільтрує всі запити. Кожен із вищезазначених контролерів може відображати кілька подань за допомогою механізму контейнерів у межах єдиної сторінки, яку ми розглянули.
Відсутність єдиного контролера, який обирає, які види відправляти клієнту, має свої недоліки. Розглянемо приклад обробки помилок. Кожна з дій, що надаються додатком, може потребувати відображення виду помилок. Кожен контролер [applix.aspx] матиме власний вигляд [erreurs], оскільки цей вигляд є просто окремим контейнером сторінки контролера. Немає можливості створити єдиний вигляд [erreurs], який би використовувався всіма окремими додатками. Дійсно, такий вигляд зазвичай містить посилання для повернення до форми з помилками, і її необхідно відновити у тому стані, в якому вона була перевірена, щоб користувач міг виправити свої помилки. Це відновлення здійснюється за допомогою механізму [VIEWSTATE], який не працює між різними контролерами. Якщо додатки розробляються різними людьми, існує ризик, що сторінки помилок матимуть різний вигляд залежно від дії, обраної користувачем, що порушить цілісність додатка в цілому. Трохи пізніше ми побачимо, що ASP.NET пропонує рішення саме цієї проблеми спільного використання подання. Це може стати предметом нового серверного компонента, який ми створюємо самостійно. Достатньо використовувати цей компонент у різних додатках, щоб забезпечити цілісність загального додатка. Більш складною у керуванні є проблема порядку дій. Коли всі запити проходять через єдиний контролер, він може перевірити, чи є запитувана дія сумісною з попередньою. Цей код перевірки знаходиться в одному місці. Тут його доведеться розподілити між різними контролерами, що ускладнює обслуговування загального додатка.
Повернімося до нашого додатка, описаного вище. Його початкова сторінка — це класична сторінка HTML:
<html>
<head>
<TITLE>Composants ASP Serveur</TITLE>
<meta name="pragma" content="no-cache">
</head>
<frameset rows="130,*" frameborder="0">
<frame name="banner" src="bandeau.htm" scrolling="no">
<frameset cols="200,*">
<frame name="contents" src="options.htm">
<frame name="main" src="main.htm">
</frameset>
<noframes>
<p id="p1">
Ce jeu de frames HTML affiche plusieurs pages Web. Pour afficher ce jeu de
frames, utilisez un navigateur Web qui prend en charge HTML 4.0 et version
ultérieure.
</p>
</noframes>
</frameset>
</html>
Ця головна сторінка складається з трьох фреймів, які називаються banner, contents та main:
![]() |
Сторінка [bandeau.htm], розміщена у фреймі [banner], виглядає так:

Її код HTML виглядає так:
<html>
<head>
<META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE" />
<title>bandeau</title>
</head>
<body>
<P>
<TABLE>
<TR>
<TD><IMG alt="logo université d'angers" src="univ01.gif"></TD>
<TD>Composants serveurs ASP</TD>
</TR>
</TABLE>
</P>
<HR>
</body>
</html>
Сторінка [options.htm] розміщена в банері [contents]. Це набір посилань:
![]() | |
Усі посилання ведуть до головного контролера [main.aspx] із параметром [action], що вказує на дію, яку потрібно виконати. Вимагається, щоб ціль посилань відображалася у фреймі [main] (target="main").
Першою сторінкою, що відображається у фреймі [main], є [main.htm]:
|
Головний контролер [main.aspx, main.aspx.vb] має такі характеристики:
[main.aspx]
[main.aspx.vb]
Public Class main
Успадковує System.Web.UI.Page
Приватна процедура Page_Load(ByVal відправник як System.Object, ByVal e як System.EventArgs) За допомогою дескрипторів MyBase.Load
' отримуємо дію, яку потрібно виконати
Dim action As String
If Request.QueryString("action") Is Nothing Then
action = "label"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' виконується дія
Вибір за значенням action
Case "label"
Server.Transfer("form2.aspx")
Case "button"
Server.Transfer("form3.aspx")
Case "textbox1"
Server.Transfer("form4.aspx")
Випадок «textbox2»
Server.Transfer("form5.aspx")
Випадок «dropdownlist»
Server.Transfer("form6.aspx")
Поле «listbox»
Server.Transfer("form7.aspx")
Поле «casesacocher»
Server.Transfer("form8.aspx")
Випадок «listecasesacocher»
Server.Transfer("form8b.aspx")
Поле «panel»
Server.Transfer("form9.aspx")
Випадок Else
Server.Transfer("form2.aspx")
Кінець вибору
Кінець процедури
End Class
Наш контролер простий. Залежно від значення параметра [action] він перенаправляє обробку запиту на відповідну сторінку. Він не надає жодної додаткової цінності порівняно зі сторінкою HTML з посиланнями. Проте, щоб оцінити його користь, достатньо додати сторінку автентифікації. Якщо користувачеві потрібно пройти автентифікацію (логін, пароль) для доступу до додатків, контролер [main.aspx] став би ідеальним місцем для перевірки того, чи відбулася ця автентифікація.




