Skip to content

2. Короткий вступ до ASP.NET

Тут ми пропонуємо на прикладах ознайомити вас з поняттями ASP.NET, які стануть у нагоді в подальшому тексті документа. Цей вступ не дозволяє зрозуміти всі тонкощі взаємодії клієнт/сервер у веб-додатку. Для цього можна ознайомитися з:

Цей вступ призначений для тих, хто хоче швидко освоїти матеріал, погодившись на першому етапі залишити поза увагою деякі моменти, які можуть виявитися важливими. У подальшій частині документа ці моменти розглядаються більш детально. Ті, хто вже знайомий з ASP.NET, можуть перейти безпосередньо до параграфа 3.

2.1. Приклад проекту

2.1.1. Створення проєкту

  • у [1] створюємо новий проєкт за допомогою Visual Web Developer
  • у [2] вибираємо веб-проект на Visual C#
  • у [3] вказуємо, що хочемо створити веб-додаток ASP.NET
  • у [4] надаємо назву додатку. Для проекту буде створено папку з цією назвою.
  • у [5] вказуємо батьківську папку для папки [4] проекту
  • у [6] — створений проект
  • [Default.aspx] — це веб-сторінка, створена за замовчуванням. Вона містить теги HTML та теги ASP.NET
  • [Default.aspx.cs] містить код для обробки подій, ініційованих користувачем на сторінці [Defaul.aspx], що відображається в його браузері
  • [Default.aspx.designer.cs] містить список компонентів ASP.NET сторінки [Default.aspx]. Кожен компонент ASP.NET, розміщений на сторінці [Default.aspx], породжує оголошення цього компонента у файлі [Default.aspx.designer.cs].
  • [Web.config] — це файл конфігурації проєкту ASP.NET.
  • [References] — це список DLL, що використовуються веб-проектом. Ці DLL — це бібліотеки класів, які проект має використовувати. У [7] міститься список DLL, які за замовчуванням додані до посилань проекту. Більшість із них є непотрібними. Якщо проекту потрібно використовувати DLL, яка не вказана у [7], її можна додати за допомогою [8].

2.1.2. Сторінка [Default.aspx]

Якщо запустити проект за допомогою [Ctrl-F5], у браузері відобразиться сторінка [Default.aspx]:

  • у [1] — сторінка URL веб-проєкту. Visual Web Developer має вбудований веб-сервер, який запускається під час виконання проєкту. Він прослуховує випадковий порт, у даному випадку 1490. Зазвичай портом прослуховування є порт 80. У [1] жодна сторінка не запитується. У цьому випадку відображається сторінка [Default.aspx], звідки й походить її назва як сторінки за замовчуванням.
  • У [2] сторінка [Default.aspx] порожня.
  • у Visual Web Developer сторінку [Default.aspx] [3] можна створити візуально (вкладка [Design]) або за допомогою тегів (вкладка [Source])
  • у [4] сторінка [Defaul.aspx] у режимі [Design]. Її створюють, розміщуючи на ній компоненти, які містяться в наборі інструментів [5].

Режим [Source] [6] надає доступ до вихідного коду сторінки:


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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
  </div>
  </form>
</body>
</html>
  • рядок 1 — це директива ASP.NET, яка перелічує певні властивості сторінки
    • директива Page застосовується до веб-сторінки. Існують також інші директиви, такі як Application, WebService, ... які застосовуються до інших об’єктів ASP.NET
    • атрибут CodeBehind вказує на файл, який обробляє події сторінки
    • атрибут Language вказує на мову .NET, що використовується файлом CodeBehind
    • атрибут Inherits вказує на ім’я класу, визначеного всередині файлу CodeBehind
    • атрибут AutoEventWireUp="true" вказує, що зв’язок між подією в [Default.aspx] та її обробником у [Defaul.aspx.cs] здійснюється за назвою події. Таким чином,подія Load на сторінці [Default.aspx] оброблятиметься методом Page_Load класу Intro._Default, визначеним атрибутом Inherits.
  • рядки 4–14 описують сторінку [Defaul.aspx] за допомогою тегів:
    • класичних тегів HTML, таких як тег <body> або <div>
    • ASP.NET. Це теги, що мають атрибут runat="server". Теги ASP.NET обробляються веб-сервером перед відправкою сторінки клієнту. Вони перетворюються на теги HTML. Отже, браузер клієнта отримує стандартну сторінку HTML, у якій більше немає тегів ASP.NET.

Сторінку [Default.aspx] можна редагувати безпосередньо у вихідному коді. Іноді це простіше, ніж використовувати режим [Design]. Ми змінюємо вихідний код наступним чином:


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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Introduction ASP.NET</title>
</head>
<body>
  <h3>Introduction à ASP.NET</h3>
  <form id="form1" runat="server">
  <div>
  </div>
  </form>
</body>
</html>

У рядку 6 ми задаємо заголовок сторінки за допомогою тегу HTML <title>. У рядку 9 ми вставляємо текст у тіло (<body>) сторінки. Якщо запустити проект (Ctrl-F5), у браузері ми отримаємо такий результат:

 

2.1.3. Файли [Default.aspx.designer.cs] та [Default.aspx.cs]

Файл [Default.aspx.designer.cs] визначає компоненти сторінки [Defaul.aspx]:


//------------------------------------------------------------------------------
// <автоматично згенеровано>
//      Цей код було згенеровано інструментом.
//      Версія середовища виконання: 2.0.50727.3603
//
//      Зміни, внесені до цього файлу, можуть спричинити некоректну роботу і будуть втрачені, якщо
//      код буде згенеровано заново.
// </auto-generated>
//------------------------------------------------------------------------------

namespace Intro {
    
    
    public partial class _Default {
        
        /// <summary>
        /// Елемент управління form1.
        /// </summary>
        /// <remarks>
        /// Поле, згенероване автоматично.
        /// Щоб змінити, перемістіть оголошення поля з файлу дизайнера у файл коду.
        /// </remarks>
        protected global::System.Web.UI.HtmlControls.HtmlForm form1;
    }
}

У цьому файлі міститься список компонентів ASP.NET сторінки [Default.aspx], що мають ідентифікатор. Вони відповідають тегам [Default.aspx], що мають атрибут runat="server" та атрибут id. Таким чином, компонент у рядку 23 вище відповідає тегу


  <form id="form1" runat="server">

файлу [Default.aspx].

Розробник майже не взаємодіє з файлом [Default.aspx.designer.cs]. Проте цей файл є корисним для визначення класу конкретного компонента. Так, нижче показано, що компонент form1 належить до типу HtmlForm. Тоді розробник може дослідити цей клас, щоб дізнатися про його властивості та методи. Компоненти сторінки [Default.aspx] використовуються класом із файлу [Default.aspx.cs]:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {

    }
  }
}

Слід зауважити, що клас, визначений у файлах [Default.aspx.cs] та [Default.aspx.designer.cs], є однаковим (рядок 10): Intro._Default. Саме ключове слово partial дозволяє розширити оголошення класу на кілька файлів, у даному випадку на два.

У рядку 10, наведеному вище, видно, що клас [_Default] розширює клас [Page] і успадковує його події. Одним із них є подія Load, яка відбувається під час завантаження сторінки веб-сервером. У рядку 12 наведено метод Page_Load, який обробляє подію Load сторінки. Зазвичай саме тут відбувається ініціалізація сторінки перед її відображенням у браузері клієнта. У даному випадку метод Page_Load нічого не робить.

