Skip to content

7. Серверні компоненти ASP — 1

7.1. Introduction

У цьому розділі ми описуємо технологію, рекомендовану в ASP.NET для побудови користувацького інтерфейсу. Ми знаємо, що обробка сторінки .aspx веб-сервером складається з двох чітко розмежованих етапів:

  1. спочатку виконується контролер сторінки. Він складається з коду, розташованого або безпосередньо на самій сторінці .aspx (рішення WebMatrix), або в окремому файлі (рішення Visual Studio.NET).
  2. Потім виконується код візуалізації сторінки .aspx, який перетворюється на код HTML, що надсилається клієнту.

Image

ASP.NET пропонує три бібліотеки тегів для написання коду оформлення сторінки:

  1. класичні теги HTML. Саме їх ми використовували дотепер.
  2. серверні теги HTML
  3. теги 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>

Вміст списку та опція, яку потрібно вибрати, є динамічними елементами, тому їх має генерувати контролер сторінки. Ми вже стикалися з цією проблемою і вирішили її, додавши до коду шаблону тег

<%=uneListeHTML%>

Цей тег буде замінено значенням [String] змінної [uneListeHTML]. Це значення, обчислене контролером, має відповідати коду HTML зі списку c.a.d. «<select name=..>...</select>». Це не надто складно, і це виглядає як елегантне рішення, яке дозволяє уникнути розміщення коду генерації безпосередньо в презентаційній частині сторінки. У цьому випадку туди довелося б вставляти цикл із перевірками, що значно «забруднило» б її. Проте цей метод має один недолік. Розділення сторінки на контролер і презентацію також слугує для розмежування двох сфер компетенції:

  • сферу компетенції розробника .NET, який відповідає за контролер сторінки,
  • сферу компетенції графічного дизайнера, який відповідає за презентаційну частину сторінки

Тут видно, що генерацію коду HTML списку перенесено до контролера. Графічний дизайнер може захотіти вплинути на цей код HTML, щоб змінити «візуальний» вигляд списку. Він буде змушений працювати в частині [contrôleur] і, отже, вийти за межі своєї сфери компетенції, що несе ризик випадкового внесення помилок у код.

Бібліотеки серверних тегів вирішують цю проблему. Вони пропонують об’єкт, що представляє список HTML. Так, бібліотека [WebForms] пропонує такий тег:

<asp:ListBox id="uneListe" runat="server"></asp:ListBox>

Цей тег представляє об’єкт типу [ListBox], яким може керувати контролер сторінки. Цей об’єкт має властивості для представлення різних опцій зі списку HTML та позначення обраної опції. Отже, контролер сторінки присвоїть цим властивостям відповідні значення. Під час виконання частини, що відповідає за відображення, тег

<asp:ListBox id="uneListe" runat="server"></asp:ListBox>

буде замінена кодом 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] ідентифікує клієнта. Ось приклад:

User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

Завдяки цій інформації веб-сервер може визначити можливості клієнта, зокрема тип коду HTML, з яким він може працювати. Адже з часом з’явилося кілька версій мови HTML. Сучасні браузери підтримують найновіші версії цієї мови, чого не можуть зробити старіші браузери. Залежно від заголовка HTTP [User-Agent:], який клієнт надіслав серверу, сервер надішле йому версію HTML, яку той зможе зрозуміти. Це цікава та корисна ідея, оскільки розробник не мусить турбуватися про тип браузера клієнта, який використовує його додаток.

Нарешті, вдосконалені версії IDE, такі як Visual Studio.NET, WebMatrix тощо, дають змогу створювати веб-інтерфейс у стилі Windows. Ці інструменти, хоча й не є обов’язковими, проте надають розробнику вирішальну допомогу. Розробник створює веб-інтерфейс за допомогою графічних компонентів, які розміщує на цьому інтерфейсі. Він має прямий доступ до властивостей кожного з компонентів інтерфейсу, які може налаштовувати на свій розсуд. Ці властивості будуть перетворені в коді HTML, що визначає вигляд інтерфейсу, на атрибути тегу <asp:> відповідного компонента. Перевага для розробника полягає в тому, що йому не потрібно запам’ятовувати ані список, ані синтаксис атрибутів кожного тегу. Це значна перевага, коли ви не досконало знаєте бібліотеки серверних тегів, що пропонуються ASP.NET. Коли цей синтаксис освоєно, деякі розробники можуть віддати перевагу безпосередньому кодуванню тегів у коді візуалізації сторінки, оминаючи етап графічного проектування. У такому разі IDE більше не потрібний. Достатньо простого текстового редактора. Залежно від способу роботи акцент робиться або на компонентах (використання IDE), або на тегах (використання текстового редактора). Ці два терміни є еквівалентними. Компонент — це об’єкт, яким керуватиме код управління сторінкою. IDE надає нам доступ до його властивостей на етапі проектування. Значення, задані для цих властивостей, негайно перетворюються на атрибути тегу компонента в коді візуалізації. На етапі виконання код управління сторінки буде опрацьовувати компонент і присвоювати значення деяким його властивостям. Код представлення, у свою чергу, згенерує код компонента HTML, використовуючи, з одного боку, атрибути, задані на етапі проектування для відповідного серверного тегу, а з іншого — значення властивостей компонента, обчислені кодом управління.