Клас, пов’язаний із веб-сторінкою, у даному випадку клас Intro._Default, створюється на початку запиту клієнта та знищується після надсилання відповіді клієнту. Отже, він не може використовуватися для збереження інформації між двома запитами. Для цього потрібно використовувати поняття сеансу користувача.

2.2. Події веб-сторінки ASP.NET

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


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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Introduction ASP.NET</title>
</head>
<body>
  <h3>Introduction à ASP.NET</h3>
  <form id="form1" runat="server">
  <div>
    <table>
      <tr>
        <td>
          Nom</td>
        <td>
          <asp:TextBox ID="TextBoxNom" runat="server"></asp:TextBox>
        </td>
        <td>
          &nbsp;</td>
      </tr>
      <tr>
        <td>
          Age</td>
        <td>
          <asp:TextBox ID="TextBoxAge" runat="server"></asp:TextBox>
        </td>
        <td>
          &nbsp;</td>
      </tr>
    </table>
  </div>
  <asp:Button ID="ButtonValider" runat="server" Text="Valider" />
  <hr />
  <p>
    Evénements traités par le serveur</p>
  <p>
    <asp:ListBox ID="ListBoxEvts" runat="server"></asp:ListBox>
  </p>
  </form>
</body>
</html>

Режим роботи сторінки [Design] такий:

 

Файл [Default.aspx.designer.cs] має такий вигляд:


namespace Intro {
    public partial class _Default {
        protected global::System.Web.UI.HtmlControls.HtmlForm form1;
        protected global::System.Web.UI.WebControls.TextBox TextBoxNom;
        protected global::System.Web.UI.WebControls.TextBox TextBoxAge;
        protected global::System.Web.UI.WebControls.Button ButtonValider;
        protected global::System.Web.UI.WebControls.ListBox ListBoxEvts;
    }
}

У ньому містяться всі компоненти ASP.NET сторінки [Default.aspx], що мають ідентифікатор.

Ми модифікуємо файл [Default.aspx.cs] наступним чином:


using System;

namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
    protected void Page_Init(object sender, EventArgs e)
    {
      // відзначається подія
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Init", DateTime.Now.ToString("hh:mm:ss")));
    }

    protected void Page_Load(object sender, EventArgs e)
    {
      // записується подія
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
    }

    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // зафіксовано подію
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
    }
  }
}

Клас [_Default] (рядок 5) обробляє три події:

  • подія Init (рядок 7), яка відбувається після ініціалізації сторінки
  • подія Load (рядок 13), яка відбувається, коли сторінка завантажується веб-сервером. Подія Init відбувається перед подією Load.
  • подія Click на кнопці ButtonValider (рядок 19), яка відбувається, коли користувач натискає на кнопку [Valider]

Обробка кожної з цих трьох подій полягає у додаванні повідомлення до компонента Listbox з назвою ListBoxEvts. Це повідомлення відображає час події та її назву. Кожне повідомлення розміщується на початку списку. Отже, повідомлення, розміщені у верхній частині списку, є найсвіжішими.

Під час виконання проєкту відображається така сторінка:

З [1] видно, що події Page_Init та Page_Load відбулися саме в такому порядку. Нагадаємо, що найновіша подія знаходиться у верхній частині списку. Коли браузер запитує сторінку [Default.aspx] безпосередньо за її URL-адресою [2], він робить це за допомогою команди HTTP (протокол передачі HyperText), що називається GET. Після завантаження сторінки в браузері користувач буде ініціювати події на цій сторінці. Наприклад, він натисне на кнопку [Valider] [3]. Події, ініційовані користувачем після завантаження сторінки в браузері, запускають запит до сторінки [Default.aspx], але цього разу з командою HTTP, яка називається POST. Підсумовуючи:

  • початкове завантаження сторінки P у браузері здійснюється за допомогою операції HTTP GET
  • події, що відбуваються на сторінці згодом, щоразу генерують новий запит до тієї самої сторінки P, але цього разу з командою HTTP POST. Сторінка P може визначити, чи її було запрошено за допомогою команди GET чи команди POST, що дозволяє їй поводитися по-різному, якщо це необхідно, що, як правило, і відбувається.

Початковий запит на сторінку ASPX: GET

  • у [1], браузер запитує сторінку ASPX за допомогою команди HTTP GET без параметрів.
  • на [2] веб-сервер надсилає йому у відповідь потік HTML — переклад запитуваної сторінки ASPX.

Обробка події, що сталася на сторінці, яка відображається браузером: POST

  • у [1], під час події на сторінці HTML, браузер запитує сторінку ASPX, яка вже була отримана за допомогою операції GET, цього разу за допомогою команди HTTP POST разом із параметрами. Ці параметри — це значення компонентів, що містяться всередині тегу <form> на сторінці HTML, яка відображається браузером. Ці значення називаються значеннями, надісланими клієнтом. Вони будуть використані сторінкою ASPX для обробки запиту клієнта.
  • на [2], веб-сервер надсилає у відповідь потік HTML — переклад сторінки ASPX, яка спочатку запитувалася на сторінці POST, або іншої сторінки, якщо відбувся перехід на іншу сторінку чи перенаправлення.

Повернемося до нашої прикладної сторінки:

  • у [2], ця сторінка була отримана за допомогою GET.
  • на [1] ми бачимо дві події, що відбулися під час цього GET

Якщо вище користувач натисне на кнопку [Valider] [3], сторінка [Default.aspx] буде запрошена за допомогою POST. Цей POST супроводжуватиметься параметрами, які будуть значеннями всіх компонентів, включених у тег <form> сторінки [Default.aspx]: дві TextBox, [TextBoxNom, TextBoxAge], кнопка [ButtonValider], список [ListBoxEvts]. Значення, що передаються для цих компонентів, такі:

  • TextBox: введене значення
  • Button: текст кнопки, у даному випадку текст «Підтвердити»
  • Listbox: текст повідомлення, вибраного в ListBox

У відповідь на POST отримуємо сторінку [4]. Це знову сторінка [Default.aspx]. Це нормальна поведінка, якщо тільки не відбувається перенаправлення або переадресація сторінки за допомогою обробників подій сторінки. Можна побачити, що відбулися дві нові події:

  • подія Page_Load, яка відбулася під час завантаження сторінки
  • подія ButtonValider_Click, що сталася внаслідок натискання кнопки [Valider]

Можна помітити, що:

  • подія Page_Init не відбулася під час операції HTTP POST, тоді яквоно відбулося під час подій HTTP та GET
  • подія Page_Load відбувається завжди, незалежно від того, чи це GET, чи POST. Саме в цьому методі зазвичай потрібно знати, чи маємо ми справу з GET чи з POST.
  • Після завершення обробки POST сторінка [Default.aspx] була відправлена клієнту з змінами, внесеними обробниками подій. Так відбувається завжди. Після обробки подій сторінки P ця сама сторінка P відправляється клієнту. Є два способи обійти це правило. Останній виконаний обробник подій може
    • перенаправити потік виконання на іншу сторінку P2.
    • перенаправити браузер клієнта на іншу сторінку P2.

В обох випадках до браузера повертається саме сторінка P2. Ці два методи мають відмінності, до яких ми ще повернемося.

  • Подія ButtonValider_Click відбулася після події Page_Load. Отже, саме цей обробник може прийняти рішення про перенесення або перенаправлення на сторінку P2.
  • Список подій [4] зберіг обидві події, що відображалися під час початкового завантаження GET сторінки [Default.aspx]. Це дивно, враховуючи, що сторінка [Default.aspx] була відтворена під час POST. Ми повинні були б побачити сторінку [Default.aspx] із її проектними значеннями, а отже, порожню сторінку ListBox. Виконання обробників Page_Load та ButtonValider_Click мало б потім додати туди два повідомлення. Однак їх там чотири. Це пояснюється механізмом роботи VIEWSTATE. Під час початкового виконання GET веб-сервер надсилає сторінку [Default.aspx] із тегом HTML <input type="hidden" ...>, який називається прихованим полем (рядок 10 нижче).
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>
        Introduction ASP.NET
</title></head>
<body>
  <h3>Introduction à ASP.NET</h3>
  <form name="form1" method="post" action="default.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTMzMTEyNDMxMg9kFgICAw9kFgICBw8QZBAVAhMwNjoxNjozNjogUGFnZV9Mb2FkEzA2OjE2OjM2OiBQYWdlX0luaXQVAhMwNjoxNjozNjogUGFnZV9Mb2FkEzA2OjE2OjM2OiBQYWdlX0luaXQUKwMCZ2dkZGRW1AnTL8f/q7h2MXBLxctKD1UKfg==" />
</div>
..............................

У полі id «__VIEWSTATE» веб-сервер кодує значення всіх компонентів сторінки. Він робить це як для початкового GET, так і для наступних POST. Коли на сторінці P зустрічається POST:

  • браузер запитує сторінку P, надсилаючи у своєму запиті значення всіх компонентів, що містяться всередині тегу <form>. Вище можна побачити, що компонент «__VIEWSTATE» знаходиться всередині тегу <form>. Отже, його значення надсилається на сервер під час POST.
  • Сторінка P інстанціюється та ініціалізується з її значеннями створення
  • компонент «__VIEWSTATE» використовується для того, щоб повернути компонентам ті значення, які вони мали під час попереднього відправлення сторінки P. Саме так, наприклад, список подій [4] відновлює два перших повідомлення, які він містив, коли був надісланий у відповідь на початковий запит GET від браузера.
  • Потім компоненти сторінки P приймають значення, надіслані браузером. У цей момент форма на сторінці P перебуває у тому стані, в якому її надіслав користувач.
  • Обробляється подія Page_Load. Тут вона додає повідомлення до списку подій [4].
  • Обробляється подія, що спричинила POST. Тут ButtonValider_Click додає повідомлення до списку подій [4].
  • Сторінка P повертається. Компоненти мають такі значення:
    • або значення, відправлене через форму, c.a.d; або значення, яке компонент мав у формі на момент її відправки на сервер
    • або значення, задане одним із обробників подій.

У нашому прикладі

  • обидва компоненти TextBox відновлять свої значення, відправлені при поданні, оскільки обробники подій не впливають на них
  • список подій [4] відновлює своє значення, відправлене на сервер, c.a.d. усі події, що вже внесені до списку, а також дві нові події, створені методами Page_Load та ButtonValider_Click.

Механізм VIEWSTATE можна ввімкнути або вимкнути на рівні кожного компонента. Вимкнемо його для компонента [ListBoxEvts]:

  • у [1] VIEWSTATE компонента [ListBoxEvts] вимкнено. Для TextBox та [2] він увімкнено за замовчуванням.
  • у [3] обидві події, що повертаються після початкового GET
  • у [4]: форма заповнена, і ми натискаємо кнопку [Valider]. Буде виконано перехід POST на сторінку [Default.aspx].
  • у [6] — результат, отриманий після натискання кнопки [Valider]
  • активований механізм VIEWSTATE пояснює, що TextBox та [7] зберегли свої значення, записані в [4]
  • відключений механізм VIEWSTATE пояснює, чому компоненти [ListBoxEvts] та [8] не зберегли свій вміст [5].

2.3. Управління відправленими значеннями

Тут ми розглянемо значення, відправлені двома TextBox, коли користувач натискає кнопку [Valider]. Сторінка [Default.aspx] у режимі [Design] змінюється наступним чином:

Вихідний код елемента, доданого в [1], є таким:


  <p>
    Eléments postés au serveur :
    <asp:Label ID="LabelPost" runat="server"></asp:Label>
</p>

Ми використаємо компонент [LabelPost] для відображення значень, введених у TextBox та [2]. Код обробника подій [Default.aspx.cs] змінюється наступним чином:


using System;

namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
    protected void Page_Init(object sender, EventArgs e)
    {
      // записуємо подію
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Init", DateTime.Now.ToString("hh:mm:ss")));
    }

    protected void Page_Load(object sender, EventArgs e)
    {
      // зафіксовано подію
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
    }

    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // занотуємо подію
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
      // відображається ім'я та вік
      LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
    }
  }
}

У рядку 24 оновлюється компонент LabelPost:

  • LabelPost належить до типу [System.Web.UI.WebControls.Label] (див. Default.aspx.designer.cs). Його властивість Text представляє текст, що відображається компонентом.
  • TextBoxNom та TextBoxAge належать до типу [System.Web.UI.WebControls.TextBox]. Властивість Text компонента TextBox — це текст, що відображається в полі введення.
  • Метод Trim() видаляє пробіли, які можуть стояти перед або після рядка символів

Як було пояснено раніше, під час виконання методу ButtonValider_Click компоненти сторінки мають ті значення, які вони мали на момент відправлення сторінки користувачем. Отже, властивості Text обох TextBox мають значення, що відповідають текстам, введеним користувачем у браузері.

Ось приклад:

  • у [1] — це значення, відправлені
  • у [2] — відповідь сервера.
  • у [3], TextBox відновили свої значення, відправлені за допомогою механізму VIEWSTATE, що був активований
  • у [4] повідомлення компонента ListBoxEvts походять від методів Page_Init, Page_Load, ButtonValider_Click та призупиненого VIEWSTATE
  • У [5] значення компонента LabelPost було отримано за допомогою методу ButtonValider_Click. Ми успішно отримали обидва значення, введені користувачем у TextBox та [1].

Вище видно, що значення, надіслане для поля «вік», — це рядок «yy», що є недопустимим значенням. Ми додамо на сторінку компоненти, які називаються валідаторами. Вони слугують для перевірки правильності надісланих даних. Цю правильність можна перевірити у двох місцях:

  • на стороні клієнта. Параметр конфігурації валідатора дозволяє вибрати, чи слід виконувати перевірки у браузері. У цьому випадку перевірки виконуються за допомогою коду, вбудованого у сторінку. Коли користувач відправляє POST введені у форму значення, вони спочатку перевіряються кодом JavaScript. Якщо хоча б одна перевірка не проходить успішно, POST не виконується. Таким чином уникнути обміну даними з сервером, що робить сторінку більш відгуковою.
  • на сервері. Якщо перевірки на стороні клієнта можуть бути необов’язковими, то на стороні сервера вони є обов’язковими незалежно від того, чи відбувалася перевірка на стороні клієнта. Адже коли сторінка отримує надіслані значення, вона не має можливості дізнатися, чи були вони перевірені клієнтом перед відправленням. Тому на стороні сервера розробник завжди повинен перевіряти правильність надісланих даних.