7.2. Контекст виконання прикладів

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

  1. веб-додаток складатиметься з єдиної сторінки P, що містить форму F,
  2. клієнт надсилатиме свій перший запит безпосередньо на цю сторінку P. Це полягатиме у запиті URL-адреси сторінки P за допомогою браузера. Отже, на цю URL-адресу P буде надіслано запит GET. Сервер надасть сторінку P і, відповідно, форму F, що міститься на ній,
  3. користувач заповнить її та надішле, c.a.d. тобто виконає дію, яка змусить браузер надіслати форму F на сервер. Операція POST браузера завжди буде спрямована на сторінку P. Сервер знову надасть сторінку P із формою F, зміст якої міг бути змінений внаслідок дій користувача.
  4. Потім повторяться кроки 2 і 3.

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

7.3. Компонент Label

7.3.1. Використання

Тег <asp:label> дозволяє вставляти динамічний текст у код представлення сторінки. Отже, він не виконує нічого більше, ніж тег <%=variable%>, який ми використовували досі. Вивчення цього першого тегу дозволить нам розібратися в механізмі роботи серверних тегів. Створимо сторінку з контрольною частиною [form1.aspx.vb] та частиною відображення [form1.aspx]. Мета — відобразити час:

Image

Ця проблема вже розглядалася в розділі 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]. Отримуємо такий результат:

Image

Код 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:

        <span id="lblHeure">Il est 19:39:37</span>

Саме властивість [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]:

Image

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

Image

Image

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

Image

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

Image

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

Image

Вікно інструментів надає доступ до двох типів компонентів:

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

Двічі клацнімо на компоненті [Label] у списку елементів управління [WebControls]. На вкладці [Design] ми отримаємо такий результат:

Image

На вкладці [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], щоб у правому нижньому куті з’явилося вікно властивостей цього компонента:

Image

Читачеві пропонується переглянути властивості об’єкта [Label]. Нас тут цікавлять дві:

  • Text: це текст, який має відображати мітка — вводимо порожній рядок (c.a.d. нічого)
  • ID: це його ідентифікатор — ми вказуємо lblHeure

Вкладка [Design] виглядає так:

Image

а код [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 автоматично запускається з такими параметрами:

Image

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

Image

Надалі ми будемо переважно використовувати інструмент 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]:

Image

Ця сторінка, створена за допомогою WebMatrix, має три компоненти:

назва
тип
властивості
роль
1
Button1
Button
text=Кнопка1
кнопка «Відправити»
2
Button2
Button
text=Кнопка2
кнопка «Відправити»
3
lblInfo
Мітка
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]. Ми отримуємо таку сторінку:

Image

Код 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
    <form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">

Було виправлено два атрибути: [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] із дивним значенням:
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />

Це поле у закодованій формі відображає стан форми, надісланої клієнту. Цей стан відображає значення всіх її компонентів. Оскільки [__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], значення, надіслані браузером, будуть такими:

__VIEWSTATE=dDwxNTY0NjIwMjUwOzs%2B2mcnJczeuvF2PEfvmtv7uiUhWUw%3D&Button2=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], щоб отримати таку сторінку:

Image

Якщо ми подивимося на отриманий код 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], отримаємо таку відповідь:

Image

Код 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], щоб отримати таке відображення:

Image

4321Ця сторінка, створена за допомогою WebMatrix, містить такі компоненти:

назва
тип
властивості
роль
1
TextBox1
TextBox
AutoPostback=true
Text=
поле введення
2
TextBox2
TextBox
AutoPostback=false
Text=
поле введення
3
lblInfo1
Label
text=
інформаційне повідомлення про вміст [TextBox1]
3
lblInfo2
Мітка
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]. Ми отримуємо таку сторінку:

Image

Код 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]:
    <form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">

Давайте проведемо наш перший тест. Введемо текст у перше поле введення:

Image

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

Image

Що сталося? Коли курсор покинув перше поле введення, браузер перевірив, чи змінився його вміст. Так і сталося. Тому браузер згенерував подію [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, а також надіслані параметри. Повернімося до нашого браузера та введімо текст у друге поле введення:

Image

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

            <input name="TextBox2" type="text" id="TextBox2" />

Отже, при виході з поля введення № 2 жодна подія не відбувається. Тепер введемо новий текст у поле № 1:

Image

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

Image

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

Image

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

Image

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

Image

Потім наведемо курсор на поле № 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, і ми отримуємо таку відповідь:

Image

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

Image

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

Image

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

Image

Саме так і сталося. Час та вміст [lblInfo2] свідчать про те, що процедура [TextBox2_TextChanged] не була виконана. З огляду на це, розглянемо властивість [EnableViewState] для чотирьох компонентів форми:

TextBox1
ми хочемо зберегти стан цього компонента, щоб сервер знав, чи змінився він, чи ні
TextBox2
те саме
lblInfo1
ми не хочемо зберігати стан цього компонента. Ми хочемо, щоб текст перераховувався при кожному новому POST. Якщо він не перераховується, він повинен бути порожнім. Все це добре досягається за допомогою [EnableViewState=false]
lblInfo2 
те саме

Наша презентаційна сторінка виглядає наступним чином:

...
            <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

Image

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

version 2

Image

На цьому етапі ми змінювали вміст поля 1, не змінюючи вміст поля 2, завдяки чому процедура [TextBox2_TextChanged] не виконувалася на стороні сервера, що означало, що поле [lblInfo2] не отримувало нового значення. Тому воно відображається з попереднім значенням. У версії 1 [EnableViewState=true] цим попереднім значенням було введене значення. У версії 2 [EnableViewState=false] цим попереднім значенням є порожній рядок.

Іноді немає сенсу зберігати попередній стан компонентів. Замість того, щоб вказувати [EnableViewState=false] для кожного з них, можна вказати, що сторінка не повинна зберігати свій стан. Це робиться за допомогою директиви [Page] у коді представлення:

<%@ Page Language="VB" EnableViewState="False" %>

У цьому випадку, незалежно від значення властивості [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
SingleLine
<input type="text" ...>
MultiLine
<textarea>...</textarea>
Password
<input type="password ...>

Розглянемо використання цих властивостей на прикладі [form5.aspx]:

Image

назва
тип
властивості
роль
1
btnAjouter
Кнопка
 
кнопка [submit] — служить для додавання вмісту [TextBox1] до вмісту [TextBox2]
2
TextBox1
TextBox
TextMode=Password
Text=
захищене поле введення
3
TextBox2
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], щоб отримати таке оформлення:

Image

назва
тип
властивості
роль
1
DropDownList1
DropDownList
AutoPostback=true
EnableViewState=true
випадаючий список
2
lblInfo
Мітка
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]:

Items
колекція типу [ListItemCollection] елементів випадаючого списку. Елементи цієї колекції мають тип [ListItem].
Items.Count
кількість елементів колекції [Items]
Items(i)
елемент № i списку — типу [ListItem]
Items.Add
щоб додати новий елемент [ListItem] до колекції [Items]
Items.Clear
щоб видалити всі елементи з колекції [Items]
Items.RemoveAt(i)
для видалення елемента № i з колекції [Items]
SelectedItem
1-й елемент [ListItem] колекції [Items], властивість якого [Selected] має значення «true»
SelectedIndex
номер елемента [SelectedItem] у колекції [Items]

Елементи колекції [Items] класу [DropDownList] мають тип [ListItem]. Кожен елемент [ListItem] породжує тег HTML <option>:

<option value="V" [selected="selected"]>T</option>

Опишемо деякі властивості та методи класу [ListItem]:

ListItem(String texte, String value)
конструктор — створює елемент [ListItem] із властивостями [texte] та [value]. Елемент ListItem(T,V) породжує тег HTML <option value="V">T</option>. Отже, клас [ListITem] дозволяє описувати елементи списку HTML
Selected
булевого типу. Якщо значення «true», відповідна опція списку HTML матиме атрибут [selected="selected"]. Цей атрибут вказує браузеру, що відповідний елемент повинен відображатися як вибраний у списку HTML
Text
текст T опції HTML <option value="V" [selected="selected"]>T</option>
Value
значення 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» — саме той, значення якого було відправлено браузером. Доступ до цього елемента можна отримати кількома способами:

DropDownList1.SelectedItem
— це перший елемент [ListItem] у списку, атрибут [Selected] якого має значення «true»
DropDownList1.SelectedItem.Text
відповідає частині [texte] тегу HTML елемента <option value="...">текст</option>, обраного користувачем
DropDownList1.SelectedItem.Value
відповідає частині [value] тегу HTML елемента <option value="...">текст</option>, обраного користувачем
DropdDownList1.SelectedIndex
номер у колекції [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], щоб отримати таке оформлення:

Image

назва
тип
властивості
роль
1
txtSaisie
TextBox
EnableViewState=false
поле введення
2
btnAjouter
Кнопка
 
кнопка [submit], яка переносить у Список 1 вміст поля txtSaisie, якщо воно не порожнє.
3
ListBox1
ListBox
EnableViewState=true
SelectionMode=Single
список значень з одинарним вибором
4
ListBox2
ListBox
EnableViewState=true
SelectionMode=Multiple
список значень для множинного вибору
5
btn1vers2
Кнопка
 
кнопка [submit], яка передає у [liste 2] вибраний елемент із [liste 1]
6
btn2vers1
Кнопка
 
кнопка [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]. З’являється нова властивість:

SelectionMode
визначає режим вибору зі списку 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], щоб отримати таке оформлення:

Image

назва
тип
властивості
роль
1
RadioButton1
RadioButton2
RadioButton3
RadioButton
RadioButton1.Checked=true
RadioButton1.Text=1
RadioButton2.Checked=false
RadioButton2.Text=2
RadioButton3.Checked=false
RadioButton3.Text=3
для 3 кнопок: GroupName=radio
перемикачі
2
CheckBoxA
CheckBoxB
CheckBoxC
CheckBox
Checked=false для всіх
CheckBoxA.Text=A
CheckBoxB.Text=B CheckBoxC.Text=C
поля для позначення
3
btnEnvoyer
Кнопка
 
кнопка [submit]
4
lstInfos
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>
            &nbsp;<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 з’являється у тегах:

            <asp:CheckBox id="CheckBoxA" runat="server" ></asp:CheckBox>

Якщо не брати до уваги елементи управління [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].Controls
повертає набір дочірніх елементів управління з [Control], якщо такі є
[Control].FindControl(ID)
повертає елемент управління, ідентифікований за ID, що знаходиться в корені дерева дочірніх елементів управління [Control]. У наведеному вище прикладі: Page.FindControl("frmControls") позначає контейнер [HtmlForm]. Щоб отримати доступ до перемикача [RadioButton1], потрібно написати
Page.FindControl("frmControls").FindControl("RadioButton1")
[Control].ID
ідентифікатор [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].

Items
колекція типу [ListItemCollection], що містить елементи випадаючого списку. Елементи цієї колекції мають тип [ListItem].
Items.Count
кількість елементів колекції [Items]
Items(i)
елемент № i зі списку — типу [ListItem]
Items.Add
щоб додати новий елемент [ListItem] до колекції [Items]
Items.Clear
щоб видалити всі елементи з колекції [Items]
Items.RemoveAt(i)
для видалення елемента № i з колекції [Items]
SelectedItem
1-й елемент [ListItem] колекції [Items], властивість якого [Selected] має значення «true»
SelectedIndex
номер попереднього елемента в колекції [Items]

Деякі властивості є специфічними для класів [CheckBoxList] та [RadioButtonList]:

RepeatDirection
[horizontal] або [vertical] для горизонтальних або вертикальних списків.

Елементи колекції [Items] мають тип [ListItem]. Кожен елемент [ListItem] генеруватиме різний тег залежно від того, чи йдеться про об’єкт [CheckBoxList] чи [RadioButtonList]:

<input type="checkbox" [selected="selected"] value="V">Texte

Або

<input type="radio" [selected="selected"] value="V">Texte

Ми опишемо деякі властивості та методи класу [ListItem]:

ListItem(String texte, String value)
конструктор — створює елемент [ListItem] із властивостями text та value. Елемент ListItem(T,V) генерує тег HTML <input type="checkbox" value="V">T або <input type="radio" value="V">T залежно від випадку.
Selected
булеве значення. Якщо значення «true», відповідна опція зі списку HTML матиме атрибут [selected="selected"]. Цей атрибут вказує браузеру, що відповідний елемент має відображатися як вибраний у списку HTML
Text
текст T опції HTML <input type=".." value="V" [selected="selected"]>T
Value
значення атрибута Value опції HTML <input type=".." value="V" [selected="selected"]>T