Сторінка [Default.aspx] змінюється наступним чином:


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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Introduction ASP.NET</title>
</head>
<body>
  <h3>Introduction à ASP.NET</h3>
  <form id="form1" runat="server">
  <div>
    <table>
      <tr>
        <td>
          Nom</td>
        <td>
          <asp:TextBox ID="TextBoxNom" runat="server"></asp:TextBox>
        </td>
        <td>
          <asp:RequiredFieldValidator ID="RequiredFieldValidatorNom" runat="server" 
            ControlToValidate="TextBoxNom" Display="Dynamic" 
            ErrorMessage="Donnée obligatoire !"></asp:RequiredFieldValidator>
        </td>
      </tr>
      <tr>
        <td>
          Age</td>
        <td>
          <asp:TextBox ID="TextBoxAge" runat="server"></asp:TextBox>
        </td>
        <td>
          <asp:RequiredFieldValidator ID="RequiredFieldValidatorAge" runat="server" 
            ControlToValidate="TextBoxAge" Display="Dynamic" 
            ErrorMessage="Donnée obligatoire !"></asp:RequiredFieldValidator>
          <asp:RangeValidator ID="RangeValidatorAge" runat="server" 
            ControlToValidate="TextBoxAge" Display="Dynamic" 
            ErrorMessage="Tapez un nombre entre 1 et 150 !" MaximumValue="150" 
            MinimumValue="1" Type="Integer"></asp:RangeValidator>
        </td>
      </tr>
    </table>
  </div>
  <asp:Button ID="ButtonValider" runat="server" onclick="ButtonValider_Click" 
    Text="Valider" CausesValidation="False"/>
  <hr />
  <p>
    Evénements traités par le serveur</p>
  <p>
    <asp:ListBox ID="ListBoxEvts" runat="server" EnableViewState="False">
    </asp:ListBox>
  </p>
  <p>
    Eléments postés au serveur :
    <asp:Label ID="LabelPost" runat="server"></asp:Label>
  </p>
  <p>
    Eléments validés par le serveur :
    <asp:Label ID="LabelValidation" runat="server"></asp:Label>
  </p>
  <asp:Label ID="LabelErreursSaisie" runat="server" ForeColor="Red"></asp:Label>
  </form>
</body>
</html>

У рядках 20, 32 та 35 додано валідатори. У рядку 58 використовується компонент Label для відображення дійсних значень, надісланих методом POST. У рядку 60 компонент Label використовується для відображення повідомлення про помилку, якщо є помилки введення.

Сторінка [Default.aspx] у режимі [Design] виглядає наступним чином:

  • компоненти [1] та [2] належать до типу RequiredFieldValidator. Цей валідатор перевіряє, чи поле введення не є порожнім.
  • Компонент [3] належить до типу RangeValidator. Цей валідатор перевіряє, чи поле введення містить значення в межах двох меж.
  • У [4] — властивості валідатора [1].

Ми розглянемо обидва типи валідаторів на прикладі їхніх тегів у коді сторінки [Default.aspx]:


          <asp:RequiredFieldValidator ID="RequiredFieldValidatorNom" runat="server" 
            ControlToValidate="TextBoxNom" Display="Dynamic" 
ErrorMessage="Donnée obligatoire !"></asp:RequiredFieldValidator>
  • ID: ідентифікатор компонента
  • ControlToValidate: назва компонента, значення якого перевіряється. У цьому випадку потрібно, щоб компонент TextBoxNom не мав порожнього значення (порожній рядок або послідовність пробілів)
  • ErrorMessage: повідомлення про помилку, яке має відображатися у валідаторі у разі недійсних даних.
  • EnableClientScript: логічне значення, що вказує, чи повинен валідатор виконуватися також на стороні клієнта. Цей атрибут за замовчуванням має значення True, якщо його не вказано явно, як показано вище.
  • Display: режим відображення валідатора. Існує два режими:
    • static (за замовчуванням): валідатор займає місце на сторінці, навіть якщо не відображає повідомлення про помилку
    • dynamic: валідатор не займає місця на сторінці, якщо не відображає повідомлення про помилку.

          <asp:RangeValidator ID="RangeValidatorAge" runat="server" 
            ControlToValidate="TextBoxAge" Display="Dynamic" 
            ErrorMessage="Tapez un nombre entre 1 et 150 !" MaximumValue="150" 
            MinimumValue="1" Type="Integer"></asp:RangeValidator>
  • Тип: тип перевірених даних. У цьому випадку вік є цілим числом.
  • MinimumValue, MaximumValue: межі, в яких має знаходитися перевірене значення

Конфігурація компонента, що викликає POST, впливає на режим перевірки. У цьому випадку таким компонентом є кнопка [Valider]:


  <asp:Button ID="ButtonValider" runat="server" onclick="ButtonValider_Click"  Text="Valider" CausesValidation="True" />
  • CausesValidation: визначає автоматичний режим або імена серверних перевірок. Цей атрибут має значення за замовчуванням «True», якщо його явно не вказано. У цьому випадку
    • на стороні клієнта виконуються валідатори з атрибутами від EnableClientScript до True. POST виконується лише в тому випадку, якщо всі валідатори на стороні клієнта пройшли успішно.
    • На стороні сервера всі валідатори, присутні на сторінці, автоматично виконуються перед обробкою події, що викликала POST. У цьому випадку вони будуть виконані перед виконанням методу ButtonValider_Click. У цьому методі можна дізнатися, чи всі перевірки пройшли успішно чи ні. Page.IsValid дорівнює «True», якщо всі перевірки пройшли успішно, і «False» — у протилежному випадку. В останньому випадку можна зупинити обробку події, що викликала POST. Відправлена сторінка повертається у тому вигляді, в якому вона була введена. Валідатори, перевірка яких завершилася невдало, відображають свої повідомлення про помилки (атрибут ErrorMessage).

Якщо CausesValidation має значення False, то

  • на стороні клієнта жоден валідатор не виконується
  • На стороні сервера розробник повинен самостійно ініціювати виконання валідаторів сторінки. Це здійснюється за допомогою методу Page.Validate(). Залежно від результатів валідації цей метод встановлює значення властивості Page.IsValid як «True» або «False».

У [Default.aspx.cs] код обробки ButtonValider_Click змінюється наступним чином:


protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // занотуємо подію
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
      // відображається ім'я та вік
      LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
      // чи є сторінка валідною?
      Page.Validate();
      if (!Page.IsValid)
      {
        // загальне повідомлення про помилку
        LabelErreursSaisie.Text = "Veuillez corriger les erreurs de saisie...";
        LabelErreursSaisie.Visible = true;
        return;
      }
      // приховувати повідомлення про помилку
      LabelErreursSaisie.Visible = false;
      // відображаються перевірені ім'я та вік
      LabelValidation.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
}

У випадку, якщо атрибут CausesValidation кнопки [Valider] дорівнює True, а атрибути валідаторів EnableClientScript дорівнюють True, метод ButtonValider_Click виконується лише тоді, коли відправлені значення є дійсними. Тоді виникає питання щодо сенсу коду, який починається з 8-го рядка. Слід пам’ятати, що завжди існує можливість написання клієнтської програми, яка відправляє неперевірені значення на сторінку [Default.aspx]. Тому ця сторінка завжди повинна повторно виконувати перевірку на валідність.

  • рядок 8: запускає виконання всіх валідаторів сторінки. У випадку, якщо атрибут [Valider] кнопки [Valider] має значення від CausesValidation до True, це відбувається автоматично, і немає потреби повторювати цю процедуру. Тут є надмірність.
  • рядки 9–15: випадок, коли один із валідаторів дав збій
  • рядки 16–19: випадок, коли всі валідатори пройшли перевірку успішно

Ось два приклади виконання:

  • у [1] — приклад виконання у випадку, коли:
    • кнопка [Valider] має властивість CausesValidation до True
    • властивість валідаторів змінилася з EnableClientScript на True

Повідомлення про помилки [2] були відображені валідаторами, що виконуються на стороні клієнта за допомогою JavaScript-коду сторінки. Повідомлення POST на сервер не надходило, про що свідчить мітка відправлених елементів [3].

  • У [4] наведено приклад виконання у випадку, коли:
    • кнопка [Valider] має властивість CausesValidation, що дорівнює False
    • властивість валідаторів змінилася з EnableClientScript на False