Ми пропонуємо створити таку сторінку [form8b.aspx]:

Image

назва
тип
властивості
роль
1
RadioButtonList1
RadioButtonList
EnableViewState=true
RepeatDirection=horizontal
список перемикачів
2
CheckBoxList1
CheckBoxList
EnableViewState=true
RepeatDirection=horizontal
список прапорців
3
btnEnvoyer
Кнопка
 
кнопка [submit], яка відображає в [4] список елементів, вибраних у обох списках
4
lstInfos
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], щоб отримати таке оформлення:

Image

назва
тип
властивості
роль
1
Panel1
Панель
EnableViewState=true
контейнер елементів управління
2
ListBox1
ListBox
EnableViewState=true
список із трьох значень
3
lnkCacher
LinkButton
EnableViewState=false
посилання для приховування контейнера

Коли контейнер приховано, з’являється нове посилання:

Image

ім'я
тип
властивості
роль
4
lnkVoir
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 була такою:

Image

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

Image

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

Image

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

Image

У додатку 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..>. Запустимо цю програму. На сторінці відображається лише одна кнопка:

Image

Розглянемо код 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 сервера:

    <form action="main.aspx" runat="server">

Атрибут [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] такий:

назва
тип
властивості
роль
panelform
Панель
EnableViewState=true
вигляд форми
rdOui
rdNon
RadioButton
EnableViewState=true
GroupName=rdmarie
перемикачі
chk1
chk2
chk3
CheckBox
EnableViewState=true
прапорці
txtSaisie
TextBox
EnableViewState=true
поле введення
txtMdp
TextBox
EnableViewState=true
захищене поле введення
txtArea
TextBox
EnableViewState=true
багаторядкове поле введення
cmbValeurs
DropDownList
EnableViewState=true
випадаючий список
lstSimple
ListBox
EnableViewState=true
SelectionMode=Single
список з одинарним вибором
btnRazSimple
Кнопка
EnableViewState=false
скасовує вибір усіх елементів lstSimple
lstMultiple
ListBox
EnableViewState=true
SelectionMode=Multiple
список із можливістю множинного вибору
btnRazMultiple
Кнопка
EnableViewState=false
скасувати вибір усіх елементів lstMultiple
lblSecret
Мітка
EnableViewState=true
Visible=false
приховане поле
btnEffacer
HTML стандарт
 
відображає попередження
btnEnvoyer
Кнопка
EnableViewState=false
кнопка [submit] у формі
btnReset
HTML стандарт
 
кнопка [reset] у формі

Тут важливу роль відіграє [VIEWSTATE] для компонентів. Усі компоненти, крім кнопок, повинні мати властивість [EnableViewState=true]. Щоб зрозуміти причину цього, слід згадати, як працює додаток. Припустимо, що поле [txtSaisie] має властивість [EnableViewState=false]:

  1. клієнт вперше запитує сторінку [form10.aspx]. Він отримує форму
  2. заповнює його та відправляє, натиснувши кнопку [Envoyer]. Поля введення даних надсилаються, і сервер присвоює серверним компонентам надіслане значення або їхній стан [VIEWSTATE], якщо такий стан був. Таким чином, полю [txtSaisie] присвоюється значення, введене користувачем. Також на цьому етапі його стан [VIEWSTATE] не має значення. У результаті операції надсилається представлення [informations] — фактично це все та ж сторінка [form10.aspx], але з іншим відображеним контейнером.
  3. Користувач переглядає цей новий вигляд і використовує посилання [Retour au formulaire], щоб повернутися до нього. Тоді виконується перехід POST до [form10.aspx]. У цьому випадку надсилається щонайбільше одне значення: значення, вибране користувачем у списку інформації, яке надалі не використовується. У будь-якому разі поле [txtSaisie] не надсилається.
  4. Сервер отримує POST і присвоює серверним компонентам відправлене значення або їхній стан [VIEWSTATE], якщо такий стан був. У даному випадку [txtNom] не має відправленого значення. Якщо його атрибут [EnableViewState] дорівнює [false], йому буде присвоєно порожній рядок. Оскільки потрібно, щоб він мав значення, введене користувачем, він повинен мати властивість [EnableViewState=true].

Контейнер [panelinfo] має такі елементи управління:

ім’я
тип
властивості
роль
panelinfo
Панель
EnableViewState=false
перегляд інформації
lstInfos
ListBox
EnableViewState=false
перелік інформації, що підсумовує значення, введені користувачем
LinkButton1
LinkButton
EnableViewState=false
посилання для повернення до форми

Під час тестування, якщо подивитися на код HTML, згенерований вищенаведеним кодом представлення, можна здивуватися коду, згенерованому для прихованого поля [lblSecret]:

                    <tr>
                        <td>
                            Champ caché</td>
                        <td></td>
                    </tr>

Компонент [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 додатка має такий вигляд:

Image

Ці три подання будуть вбудовані в код відображення контролера [main.aspx] у вигляді контейнерів. Отже, цей додаток має єдину сторінку [main.aspx].

7.14.2.2. Види веб-додатку

Вигляд [formulaire] — це форма введення даних, що дозволяє розрахувати податок для користувача:

Image

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

Image

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

Image

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

Image

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

Image

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

Image

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

Image

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

Image

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]:

ім’я
тип
властивості
роль
panelform
Панель
EnableViewState=true
погляд на форму
rdOui
rdNon
RadioButton
EnableViewState=true
GroupName=rdmarie
перемикачі
txtEnfants
TextBox
EnableViewState=true
кількість дітей
txtSalaire
TextBox
EnableViewState=true
річний оклад
btnCalculer
Кнопка
 
кнопка [submit] у формі — запускає розрахунок податку
btnEffacer
Button
 
Кнопка [submit] у формі — очищає форму

Контейнер [panelerreurs]:

назва
тип
властивості
роль
panelerreurs
Панель
EnableViewState=true
перегляд помилок
lnkForm1
LinkButton
EnableViewState=true
посилання на форму
erreursHTML
Літерал
 
код HTML зі списку помилок

Контейнер [panelsimulations]:

назва
тип
властивості
роль
panelsimulations
Панель
EnableViewState=true
перегляд симуляцій
lnkForm2
LinkButton
EnableViewState=true
посилання на форму
simulationsHTML
Літерал
 
код HTML зі списку моделювань у таблиці HTML

7.14.2.4. Код управління додатком

Код контролю додатка розміщений у файлах [global.asax.vb] та [main.aspx.vb]. Файл [global.asax] визначено наступним чином:

<%@ Application src="Global.asax.vb" Inherits="Global" %>

Файл [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]:

Image

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

Image

7.14.3. Приклад 3

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

Image

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

Image

Контролер [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], виглядає так:

Image

Її код 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]. Це набір посилань:


<html>
    <head>
        <meta http-equiv="pragma" content="no-cache" />
        <title>options</title>
    </head>
    <body bgcolor="Gold">
        <ul>
            <li>
                <a href="main.aspx?action=label" 
                 target="main">Label</a>
            <li>
                <a href="main.aspx?action=button" 
                 target="main">Button</a>
            <li>
                <a href="main.aspx?action=textbox1" 
                 target="main">TextBox-1</a></li>
            <li>
                <a href="main.aspx?action=textbox2" 
                 target="main">TextBox-2</a></li>
            <li>
                <a href="main.aspx?action=dropdownlist" 
                 target="main">DropDownList</a></li>
            <li>
                <a href="main.aspx?action=listbox" 
                 target="main">ListBox</a></li>
            <li>
                <a href="main.aspx?action=casesacocher" 
                  target="main">CheckBox</a> et<br>
                RadioButton</li>
            <li>
                <a href="main.aspx?action=listecasesacocher" 
                  target="main">CheckBoxList</a> et<br>
                RadioButtonList</a></li>
            <li>
                <a href="main.aspx?action=panel" 
                  target="main">Panel</a></li>
        </ul>
    </body>
</html>

Усі посилання ведуть до головного контролера [main.aspx] із параметром [action], що вказує на дію, яку потрібно виконати. Вимагається, щоб ціль посилань відображалася у фреймі [main] (target="main").

Першою сторінкою, що відображається у фреймі [main], є [main.htm]:


<html>
    <head>
        <title>main</title>
    </head>
    <body>
        <P>Choisissez une option pour tester découvrir un type de composant serveur...</P>
    </body>
</html>

Головний контролер [main.aspx, main.aspx.vb] має такі характеристики:

[main.aspx]

<%@ page src="main.aspx.vb" inherits="main" autoeventwireup="false" %>

[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] став би ідеальним місцем для перевірки того, чи відбулася ця автентифікація.