Повідомлення про помилки [5] були відображені валідаторами, що виконуються на стороні сервера. Як показує [6], запит POST до сервера дійсно відбувся. У [7] — повідомлення про помилку, яке відображається методом [ButtonValider_Click] у разі помилок введення даних.

  • у [8] — приклад, отриманий із правильними даними. [9,10] показує, що відправлені елементи пройшли перевірку. Під час повторних тестів необхідно встановити для властивості EnableViewState мітки [LabelValidation] значення False, щоб повідомлення про перевірку не залишалося на екрані під час повторних запусків.

2.4. Управління даними області дії «Додаток»

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

Клас сторінки ASPX створюється на початку клієнтського запиту та знищується в кінці цього запиту. Тому він не може використовуватися для зберігання даних між двома запитами. Може виникнути потреба у зберіганні двох типів даних:

  • дані, спільні для всіх користувачів веб-додатку. Зазвичай це дані, доступні лише для читання. Для реалізації такого спільного використання даних використовуються три файли:
    • [Web.Config]: файл конфігурації додатка
    • [Global.asax, Global.asax.cs]: дозволяє визначити клас, який називається глобальним класом додатка, термін існування якого збігається з терміном існування самого додатка, а також обробники для певних подій цього ж додатка.

Глобальний клас додатка дозволяє визначити дані, які будуть доступні для всіх запитів усіх користувачів.

  • дані, спільні для запитів одного й того самого клієнта. Ці дані зберігаються в об’єкті, який називається «Сесія». У цьому контексті термін «сесія клієнта» позначає пам’ять клієнта. Усі запити клієнта мають доступ до цієї сесії. Вони можуть зберігати та зчитувати інформацію з неї

Вище наведено типи пам’яті, до яких має доступ сторінка ASPX:

  • пам’ять додатка, яка здебільшого містить дані, доступні лише для читання, і до якої мають доступ усі користувачі;
  • пам'ять конкретного користувача, або сесія, яка містить дані для читання та запису і є доступною для послідовних запитів одного й того самого користувача.
  • Хоча це не показано вище, існує пам’ять запиту, або контекст запиту. Запит користувача може оброблятися кількома послідовними сторінками ASPX. Контекст запиту дозволяє сторінці 1 передавати інформацію сторінці 2.

Тут нас цікавлять дані області дії Application, тобто ті, що є спільними для всіх користувачів. Глобальний клас додатка можна створити таким чином:

  • у [1] додаємо новий елемент до проєкту
  • у [2] додаємо глобальний клас додатка
  • у [3] зберігаємо ім’я за замовчуванням [Global.asax] для нового елемента
  • у [4] до проекту додано два нових файли
  • у [5] відображається розмітка [Global.asax]

<%@ Application Codebehind="Global.asax.cs" Inherits="Intro.Global" Language="C#" %>
  • тег Application замінює тег Page, який раніше використовувався для [Default.aspx]. Він ідентифікує глобальний клас додатка
  • Codebehind: визначає файл, у якому визначено глобальний клас додатка
  • Inherits: визначає ім’я цього класу

Створений клас Intro.Global має такий вигляд:


using System;

namespace Intro
{
  public class Global : System.Web.HttpApplication
  {

    protected void Application_Start(object sender, EventArgs e)
    {

    }

    protected void Session_Start(object sender, EventArgs e)
    {

    }

    protected void Application_BeginRequest(object sender, EventArgs e)
    {

    }

    protected void Application_AuthenticateRequest(object sender, EventArgs e)
    {

    }

    protected void Application_Error(object sender, EventArgs e)
    {

    }

    protected void Session_End(object sender, EventArgs e)
    {

    }

    protected void Application_End(object sender, EventArgs e)
    {

    }
  }
}
  • рядок 5: глобальний клас додатка походить від класу HttpApplication

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

  • рядки 8, 38: обробляють події Application_Start (запуск додатка) та Application_End (завершення роботи додатка при зупинці веб-сервера або при вивантаженні додатка адміністратором)
  • рядки 13, 33: обробляють події Session_Start (запуск нової сесії клієнта при підключенні нового клієнта або після закінчення терміну дії існуючої сесії) та Session_End (завершення сесії клієнта — або явно за допомогою програмного коду, або неявно через перевищення дозволеного терміну дії сесії).
  • рядок 28: обробляє подію Application_Error (виникнення винятку, який не обробляється кодом додатка та передається на сервер)
  • рядок 18: обробляє подію Application_BeginRequest (надходження нового запиту).
  • рядок 23: обробляє подію Application_AuhenticateRequest (відбувається, коли користувач пройшов аутентифікацію).

Метод [Application_Start] часто використовується для ініціалізації додатка на основі інформації, що міститься в [Web.Config]. Той, що генерується під час початкового створення проєкту, має такий вигляд:


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

<configuration>
    <configSections>
...
    </configSections>  
  
    <appSettings/>
    <connectionStrings/>
  
    <system.web>
...
    </system.web>

    <system.codedom>
....
    </system.codedom>
    
    <!-- 
        La section system.webServer est requise pour exécuter ASP.NET AJAX sur Internet
        Information Services 7.0.  Elle n'est pas nécessaire pour les versions précédentes d'IIS.
    -->
    <system.webServer>
...
    </system.webServer>

    <runtime>
....
    </runtime>

</configuration>

Для нашого поточного додатка цей файл не потрібен. Якщо його видалити або перейменувати, додаток продовжить працювати у звичайному режимі. Ми зосередимося на тегах у рядках 8 і 9:

  • <appsettings> дозволяє визначити словник інформації
  • <connectionStrings> дозволяє визначити рядки підключення до баз даних

Розглянемо такий файл [Web.config]:


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

<configuration>
    <configSections>
...
    </configSections>  
  
  <appSettings>
    <add key="cle1" value="valeur1"/>
    <add key="cle2" value="valeur2"/>
  </appSettings>
  <connectionStrings>
    <add connectionString="connectionString1" name="conn1"/>
  </connectionStrings>
  
    <system.web>
...

Цей файл може використовуватися таким глобальним класом додатка:


using System;
using System.Configuration;

namespace Intro
{
  public class Global : System.Web.HttpApplication
  {
    public static string Param1 { get; set; }
    public static string Param2 { get; set; }
    public static string ConnString1 { get; set; }
    public static string Erreur { get; set; }

    protected void Application_Start(object sender, EventArgs e)
    {
      try
      {
        Param1 = ConfigurationManager.AppSettings["cle1"];
        Param2 = ConfigurationManager.AppSettings["cle2"];
        ConnString1 = ConfigurationManager.ConnectionStrings["conn1"].ConnectionString;
      }
      catch (Exception ex)
      {
        Erreur = string.Format("Erreur de configuration : {0}", ex.Message);
      }
    }

    protected void Session_Start(object sender, EventArgs e)
    {

    }

  }
}
  • рядки 8–11: чотири статичні властивості P. Оскільки час існування класу Global збігається з часом існування додатка, будь-який запит до додатка матиме доступ до цих властивостей P за допомогою синтаксису Global.P.
  • рядки 17–19: доступ до файлу [Web.config] здійснюється через клас [System.Configuration.ConfigurationManager]
  • рядки 17–18: витягує елементи тегу <appSettings> з файлу [Web.config] за допомогою атрибута key.
  • рядок 19: отримує елементи тегу <connectionStrings> з файлу [Web.config] за допомогою атрибута name.

Статичні атрибути рядків 8–11 доступні з будь-якого обробника подій завантажених сторінок ASPX. Ми використовуємо їх в обробнику [Page_Load] сторінки [Default.aspx]:


    protected void Page_Load(object sender, EventArgs e)
    {
      // записується подія
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
      // отримується інформація з глобального класу додатка
      LabelGlobal.Text = string.Format("Param1={0},Param2={1},ConnString1={2},Erreur={3}", Global.Param1, Global.Param2, Global.ConnString1, Global.Erreur);
}
  • рядок 6: чотири статичні атрибути глобального класу додатка використовуються для заповнення нового мітки на сторінці [Default.aspx]

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

Зверху ми бачимо, що параметри [web.config] були правильно отримані. Глобальний клас додатка — це ідеальне місце для зберігання інформації, яка є спільною для всіх користувачів.

2.5. Управління даними в області дії «Сесія»

Тут нас цікавить, як зберігати інформацію протягом серії запитів певного користувача:

Кожен користувач має власну пам’ять, яку називають сесією.

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

  • Session_Start: початок сесії
  • Session_end: закінчення сесії

Механізм сесії реалізовано таким чином:

  • під час першого запиту користувача веб-сервер створює токен сесії, який він присвоює користувачеві. Цей токен — це унікальний для кожного користувача набір символів. Він надсилається сервером у відповіді на перший запит користувача.
  • під час наступних запитів користувач (веб-браузер) включає у свій запит сесійний токен, який йому було присвоєно. Таким чином веб-сервер може його розпізнати.
  • Сесія має певний термін дії. Коли веб-сервер отримує запит від користувача, він обчислює час, що минув з моменту попереднього запиту. Якщо цей час перевищує термін дії сесії, для користувача створюється нова сесія. Дані попередньої сесії втрачаються. У веб-сервері IIS (Internet Information Server) від Microsoft тривалість сесії за замовчуванням становить 20 хвилин. Це значення може бути змінене адміністратором веб-сервера.
  • Веб-сервер розпізнає, що має справу з першим запитом користувача, оскільки цей запит не містить сесійного токена. Це єдиний запит.

Будь-яка сторінка ASP.NET має доступ до сесії користувача через властивість Session сторінки, типу [System.Web.SessionState.HttpSessionState]. Ми будемо використовувати такі властивості P та методи M класу HttpSessionState:

Ім’я
Тип
Роль
Item[String clé]
P
Сесія може бути побудована у вигляді словника. Item[clé] — це елемент сесії, ідентифікований за clé. Замість запису [HttpSessionState].Item[clé] можна також записати [HttpSessionState].[clé].
Очистити
M
очищає словник сеансу
Скасувати
M
завершує сесію. Після цього сесія більше не є дійсною. Нова сесія розпочнеться з наступним запитом користувача.

Як приклад користувацької пам'яті ми підрахуємо, скільки разів користувач натискає на кнопку [Valider]. Щоб отримати цей результат, потрібно вести лічильник у сесії користувача.

Сторінка [Default.aspx] змінюється наступним чином:

Глобальний клас додатка [Global.asax.cs] змінюється наступним чином:


using System;
using System.Configuration;

namespace Intro
{
  public class Global : System.Web.HttpApplication
  {
    public static string Param1 { get; set; }
...

    protected void Application_Start(object sender, EventArgs e)
    {
...
    }

    protected void Session_Start(object sender, EventArgs e)
    {
      // лічильник запитів
      Session["nbRequêtes"] = 0;
    }

  }
}

У рядку 19 сесія користувача використовується для зберігання лічильника запитів, ідентифікованого ключем «nbRequêtes». Цей лічильник оновлюється менеджером [ButtonValider_Click] сторінки [Default.aspx]:


using System;

namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
....

    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // записується подія
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
      // відображаємо опубліковані ім'я та вік
      LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
      // кількість запитів
      Session["nbRequêtes"] = (int)Session["nbRequêtes"] + 1;
      LabelNbRequetes.Text = Session["nbRequêtes"].ToString();
      // чи є сторінка валідною?
      Page.Validate();
      if (!Page.IsValid)
      {
...
      }
...
    }
  }
}
  • рядок 16: лічильник запитів збільшується
  • рядок 17: лічильник відображається на сторінці

Ось приклад виконання:

2.6. Обробка GET / POST під час завантаження сторінки

Ми вже зазначали, що існує два типи запитів до сторінки ASPX:

  • початковий запит браузера, зроблений за допомогою команди HTTP GET. Сервер відповідає, надсилаючи запитувану сторінку. Припустимо, що ця сторінка є формою, c.a.d, і що на надісланій сторінці ASPX міститься тег <form runat="server"...>.
  • Наступні запити надсилаються браузером у відповідь на певні дії користувача у формі. Тоді браузер надсилає запит HTTP POST.

Незалежно від того, чи це запит GET, чи запит POST, виконується метод [Page_Load]. Під час виконання GET цей метод зазвичай використовується для ініціалізації сторінки, що надсилається до браузера клієнта. Потім, завдяки механізму VIEWSTATE, сторінка залишається ініціалізованою і змінюється лише за допомогою обробників подій, які викликають POST. У Page_Load немає необхідності переініціалізувати сторінку. Звідси випливає необхідність для цього методу знати, чи є запит клієнта GET чи POST.

Розглянемо наступний приклад. До сторінки [Default.aspx] додаємо випадаючий список. Вміст цього списку буде визначено в обробнику Page_Load запиту GET:

Список, що розгортається, оголошується в [Default.aspx.designer.cs] наступним чином:


        protected global::System.Web.UI.WebControls.DropDownList DropDownListNoms;

Ми будемо використовувати такі методи M та властивості P класу [DropDownList]:

Ім’я
Тип
Роль
Елементи
P
колекція типу ListItemCollection елементів типу ListItem зі списку, що розгортається
SelectedIndex
P
індекс (починаючи з 0) елемента, вибраного у випадаючому списку під час відправлення форми
SelectedItem
P
елемент типу ListItem, вибраний у випадаючому списку під час відправлення форми
SelectedValue
P
значення типу string для елемента типу ListItem, вибраного у випадаючому списку під час відправлення форми. Ми незабаром визначимо це поняття «значення».

Клас ListItem елементів випадаючого списку використовується для генерації тегів <option> тегу HTML <select>:

1
2
3
4
5
<select ....>
    <option value="val1">texte1</option>
    <option value="val2">texte2</option>
....
</select>

У тезі <option>

  • textei — це текст, що відображається у випадаючому списку
  • vali — це значення, яке надсилає браузер, якщо textei — це текст, вибраний у випадаючому списку

Кожен варіант можна створити за допомогою об’єкта LisItem, сформованого за допомогою конструктора ListItem(string текст, string значення).

У [Default.aspx.cs] код обробника [Page_Load] змінюється наступним чином:


    protected void Page_Load(object sender, EventArgs e)
    {
      // записуємо подію
      ...
      // отримується інформація з глобального класу додатка
      ...
      // ініціалізація комбінованого списку імен лише під час початкового виклику GET
      if (!IsPostBack)
      {
        for (int i = 0; i < 3; i++)
        {
          DropDownListNoms.Items.Add(new ListItem("nom"+i,i.ToString()));
        }
      }
}
  • рядок 8: клас Page має атрибут IsPostBack типу «логічне значення». Фактично це означає, що запит користувача є POST. Отже, рядки 10–13 виконуються лише для початкового GET клієнта.
  • рядок 12: до списку [DropDownListNoms] додається елемент типу ListItem (string texte, string value). Текст, що відображається для (i+1)-го елемента, буде nomi, а значення, відправлене для цього елемента у разі його вибору, буде i.

Обробник [ButtonValider_Click] змінено для відображення значення, відправленого через випадаючий список:


    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // фіксується подія
...
      // відображаються відправлені значення
      LabelPost.Text = string.Format("nom={0}, age={1}, combo={2}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim(), DropDownListNoms.SelectedValue);
      // кількість запитів
...
}

У рядку 6 значення, що відображається для списку [DropDownListNoms], отримується за допомогою властивості SelectedValue цього списку. Ось приклад виконання:

  • у [1] — вміст випадаючого списку після початкового GET і безпосередньо перед першим POST
  • в [2] — сторінка після першого POST.
  • в [3] — значення, відправлене для випадаючого списку. Відповідає атрибуту value елемента ListItem, вибраного зі списку.
  • у [4] — випадаючий список. Він містить ті самі елементи, що й після початкового GET. Це пояснюється механізмом роботи VIEWSTATE.

Щоб зрозуміти взаємодію між VIEWSTATE зі списку DropDownListNoms та тестом if (! IsPostBack) у менеджері Page_Load для [Default.aspx], читачеві пропонується повторити попередній тест із такими налаштуваннями:

Випадок
DropDownListNoms.EnableViewState
тест if(! IsPostBack) у Page_Load від [Default.aspx]
1
true
présent
2
false
présent
3
true
absent
4
false
absent

Різні тести дають такі результати:

  1. це той випадок, що наведено вище
  2. список заповнюється під час початкового GET, але не під час наступних POST. Оскільки EnableViewState дає помилковий результат, список залишається порожнім після кожного POST
  3. список заповнюється як після початкового GET, так і під час наступних POST. Оскільки EnableViewState відповідає vrai, то після початкового GET маємо 3 імена, після першого POST — 6 імен, після другого POST — 9 імен, ...
  4. Список заповнюється як після початкового запиту GET, так і під час наступних запитів POST. Оскільки EnableViewState дорівнює faux, список заповнюється лише трьома іменами при кожному запиті, незалежно від того, чи це початковий запит GET, чи наступні запити POST. Ми бачимо ту саму поведінку, що й у випадку 1. Отже, існує два способи отримати той самий результат.

2.7. Обробка VIEWSTATE елементів сторінки ASPX

За замовчуванням усі елементи сторінки ASPX мають властивість EnableViewState, що дорівнює True. Кожного разу, коли сторінка ASPX надсилається до браузера клієнта, вона містить приховане поле __VIEWSTATE, значенням якого є рядок символів, що кодує сукупність значень компонентів, властивість яких має значення від EnableViewState до True. Щоб мінімізувати розмір цього рядка, можна спробувати зменшити кількість компонентів, властивості яких мають значення від EnableViewState до True.

Нагадаємо, як компоненти сторінки ASPX отримують свої значення після виконання POST:

  1. створюється екземпляр сторінки ASPX. Компоненти ініціалізуються з їхніми проектними значеннями.
  2. значення __VIEWSTATE, надіслане браузером, використовується для того, щоб надати компонентам ті значення, які вони мали, коли сторінка ASPX була надіслана браузеру попереднього разу.
  3. значення, надіслані браузером, присвоюються компонентам
  4. виконуються обробники подій. Вони можуть змінювати значення деяких компонентів.

З цієї послідовності випливає, що компоненти, які:

  • мають відправлені значення
  • значення яких змінено обробником події

можуть мати властивість від EnableViewState до Faux, оскільки їхнє значення VIEWSTATE (крок 2) буде змінено на одному з кроків 3 або 4.

Список компонентів нашої сторінки доступний у [Default.aspx.designer.cs]:


namespace Intro {
    public partial class _Default {
        protected global::System.Web.UI.HtmlControls.HtmlForm form1;
        protected global::System.Web.UI.WebControls.TextBox TextBoxNom;
        protected global::System.Web.UI.WebControls.RequiredFieldValidator RequiredFieldValidatorNom;
        protected global::System.Web.UI.WebControls.TextBox TextBoxAge;
        protected global::System.Web.UI.WebControls.RequiredFieldValidator RequiredFieldValidatorAge;
        protected global::System.Web.UI.WebControls.RangeValidator RangeValidatorAge;
        protected global::System.Web.UI.WebControls.DropDownList DropDownListNoms;
        protected global::System.Web.UI.WebControls.Button ButtonValider;
        protected global::System.Web.UI.WebControls.ListBox ListBoxEvts;
        protected global::System.Web.UI.WebControls.Label LabelPost;
        protected global::System.Web.UI.WebControls.Label LabelValidation;
        protected global::System.Web.UI.WebControls.Label LabelErreursSaisie;
        protected global::System.Web.UI.WebControls.Label LabelGlobal;
        protected global::System.Web.UI.WebControls.Label LabelNbRequetes;
    }
}

Значення властивості EnableViewState для цих компонентів може бути таким:

Composant
Опубліковане значення
EnableViewState
Pourquoi
TextBoxNom
значення, введене в TextBox
False
значення компонента занесено
TextBoxAge
те саме
  
RequiredFieldValidatorNom
немає
False
відсутність поняття вартості компонента
RequiredFieldValidatorAge
те саме
  
RangeValidatorAge
те саме
  
LabelPost
немає
False
отримує своє значення через обробник події
LabelValidation
те саме
  
LabelErreursSaisie
те саме
  
LabelGlobal
те саме
  
LabelNbRequetes
те саме
  
DropDownListNoms
«значення» вибраного елемента
True
ми хочемо зберегти вміст списку під час виконання запитів, не перегенеруючи його
ListBoxEvts
«value» вибраного елемента
False
вміст списку генерується обробником подій
ButtonValider
назва кнопки
False
компонент зберігає своє проектне значення

2.8. Перенаправлення зі сторінки на іншу

Дотепер операції GET та POST завжди повертали одну й ту саму сторінку [Default.aspx]. Розглянемо випадок, коли запит обробляється двома послідовними сторінками ASPX — [Default.aspx] та [Page1.aspx] — і саме остання повертається клієнту. Крім того, ми розглянемо, як сторінка [Default.aspx] може передавати інформацію сторінці [Page1.aspx] через пам’ять, яку ми назвемо пам’яттю запиту.

Створюємо сторінку [Page1.aspx]:

  • у [1] додаємо новий елемент до проєкту
  • у [2] додаємо елемент [Web Form] з назвою [Page1.aspx] [3]
  • у [4] додається сторінка
  • у [5] — сторінка після побудови

Вихідний код [Page1.aspx] такий:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Page1.aspx.cs" Inherits="Intro.Page1" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>Page1</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h1>
      Page 1</h1>
    <asp:Label ID="Label1" runat="server"></asp:Label>
    <br />
    <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/Default.aspx">Retour 
      vers page [Default]</asp:HyperLink>
  </div>
  </form>
</body>
</html>
  • рядок 13: мітка, яка слугуватиме для відображення інформації, переданої сторінкою [Default.aspx]
  • рядок 15: посилання HTML на сторінку [Default.aspx]. Коли користувач натискає на це посилання, браузер запитує сторінку [Default.aspx] за допомогою операції GET. Сторінка [Default.aspx] завантажується так, ніби користувач ввів її URL-адресу безпосередньо в браузері.

Сторінка [Default.aspx] доповнюється новим компонентом типу LinkButton:

Вихідний код цього нового компонента такий:


  <asp:LinkButton ID="LinkButtonToPage1" runat="server" CausesValidation="False" 
    EnableViewState="False" onclick="LinkButtonToPage1_Click">Forward vers Page1</asp:LinkButton>
  • CausesValidation="False": клік на посилання спричинить перехід від POST до [Defaul.aspx]. Компонент [LinkButton] працює так само, як компонент [Button]. У цьому випадку ми не хочемо, щоб клік по посиланню запускав роботу валідаторів.
  • EnableViewState="False": немає необхідності зберігати стан посилання між запитами. Воно зберігає свої значення, задані при створенні.
  • onclick="LinkButtonToPage1_Click": ім’я методу, який у [Defaul.aspx.cs] обробляє подію Click на компоненті LinkButtonToPage1.

Код обробника LinkButtonToPage1_Click має такий вигляд:


  // на сторінку 1
  protected void LinkButtonToPage1_Click(object sender, EventArgs e)
  {
    // додаємо інформацію до контексту
    Context.Items["msg1"] = "Message de Default.aspx pour Page1";
    // передаємо запит на Page1
    Server.Transfer("Page1.aspx",true);
}

У рядку 7 запит передається на сторінку [Page1.aspx] за допомогою методу [Server.Transfer]. Другий параметр методу, який має значення true, вказує на те, що до [Page1.aspx] слід передати всю інформацію, яка була надіслана до [Default.aspx] під час виконання POST. Це дозволяє, наприклад, [Page1.aspx] отримати доступ до значень, відправлених через колекцію з назвою Request.Form. У рядку 5 використовується так званий контекст запиту. Доступ до нього здійснюється через властивість Context класу Page. Цей контекст може слугувати пам’яттю між різними сторінками, що обробляють один і той самий запит, у даному випадку [Default.aspx] та [Page1.aspx]. Для цього використовується словник Items.

Коли [Page1.aspx] завантажується операцією Server.Transfer("Page1.aspx",true), все відбувається так, ніби [Page1.aspx] було викликано операцією GET з браузера. Обробник Page_Load для [Page1.aspx] виконується у звичайному режимі. Ми використаємо його для відображення повідомлення, розміщеного [Default.aspx] у контексті запиту:


using System;

namespace Intro
{
  public partial class Page1 : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      Label1.Text = Context.Items["msg1"] as string;
    }
  }
}

У рядку 9 повідомлення, розміщене [Default.aspx] у контексті запиту, відображається в Label1.

Ось приклад виконання:

  • на сторінці [Default.aspx] [1] ми натискаємо на посилання [2], яке перенаправляє нас на сторінку Page1
  • на сторінці [3] відображається сторінка Page1
  • на [4] — повідомлення, створене на [Default.aspx] і відображене на [Page1.aspx]
  • у [5] сторінка URL, що відображається в браузері, відповідає сторінці [Default.aspx]

2.9. Перенаправлення однієї сторінки на іншу

Тут ми розглянемо іншу техніку, функціонально близьку до попередньої: коли користувач запитує сторінку [Default.aspx] через POST, він отримує у відповідь іншу сторінку [Page2.aspx]. У попередньому методі запит користувача послідовно оброблявся двома сторінками: [Default.aspx] та [Page1.aspx]. У методі перенаправлення сторінки, який ми зараз розглядаємо, браузер надсилає два окремі запити:

  • на [1], браузер надсилає запит POST на сторінку [Default.aspx]. Ця сторінка обробляє запит і надсилає браузеру так звану відповідь перенаправлення. Ця відповідь — це простий потік HTTP (рядки тексту), що вимагає від браузера перенаправитися на іншу URL-адресу [Page2.aspx]. [Default.aspx] не надсилає потік HTML у цій першій відповіді.
  • У [2] браузер надсилає запит GET на сторінку [Page2.aspx]. Ця сторінка потім надсилається у відповідь браузеру.
  • Якщо сторінка [Default.aspx] хоче передати інформацію сторінці [Page2.aspx], вона може зробити це через сесію користувача. На відміну від попереднього методу, контекст запиту тут не можна використовувати, оскільки тут є два окремі запити, а отже, два окремі контексти. Тому для взаємодії між сторінками потрібно використовувати сесію користувача.

Як це було зроблено для [Page1.aspx], ми додаємо до проєкту сторінку [Page2.aspx]:

  • у [1], [Page2.aspx] було додано до проєкту
  • у [2] візуальний вигляд [Page2.aspx]
  • у [3], ми додаємо на сторінку [Default.aspx] компонент LinkButton [4], який перенаправить користувача на [Page2.aspx].

Вихідний код [Page2.aspx] аналогічний коду [Page1.aspx]:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Page2.aspx.cs" Inherits="Intro.Page2" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>Page2</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h1>
      Page 2</h1>
    <asp:Label ID="Label1" runat="server"></asp:Label>
    <br />
    <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/Default.aspx">Retour 
      vers page [Default]</asp:HyperLink>
  </div>
  </form>
</body>
</html>

У [Default.aspx] додавання компонента LinkButton згенерувало такий вихідний код:


<asp:LinkButton ID="LinkButtonToPage2" runat="server" 
    onclick="LinkButtonToPage2_Click">Redirection vers Page2</asp:LinkButton>

Перенаправлення на [Page2.aspx] забезпечує менеджер [LinkButtonToPage2_Click]. Його код у [Defaul.aspx.cs] такий:


    protected void LinkButtonToPage2_Click(object sender, EventArgs e)
    {
      // додаємо повідомлення до сесії
      Session["msg2"] = "Message de [Default.aspx] pour [Page2.aspx]";
      // перенаправляємо клієнта на [Page2.aspx]
      Response.Redirect("Page2.aspx");
}
  • рядок 4: записуємо повідомлення в сесію користувача
  • рядок 5: об’єкт Response є властивістю будь-якої сторінки ASPX. Він представляє відповідь, надану клієнту. Він має метод Redirect, завдяки якому відповідь, надана клієнту, буде командою HTTP на перенаправлення.

Коли браузер отримає команду перенаправлення на [Page2.aspx], він виконає GET на цій сторінці. На цій сторінці буде виконано метод [Page_Load]. Ми використаємо його, щоб отримати повідомлення, розміщене [Default.aspx] у сесії, та відобразити його. Код [Page2.aspx.cs] виглядає так:


using System;

namespace Intro
{
  public partial class Page2 : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      // відображається повідомлення, додане до сесії за допомогою [Default.aspx]
      Label1.Text = Session["msg2"] as string;
    }
  }
}

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

  • на [1] натискаємо на посилання перенаправлення з [Default.aspx]. Здійснюється перенаправлення з POST на сторінку [Default.aspx]
  • на [2], браузер був перенаправлений на [Page2.aspx]. Це видно з URL, що відображається браузером. У попередньому методі цією URL-адресою була [Default.aspx], оскільки єдиний запит, зроблений браузером, був саме на цю URL-адресу. Тут спочатку відбувається перенаправлення з POST на [Default.aspx], а потім, без відома користувача, — друге перенаправлення з GET на [Page2.aspx].
  • У [3] видно, що [Page2.aspx] правильно отримав повідомлення, розміщене [Default.aspx] у сесії.

2.10. Conclusion

На прикладі кількох прикладів ми ознайомилися з поняттями ASP.NET, які стануть у нагоді в подальшому тексті документа. Цього вступу недостатньо для розуміння тонкощів обміну даними між клієнтом і сервером у веб-додатку. Для цього можна ознайомитися з:

  • Програмування ASP.NET [Développement WEB avec ASP.NET 1.1 ]