Skip to content

2. Krótkie wprowadzenie do ASP.NET

W niniejszym dokumencie zamierzamy przedstawić, na podstawie kilku przykładów, pojęcia związane z ASP.NET, które będą nam przydatne w dalszej części dokumentu. Niniejsze wprowadzenie nie pozwala na zrozumienie subtelności komunikacji klient–serwer w aplikacji internetowej. W tym celu warto zapoznać się z:

Niniejsze wprowadzenie jest przeznaczone dla osób, które chcą szybko przejść do sedna, godząc się na razie z pominięciem niektórych kwestii, które mogą okazać się istotne. W dalszej części dokumentu omówimy je bardziej szczegółowo. Osoby zaznajomione z ASP.NET mogą przejść bezpośrednio do akapitu 3.

2.1. Przykładowy projekt

2.1.1. Tworzenie projektu

  • w [1] tworzymy nowy projekt za pomocą programu Visual Web Developer
  • w [2] wybieramy projekt internetowy w Visual C#
  • w [3], określamy, że chcemy utworzyć aplikację internetową w ASP.NET
  • w [4] nadajemy nazwę aplikacji. Zostanie utworzony folder projektu o tej nazwie.
  • w [5] należy wskazać folder nadrzędny dla folderu projektu [4]
  • w [6] – utworzony projekt
  • [Default.aspx] to strona internetowa utworzona domyślnie. Zawiera ona tagi HTML oraz tagi ASP.NET
  • [Default.aspx.cs] zawiera kod do obsługi zdarzeń wywołanych przez użytkownika na stronie [Defaul.aspx] wyświetlanej w jego przeglądarce
  • [Default.aspx.designer.cs] zawiera listę komponentów ASP.NET strony [Default.aspx]. Każdy komponent ASP.NET umieszczony na stronie [Default.aspx] powoduje utworzenie deklaracji tego komponentu w pliku [Default.aspx.designer.cs].
  • [Web.config] to plik konfiguracyjny projektu ASP.NET.
  • [References] to lista plików DLL wykorzystywanych przez projekt internetowy. Te pliki DLL to biblioteki klas, z których projekt będzie korzystał. W pliku [7] znajduje się lista plików DLL domyślnie umieszczonych w odwołaniach projektu. Większość z nich jest zbędna. Jeśli projekt ma korzystać z biblioteki DLL, która nie figuruje na liście w pliku [7], można ją dodać za pomocą pliku [8].

2.1.2. Strona [Default.aspx]

Jeśli uruchomimy projekt za pomocą pliku [Ctrl-F5], w przeglądarce wyświetli się strona [Default.aspx]:

  • w pliku [1] wyświetla się strona URL z projektu internetowego. Program Visual Web Developer posiada wbudowany serwer WWW, który uruchamia się po zleceniu wykonania projektu. Serwer nasłuchuje na losowym porcie, w tym przypadku 1490. Port nasłuchowy to zazwyczaj port 80. W pliku [1] nie wywołano żadnej strony. W takim przypadku wyświetlana jest strona [Default.aspx], stąd jej nazwa „strona domyślna”.
  • W [2] strona [Default.aspx] jest pusta.
  • w programie Visual Web Developer stronę [Default.aspx] [3] można utworzyć wizualnie (zakładka [Design]) lub za pomocą znaczników (zakładka [Source])
  • w [4] strona [Defaul.aspx] w trybie [Design]. Tworzy się ją poprzez umieszczanie na niej komponentów dostępnych w zestawie narzędzi [5].

Tryb [Source] [6] zapewnia dostęp do kodu źródłowego strony:


<%@ 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>
  • wiersz 1 to dyrektywa ASP.NET, która wyświetla niektóre właściwości strony
    • dyrektywa Page ma zastosowanie do strony internetowej. Istnieją inne dyrektywy, takie jak Application, WebService, ... które mają zastosowanie do innych obiektów ASP.NET
    • atrybut CodeBehind wskazuje plik obsługujący zdarzenia na stronie
    • atrybut Language wskazuje język .NET używany przez plik CodeBehind
    • atrybut Inherits wskazuje nazwę klasy zdefiniowanej w pliku CodeBehind
    • Atrybut AutoEventWireUp="true" wskazuje, że powiązanie między zdarzeniem w [Default.aspx] a jego procedurą obsługi w [Defaul.aspx.cs] odbywa się na podstawie nazwy zdarzenia. W ten sposóbzdarzenie Load na stronie [Default.aspx] zostanie przetworzone przez metodę Page_Load klasy Intro._Default zdefiniowanej przez atrybut Inherits.
  • Wiersze 4–14 opisują stronę [Defaul.aspx] za pomocą tagów:
    • klasycznych znaczników HTML, takich jak znacznik <body> lub <div>
    • ASP.NET. Są to tagi posiadające atrybut runat="server". Tagi ASP.NET są przetwarzane przez serwer WWW przed wysłaniem strony do klienta. Są one przekształcane w tagi HTML. Przeglądarka klienta otrzymuje zatem standardową stronę HTML, w której nie ma już tagów ASP.NET.

Stronę [Default.aspx] można modyfikować bezpośrednio w kodzie źródłowym. Czasami jest to prostsze niż korzystanie z trybu [Design]. Modyfikujemy kod źródłowy w następujący sposób:


<%@ 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>

W wierszu 6 nadajemy stronie tytuł za pomocą tagu HTML <title>. W wierszu 9 umieszczamy tekst w treści (<body>) strony. Jeśli uruchomimy projekt (Ctrl-F5), w przeglądarce otrzymamy następujący wynik:

 

2.1.3. Pliki [Default.aspx.designer.cs] i [Default.aspx.cs]

Plik [Default.aspx.designer.cs] definiuje elementy strony [Defaul.aspx]:


//------------------------------------------------------------------------------
// <wygenerowane automatycznie>
//      Ten kod został wygenerowany przez narzędzie.
//      Wersja środowiska uruchomieniowego: 2.0.50727.3603
//
//      Zmiany wprowadzone w tym pliku mogą spowodować nieprawidłowe działanie i zostaną utracone, jeśli
//      kod zostanie wygenerowany ponownie.
// </auto-generated>
//------------------------------------------------------------------------------

namespace Intro {
    
    
    public partial class _Default {
        
        /// <summary>
        /// Kontrola form1.
        /// </summary>
        /// <uwagi>
        /// Pole wygenerowane automatycznie.
        /// Aby wprowadzić zmiany, przenieś deklarację pola z pliku projektowego do pliku kodu zaplecza.
        /// </uwagi>
        protected global::System.Web.UI.HtmlControls.HtmlForm form1;
    }
}

W tym pliku znajduje się lista komponentów ASP.NET ze strony [Default.aspx] posiadających identyfikator. Odpowiadają one tagom z pliku [Default.aspx] posiadającym atrybut runat="server" oraz atrybut id. Zatem komponent z wiersza 23 powyżej odpowiada tagowi


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

w pliku [Default.aspx].

Programista rzadko korzysta z pliku [Default.aspx.designer.cs]. Niemniej jednak plik ten jest przydatny do ustalenia klasy konkretnego komponentu. Jak widać poniżej, komponent form1 jest typu HtmlForm. Programista może następnie zapoznać się z tą klasą, aby poznać jej właściwości i metody. Komponenty strony [Default.aspx] są wykorzystywane przez klasę z pliku [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)
    {

    }
  }
}

Należy zauważyć, że klasa zdefiniowana w plikach [Default.aspx.cs] i [Default.aspx.designer.cs] jest ta sama (wiersz 10): Intro._Default. To właśnie słowo kluczowe partial umożliwia rozszerzenie deklaracji klasy na wiele plików, w tym przypadku na dwa.

W wierszu 10 powyżej widać, że klasa [_Default] rozszerza klasę [Page] i dziedziczy po niej zdarzenia. Jednym z nich jest zdarzenie Load, które występuje, gdy strona jest ładowana przez serwer WWW. W wierszu 12 znajduje się metoda Page_Load, która obsługuje zdarzenie Load strony. Zazwyczaj właśnie w tym miejscu inicjuje się stronę przed jej wyświetleniem w przeglądarce klienta. W tym przypadku metoda Page_Load nie wykonuje żadnej czynności.

Klasa powiązana ze stroną internetową, w tym przypadku klasa Intro._Default, jest tworzona na początku żądania klienta i niszczona po wysłaniu odpowiedzi do klienta. Nie może więc służyć do przechowywania informacji między dwoma żądaniami. W tym celu należy skorzystać z pojęcia sesji użytkownika.

2.2. Zdarzenia na stronie internetowej ASP.NET

Tworzymy następującą stronę [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>

Tryb działania strony [Design] jest następujący:

 

Plik [Default.aspx.designer.cs] wygląda następująco:


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;
    }
}

Znajdują się w nim wszystkie elementy ASP.NET ze strony [Default.aspx] posiadające identyfikator.

Modyfikujemy plik [Default.aspx.cs] w następujący sposób:


using System;

namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
    protected void Page_Init(object sender, EventArgs e)
    {
      // odnotowuje się zdarzenie
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Init", DateTime.Now.ToString("hh:mm:ss")));
    }

    protected void Page_Load(object sender, EventArgs e)
    {
      // odnotowuje się zdarzenie
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
    }

    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // odnotowuje się zdarzenie
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
    }
  }
}

Klasa [_Default] (wiersz 5) obsługuje trzy zdarzenia:

  • zdarzenie Init (wiersz 7), które występuje po zainicjowaniu strony
  • zdarzenie Load (wiersz 13), które występuje po załadowaniu strony przez serwer WWW. Zdarzenie Init występuje przed zdarzeniem Load.
  • zdarzenie Click na przycisku ButtonValider (wiersz 19), które występuje, gdy użytkownik kliknie przycisk [Valider]

Obsługa każdego z tych trzech zdarzeń polega na dodaniu komunikatu do komponentu Listbox o nazwie ListBoxEvts. Komunikat ten wyświetla godzinę zdarzenia oraz jego nazwę. Każdy komunikat umieszczany jest na początku listy. Zatem komunikaty znajdujące się na górze listy są najnowsze.

Po uruchomieniu projektu wyświetla się następująca strona:

Z [1] wynika, że zdarzenia Page_Init i Page_Load miały miejsce w tej kolejności. Przypominamy, że najnowsze zdarzenie znajduje się na górze listy. Gdy przeglądarka żąda strony [Default.aspx] bezpośrednio poprzez jej adres URL [2], robi to za pomocą polecenia HTTP (HyperText Transfer Protocol) o nazwie GET. Po załadowaniu strony w przeglądarce użytkownik wywoła zdarzenia na tej stronie. Na przykład kliknie przycisk o nazwie [Valider] [3]. Zdarzenia wywołane przez użytkownika po załadowaniu strony w przeglądarce uruchamiają żądanie do strony [Default.aspx], ale tym razem z poleceniem HTTP o nazwie POST. Podsumowując:

  • początkowe załadowanie strony P w przeglądarce odbywa się poprzez operację HTTP GET
  • zdarzenia, które następnie mają miejsce na stronie, generują za każdym razem nowe żądanie skierowane do tej samej strony P, ale tym razem z poleceniem HTTP POST. Strona P może rozpoznać, czy została wywołana za pomocą polecenia GET, czy też POST, co pozwala jej zachowywać się inaczej, jeśli jest to konieczne, co ma miejsce w większości przypadków.

Pierwotne wywołanie strony ASPX: GET

  • na [1], przeglądarka żąda strony ASPX za pomocą polecenia HTTP GET bez parametrów.
  • W przypadku [2] serwer WWW wysyła w odpowiedzi strumień HTML, będący tłumaczeniem żądanej strony ASPX.

Obsługa zdarzenia wygenerowanego na stronie wyświetlanej przez przeglądarkę: POST

  • na [1], podczas zdarzenia na stronie HTML, przeglądarka żąda strony ASPX, która została już pobrana za pomocą operacji GET, tym razem za pomocą polecenia HTTP POST wraz z parametrami. Parametry te to wartości elementów znajdujących się wewnątrz tagu <form> na stronie HTML wyświetlanej przez przeglądarkę. Wartości te nazywane są wartościami przesłanymi przez klienta. Zostaną one wykorzystane przez stronę ASPX w celu przetworzenia żądania klienta.
  • W przypadku strony [2] serwer WWW wysyła w odpowiedzi strumień HTML, będący tłumaczeniem strony ASPX, o którą pierwotnie poproszono w POST, lub innej strony, jeśli nastąpiło przekierowanie lub zmiana strony.

Wróćmy do naszej przykładowej strony:

  • w [2] – strona została uzyskana poprzez GET.
  • w [1] widać dwa zdarzenia, które miały miejsce podczas tego GET

Jeśli powyżej użytkownik kliknie przycisk [Valider] [3], strona [Default.aspx] zostanie wywołana za pomocą POST. Do tego POST dołączone zostaną parametry, które będą stanowić wartości wszystkich elementów zawartych w tagu <form> strony [Default.aspx]: oba elementy TextBox i [TextBoxNom, TextBoxAge], przycisk [ButtonValider] oraz lista [ListBoxEvts]. Wartości przesłane dla tych elementów są następujące:

  • TextBox: wprowadzona wartość
  • Button: tekst przycisku, w tym przypadku tekst „Potwierdź”
  • Listbox: tekst komunikatu wybranego w ListBox

W odpowiedzi na POST otrzymujemy stronę [4]. Jest to ponownie strona [Default.aspx]. Jest to normalne zachowanie, o ile nie nastąpiło przekazanie lub przekierowanie strony przez menedżery zdarzeń tej strony. Widać, że wystąpiły dwa nowe zdarzenia:

  • zdarzenie Page_Load, które miało miejsce podczas ładowania strony
  • zdarzenie ButtonValider_Click, które wystąpiło w wyniku kliknięcia przycisku [Valider]

Można zauważyć, że:

  • zdarzenie Page_Init nie wystąpiło w ramach operacji HTTP POST, podczas gdywystąpiło ono w przypadku zdarzeń HTTP i GET
  • zdarzenie Page_Load występuje zawsze, niezależnie od tego, czy dotyczy operacji GET, czy POST. W tej metodzie zazwyczaj trzeba wiedzieć, czy mamy do czynienia z GET, czy z POST.
  • Po zakończeniu przetwarzania POST strona [Default.aspx] została odesłana do klienta wraz ze zmianami wprowadzonymi przez moduły obsługi zdarzeń. Tak jest zawsze. Po przetworzeniu zdarzeń strony P ta sama strona P jest odsyłana do klienta. Istnieją dwa sposoby na obejście tej reguły. Ostatni uruchomiony moduł obsługi zdarzeń może
    • przekierować tok wykonania na inną stronę P2.
    • przekierować przeglądarkę klienta na inną stronę P2.

W obu przypadkach do przeglądarki zwracana jest strona P2. Obie metody różnią się od siebie w pewnych aspektach, do których jeszcze wrócimy.

  • Zdarzenie ButtonValider_Click wystąpiło po zdarzeniu Page_Load. To właśnie ten handler może podjąć decyzję o przeniesieniu lub przekierowaniu na stronę P2.
  • Lista zdarzeń [4] zachowała oba zdarzenia wyświetlone podczas początkowego ładowania GET strony [Default.aspx]. Jest to zaskakujące, biorąc pod uwagę, że strona [Default.aspx] została odtworzona podczas operacji POST. Powinna pojawić się strona [Default.aspx] z wartościami projektowymi, a zatem strona ListBox powinna być pusta. Wykonanie procedur Page_Load i ButtonValider_Click powinno następnie umieścić tam dwa komunikaty. Znajdujemy jednak cztery. Wyjaśnia to mechanizm działania VIEWSTATE. Podczas początkowego działania GET serwer WWW wysyła stronę [Default.aspx] z tagiem HTML <input type="hidden" ...>, zwanym polem ukrytym (wiersz 10 poniżej).
<!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>
..............................

W polu identyfikatora „__VIEWSTATE” serwer WWW koduje wartości wszystkich elementów strony. Czyni to zarówno w przypadku początkowego identyfikatora GET, jak i kolejnych identyfikatorów POST. Gdy na stronie P pojawia się element o identyfikatorze „POST”:

  • przeglądarka żąda strony P, wysyłając w swoim żądaniu wartości wszystkich elementów znajdujących się wewnątrz tagu <form>. Powyżej widać, że komponent „__VIEWSTATE” znajduje się wewnątrz tagu <form>. Jego wartość jest zatem wysyłana do serwera podczas wystąpienia POST.
  • Strona P jest instancjonowana i inicjowana z jej wartościami konstrukcyjnymi
  • komponent „__VIEWSTATE” służy do przywrócenia komponentom wartości, jakie miały w momencie poprzedniego wysłania strony P. W ten sposób na przykład lista zdarzeń [4] odzyskuje dwa pierwsze komunikaty, które zawierała, gdy została wysłana w odpowiedzi na początkowy komunikat GET z przeglądarki.
  • Następnie komponenty strony P przyjmują wartości przesłane przez przeglądarkę. W tym momencie formularz na stronie P znajduje się w stanie, w jakim użytkownik go przesłał.
  • Obsługiwane jest zdarzenie Page_Load. W tym miejscu dodaje ono komunikat do listy zdarzeń [4].
  • Przetwarzane jest zdarzenie, które wywołało POST. W tym miejscu ButtonValider_Click dodaje komunikat do listy zdarzeń [4].
  • Strona P jest zwracana. Komponenty mają następujące wartości:
    • albo wartość przesłaną, c.a.d; albo wartość, jaką komponent miał w formularzu w momencie jego przesłania na serwer
    • albo wartość podana przez jeden z menedżerów zdarzeń.

W naszym przykładzie

  • oba komponenty TextBox odzyskają swoje wartości przesłane, ponieważ menedżery zdarzeń nie ingerują w nie
  • lista zdarzeń [4] odzyskuje swoją wartość przesłaną, c.a.d. wszystkie zdarzenia już wpisane na listę oraz dwa nowe zdarzenia utworzone przez metody Page_Load i ButtonValider_Click.

Mechanizm VIEWSTATE można włączyć lub wyłączyć na poziomie każdego komponentu. Wyłączmy go dla komponentu [ListBoxEvts]:

  • w [1] mechanizm VIEWSTATE komponentu [ListBoxEvts] jest wyłączony. Mechanizm komponentów TextBox i [2] jest domyślnie włączony.
  • W przypadku [3] oba zdarzenia zwracane po początkowym GET
  • w [4] – formularz został wypełniony i kliknięto przycisk [Valider]. Nastąpi przekierowanie POST na stronę [Default.aspx].
  • w [6] – wynik zwrócony po kliknięciu przycisku [Valider]
  • aktywowany mechanizm VIEWSTATE wyjaśnia, dlaczego TextBox i [7] zachowały swoje wartości zapisane w [4]
  • wyłączony mechanizm VIEWSTATE wyjaśnia, dlaczego komponenty [ListBoxEvts] i [8] nie zachowały swojej zawartości z [5].

2.3. Zarządzanie wartościami przesłanymi

W tym miejscu skupimy się na wartościach przesłanych przez oba elementy TextBox, gdy użytkownik kliknie przycisk [Valider]. Strona [Default.aspx] w trybie [Design] zmienia się w następujący sposób:

Kod źródłowy elementu dodanego w [1] jest następujący:


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

Wykorzystamy komponent [LabelPost] do wyświetlenia wartości wprowadzonych w obu elementach TextBox i [2]. Kod menedżera zdarzeń [Default.aspx.cs] zmienia się w następujący sposób:


using System;

namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
    protected void Page_Init(object sender, EventArgs e)
    {
      // odnotowujemy zdarzenie
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Init", DateTime.Now.ToString("hh:mm:ss")));
    }

    protected void Page_Load(object sender, EventArgs e)
    {
      // odnotowuje się zdarzenie
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
    }

    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // odnotowuje się wydarzenie
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
      // wyświetla się imię i wiek
      LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
    }
  }
}

W wierszu 24 aktualizuje się komponent LabelPost:

  • LabelPost jest typu [System.Web.UI.WebControls.Label] (por. Default.aspx.designer.cs). Jego właściwość Text reprezentuje tekst wyświetlany przez komponent.
  • TextBoxNom i TextBoxAge są typu [System.Web.UI.WebControls.TextBox]. Właściwość Text komponentu TextBox to tekst wyświetlany w polu wprowadzania danych.
  • Metoda Trim() usuwa spacje, które mogą występować przed lub po ciągu znaków

Jak wyjaśniono wcześniej, po uruchomieniu metody ButtonValider_Click komponenty strony przyjmują wartości, jakie miały w momencie wysłania strony przez użytkownika. Właściwości Text obu metod TextBox mają zatem wartości odpowiadające tekstom wprowadzonym przez użytkownika w przeglądarce.

Oto przykład:

  • w [1] wartości przesłane
  • w [2], odpowiedź serwera.
  • w [3], TextBox odzyskały swoje wartości przesłane dzięki mechanizmowi VIEWSTATE, który został aktywowany
  • w [4], komunikaty komponentu ListBoxEvts pochodzą z metod Page_Init, Page_Load, ButtonValider_Click oraz z wyłączonego komponentu VIEWSTATE
  • W komponencie [5] wartość komponentu LabelPost została uzyskana za pomocą metody ButtonValider_Click. Udało się poprawnie pobrać obie wartości wprowadzone przez użytkownika w komponentach TextBox i [1].

Jak widać powyżej, wartość przesłana dla pola „wiek” to ciąg znaków „yy”, co jest wartością niedozwoloną. Dodamy do strony komponenty zwane walidatorami. Służą one do sprawdzania poprawności przesłanych danych. Poprawność tę można sprawdzić w dwóch miejscach:

  • po stronie klienta. Opcja konfiguracyjna walidatora pozwala zdecydować, czy testy mają być przeprowadzane w przeglądarce. W takim przypadku są one wykonywane przez kod JavaScript osadzony na stronie HTML. Gdy użytkownik wysyła wartości wprowadzone w formularzu, są one najpierw sprawdzane przez kod JavaScript. Jeśli którykolwiek z testów zakończy się niepowodzeniem, wysyłanie danych nie jest realizowane. Pozwala to uniknąć dodatkowej komunikacji z serwerem, dzięki czemu strona działa sprawniej.
  • na serwerze. O ile testy po stronie klienta mogą być opcjonalne, o tyle po stronie serwera są one obowiązkowe, niezależnie od tego, czy po stronie klienta przeprowadzono weryfikację, czy nie. W rzeczywistości, gdy strona otrzymuje przesłane wartości, nie ma możliwości ustalenia, czy zostały one zweryfikowane przez klienta przed wysłaniem. Po stronie serwera programista musi zatem zawsze sprawdzać poprawność przesłanych danych.

Strona [Default.aspx] zmienia się w następujący sposób:


<%@ 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>

W wierszach 20, 32 i 35 dodano walidatory. W wierszu 58 wykorzystano komponent Label do wyświetlenia prawidłowych wartości przesłanych metodą POST. W wierszu 60 wykorzystywany jest komponent Label do wyświetlania komunikatu o błędzie w przypadku wystąpienia błędów wprowadzenia danych.

Strona [Default.aspx] w trybie [Design] wygląda następująco:

  • Komponenty [1] i [2] są typu RequiredFieldValidator. Ten moduł sprawdzający weryfikuje, czy pole wprowadzania danych nie jest puste.
  • Komponent [3] jest typu RangeValidator. Ten walidator sprawdza, czy pole wprowadzania danych zawiera wartość mieszczącą się w przedziale określonych granic.
  • W [4] znajdują się właściwości walidatora [1].

Przedstawimy oba typy walidatorów na przykładzie ich tagów w kodzie strony [Default.aspx]:


          <asp:RequiredFieldValidator ID="RequiredFieldValidatorNom" runat="server" 
            ControlToValidate="TextBoxNom" Display="Dynamic" 
ErrorMessage="Donnée obligatoire !"></asp:RequiredFieldValidator>
  • ID: identyfikator komponentu
  • ControlToValidate: nazwa komponentu, którego wartość jest sprawdzana. W tym przypadku chcemy, aby komponent TextBoxNom nie miał pustej wartości (pustego ciągu znaków lub ciągu spacji)
  • ErrorMessage: komunikat o błędzie, który ma zostać wyświetlony w walidatorze w przypadku nieprawidłowych danych.
  • EnableClientScript: wartość logiczna wskazująca, czy walidator ma być uruchamiany również po stronie klienta. Atrybut ten ma domyślną wartość True, o ile nie został wyraźnie ustawiony w sposób opisany powyżej.
  • Display: tryb wyświetlania walidatora. Dostępne są dwa tryby:
    • static (domyślny): walidator zajmuje miejsce na stronie, nawet jeśli nie wyświetla komunikatu o błędzie
    • dynamic: walidator nie zajmuje miejsca na stronie, jeśli nie wyświetla komunikatu o błędzie.

          <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>
  • Typ: typ sprawdzanych danych. W tym przypadku wiek jest liczbą całkowitą.
  • MinimumValue, MaximumValue: granice, w których musi mieścić się sprawdzana wartość

Konfiguracja komponentu, który wywołuje POST, ma wpływ na sposób walidacji. W tym przypadku komponentem tym jest przycisk [Valider]:


  <asp:Button ID="ButtonValider" runat="server" onclick="ButtonValider_Click"  Text="Valider" CausesValidation="True" />
  • CausesValidation: określa tryb automatyczny lub nazwy walidacji po stronie serwera. Atrybut ten ma wartość domyślną „True”, jeśli nie zostanie wyraźnie określony. W takim przypadku
    • po stronie klienta uruchamiane są walidatory o nazwach od EnableClientScript do True. POST ma miejsce tylko wtedy, gdy wszystkie walidatory po stronie klienta zakończą się powodzeniem.
    • Po stronie serwera wszystkie walidatory obecne na stronie są automatycznie uruchamiane przed przetworzeniem zdarzenia, które wywołało POST. W tym przypadku zostałyby one uruchomione przed wykonaniem metody ButtonValider_Click. W tej metodzie można sprawdzić, czy wszystkie walidacje zakończyły się powodzeniem, czy nie. Page.IsValid ma wartość „True”, jeśli wszystkie zakończyły się powodzeniem, a „False” w przeciwnym razie. W tym drugim przypadku można przerwać przetwarzanie zdarzenia, które wywołało POST. Przesłana strona jest zwracana w takiej postaci, w jakiej została wprowadzona. Walidatory, które zakończyły się niepowodzeniem, wyświetlają wówczas komunikaty o błędach (atrybut ErrorMessage).

Jeśli CausesValidation ma wartość False, to

  • po stronie klienta nie jest uruchamiany żaden moduł sprawdzający poprawność
  • Po stronie serwera to programista sam musi zainicjować uruchomienie walidatorów strony. Odbywa się to za pomocą metody Page.Validate(). W zależności od wyniku walidacji metoda ta ustawia właściwość Page.IsValid na „True” lub „False”.

W metodzie [Default.aspx.cs] kod przetwarzania z metody ButtonValider_Click zmienia się w następujący sposób:


protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // zapisuje się wydarzenie
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
      // wyświetla się imię i wiek
      LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
      // czy strona jest poprawna?
      Page.Validate();
      if (!Page.IsValid)
      {
        // ogólny komunikat o błędzie
        LabelErreursSaisie.Text = "Veuillez corriger les erreurs de saisie...";
        LabelErreursSaisie.Visible = true;
        return;
      }
      // ukrywanie komunikatu o błędzie
      LabelErreursSaisie.Visible = false;
      // wyświetla się zweryfikowane imię i wiek
      LabelValidation.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
}

W przypadku, gdy przycisk [Valider] ma atrybut CausesValidation ustawiony na True, a walidatory mają atrybut EnableClientScript ustawiony na True, metoda ButtonValider_Click jest wykonywana tylko wtedy, gdy przesłane wartości są prawidłowe. Można zatem zastanawiać się nad znaczeniem kodu znajdującego się od linii 8. Należy pamiętać, że zawsze istnieje możliwość napisania klienta programowego, który wysyła niesprawdzone wartości na stronę [Default.aspx]. Dlatego strona ta musi zawsze ponownie przeprowadzać testy poprawności.

  • wiersz 8: uruchamia wszystkie walidatory na stronie. W przypadku, gdy przycisk [Valider] ma atrybut ustawiony na wartość od CausesValidation do True, odbywa się to automatycznie i nie ma potrzeby powtarzania tego procesu. W tym miejscu występuje redundancja.
  • wiersze 9–15: przypadek, w którym jeden z walidatorów zakończył się niepowodzeniem
  • wiersze 16–19: przypadek, w którym wszystkie walidatory zakończyły się powodzeniem

Oto dwa przykłady wykonania:

  • w [1], przykład wykonania w sytuacji, gdy:
    • przycisk [Valider] ma swoją właściwość zmienioną z CausesValidation na True
    • walidatory mają swoją właściwość zmienioną z EnableClientScript na True

Komunikaty o błędach [2] zostały wyświetlone przez walidatory uruchomione po stronie klienta przez kod JavaScript strony. Nie wysłano komunikatu POST do serwera, co pokazuje etykieta wysłanych elementów [3].

  • W przypadku [4], przykładowe wykonanie w sytuacji, gdy:
    • przycisk [Valider] ma swoją właściwość CausesValidation ustawioną na False
    • walidatory mają swoją właściwość EnableClientScript ustawioną na False

Komunikaty o błędach [5] zostały wyświetlone przez walidatory uruchomione po stronie serwera. Jak pokazuje [6], rzeczywiście nastąpiło przesłanie POST do serwera. W [7] komunikat o błędzie wyświetlany przez metodę [ButtonValider_Click] w przypadku błędów wprowadzenia danych.

  • w [8] przykład uzyskany przy użyciu poprawnych danych. [9,10] pokazuje, że przesłane elementy zostały zatwierdzone. Podczas wielokrotnego testowania należy ustawić właściwość EnableViewState etykiety [LabelValidation] na wartość False, aby komunikat o walidacji nie pozostawał wyświetlony podczas kolejnych uruchomień.

2.4. Zarządzanie danymi o zasięgu aplikacji

Wróćmy do architektury działania strony ASPX:

Klasa strony ASPX jest instancjonowana na początku żądania klienta i niszczona po jego zakończeniu. Nie może więc służyć do przechowywania danych między dwoma żądaniami. Można chcieć przechowywać dwa rodzaje danych:

  • dane współdzielone przez wszystkich użytkowników aplikacji internetowej. Są to zazwyczaj dane tylko do odczytu. Do wdrożenia tego współdzielenia danych wykorzystywane są trzy pliki:
    • [Web.Config]: plik konfiguracyjny aplikacji
    • [Global.asax, Global.asax.cs]: umożliwia zdefiniowanie klasy, zwanej globalną klasą aplikacji, której czas życia pokrywa się z czasem działania aplikacji, a także menedżerów dla niektórych zdarzeń tej samej aplikacji.

Globalna klasa aplikacji pozwala zdefiniować dane, które będą dostępne dla wszystkich żądań wszystkich użytkowników.

  • dane współdzielone przez żądania tego samego klienta. Dane te są przechowywane w obiekcie zwanym sesją. Mówimy wówczas o sesji klienta, aby określić pamięć klienta. Wszystkie żądania danego klienta mają dostęp do tej sesji. Mogą w niej przechowywać i odczytywać informacje

Powyżej przedstawiono rodzaje pamięci, do których ma dostęp strona ASPX:

  • pamięć aplikacji, która w większości przypadków zawiera dane tylko do odczytu i jest dostępna dla wszystkich użytkowników;
  • pamięć konkretnego użytkownika, czyli sesja, która zawiera dane do odczytu i zapisu i jest dostępna dla kolejnych żądań tego samego użytkownika.
  • nie przedstawiono powyżej, istnieje pamięć żądania lub kontekst żądania. Żądanie użytkownika może być przetwarzane przez kilka kolejnych stron ASPX. Kontekst żądania pozwala stronie 1 przekazać informacje do strony 2.

W tym miejscu skupiamy się na danych o zasięgu Application, czyli tych, które są wspólne dla wszystkich użytkowników. Klasę globalną aplikacji można utworzyć w następujący sposób:

  • w [1] dodajemy nowy element do projektu
  • w [2] dodajemy globalną klasę aplikacji
  • w pliku [3] zachowuje się domyślną nazwę [Global.asax] dla nowego elementu
  • w [4] do projektu dodano dwa nowe pliki
  • w [5] wyświetlono kod źródłowy pliku [Global.asax]

<%@ Application Codebehind="Global.asax.cs" Inherits="Intro.Global" Language="C#" %>
  • tag Application zastępuje tag Page, który wcześniej występował dla [Default.aspx]. Określa on klasę aplikacji globalnej
  • Kod zaplecza: określa plik, w którym zdefiniowana jest globalna klasa aplikacji
  • Inherits: określa nazwę tej klasy

Wygenerowana klasa Intro.Global ma następujący wygląd:


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)
    {

    }
  }
}
  • wiersz 5: globalna klasa aplikacji wywodzi się z klasy HttpApplication

Klasa jest generowana wraz ze szkieletami procedur obsługi zdarzeń aplikacji:

  • wiersze 8, 38: obsługują zdarzenia Application_Start (uruchomienie aplikacji) oraz Application_End (zakończenie działania aplikacji w momencie wyłączenia serwera WWW lub zwolnienia aplikacji przez administratora)
  • wiersze 13, 33: obsługują zdarzenia Session_Start (uruchomienie nowej sesji klienta po pojawieniu się nowego klienta lub po wygaśnięciu istniejącej sesji) oraz Session_End (zakończenie sesji klienta – albo jawnie programowo, albo niejawnie poprzez przekroczenie dozwolonego czasu trwania sesji).
  • wiersz 28: obsługuje zdarzenie Application_Error (wystąpienie wyjątku nieobsługiwanego przez kod aplikacji i przekazanie go do serwera)
  • wiersz 18: obsługuje zdarzenie Application_BeginRequest (nadejście nowego żądania).
  • wiersz 23: obsługuje zdarzenie Application_AuhenticateRequest (występuje, gdy użytkownik się uwierzytelnił).

Metoda [Application_Start] jest często wykorzystywana do zainicjowania aplikacji na podstawie informacji zawartych w [Web.Config]. Kod wygenerowany podczas pierwszego utworzenia projektu wygląda następująco:


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

W przypadku naszej obecnej aplikacji ten plik jest zbędny. Jeśli go usuniemy lub zmienimy jego nazwę, aplikacja nadal będzie działać normalnie. Skupimy się na tagach w wierszach 8 i 9:

  • <appsettings> pozwala zdefiniować słownik informacji
  • <connectionStrings> pozwala zdefiniować ciągi połączeń z bazami danych

Rozważmy następujący plik [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>
...

Plik ten może być wykorzystywany przez następującą globalną klasę aplikacji:


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)
    {

    }

  }
}
  • wiersze 8–11: cztery właściwości statyczne P. Ponieważ czas życia klasy Global pokrywa się z czasem życia aplikacji, każde wywołanie aplikacji będzie miało dostęp do tych właściwości P za pomocą składni Global.P.
  • wiersze 17–19: plik [Web.config] jest dostępny za pośrednictwem klasy [System.Configuration.ConfigurationManager]
  • wiersze 17–18: pobiera elementy z tagu <appSettings> z pliku [Web.config] za pomocą atrybutu key.
  • wiersz 19: pobiera elementy z tagu <connectionStrings> z pliku [Web.config] za pomocą atrybutu name.

Statyczne atrybuty w wierszach 8–11 są dostępne z dowolnego obsługi zdarzeń na załadowanych stronach ASPX. Wykorzystujemy je w obsłudze zdarzeń [Page_Load] na stronie [Default.aspx]:


    protected void Page_Load(object sender, EventArgs e)
    {
      // rejestruje się zdarzenie
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
      // pobierane są informacje z globalnej klasy aplikacji
      LabelGlobal.Text = string.Format("Param1={0},Param2={1},ConnString1={2},Erreur={3}", Global.Param1, Global.Param2, Global.ConnString1, Global.Erreur);
}
  • wiersz 6: cztery statyczne atrybuty globalnej klasy aplikacji są wykorzystywane do wypełnienia nowej etykiety na stronie [Default.aspx]

Po uruchomieniu otrzymujemy następujący wynik:

Powyżej widać, że parametry [web.config] zostały poprawnie pobrane. Klasa globalna aplikacji jest odpowiednim miejscem do przechowywania informacji wspólnych dla wszystkich użytkowników.

2.5. Zarządzanie danymi w zakresie sesji

W tym miejscu skupiamy się na sposobie przechowywania informacji w trakcie kolejnych żądań danego użytkownika:

Każdy użytkownik ma własną pamięć, zwaną sesją.

Widzieliśmy już, że globalna klasa aplikacji posiada dwa menedżery do obsługi zdarzeń:

  • Session_Start: początek sesji
  • Session_end: koniec sesji

Mechanizm sesji działa w następujący sposób:

  • podczas pierwszego żądania użytkownika serwer WWW tworzy token sesji, który przypisuje użytkownikowi. Token ten jest unikalnym ciągiem znaków dla każdego użytkownika. Serwer wysyła go w odpowiedzi na pierwsze żądanie użytkownika.
  • W przypadku kolejnych żądań użytkownik (przeglądarka internetowa) dołącza do swojego żądania przypisany mu token sesji. Dzięki temu serwer WWW jest w stanie go rozpoznać.
  • Sesja ma określony czas trwania. Gdy serwer WWW otrzymuje żądanie od użytkownika, oblicza czas, jaki upłynął od poprzedniego żądania. Jeśli czas ten przekracza czas trwania sesji, dla użytkownika tworzona jest nowa sesja. Dane z poprzedniej sesji zostają utracone. W przypadku serwera internetowego IIS (Internet Information Server) firmy Microsoft sesje trwają domyślnie 20 minut. Wartość tę może zmienić administrator serwera internetowego.
  • Serwer WWW rozpoznaje, że ma do czynienia z pierwszym żądaniem użytkownika, ponieważ żądanie to nie zawiera tokenu sesji. Jest to jedyne żądanie.

Każda strona typu ASP.NET ma dostęp do sesji użytkownika poprzez właściwość Session strony, typu [System.Web.SessionState.HttpSessionState]. Będziemy korzystać z następujących właściwości P i metod M klasy HttpSessionState:

Nazwa
Typ
Rola
Element[String clé]
P
Sesja może być zbudowana jako słownik. Item[clé] to element sesji identyfikowany przez clé. Zamiast zapisywać [HttpSessionState].Item[clé], można również zapisać [HttpSessionState].[clé].
Wyczyść
M
wyczyści słownik sesji
Anuluj
M
kończy sesję. Sesja przestaje wtedy obowiązywać. Nowa sesja rozpocznie się wraz z kolejnym żądaniem użytkownika.

Jako przykład pamięci użytkownika policzymy, ile razy użytkownik kliknie przycisk [Valider]. Aby uzyskać ten wynik, należy utrzymywać licznik w sesji użytkownika.

Strona [Default.aspx] zmienia się w następujący sposób:

Globalna klasa aplikacji [Global.asax.cs] zmienia się w następujący sposób:


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)
    {
      // licznik żądań
      Session["nbRequêtes"] = 0;
    }

  }
}

W wierszu 19 wykorzystuje się sesję użytkownika do przechowywania licznika żądań oznaczonego kluczem „nbRequêtes”. Licznik ten jest aktualizowany przez menedżera [ButtonValider_Click] strony [Default.aspx]:


using System;

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

    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // rejestruje się zdarzenie
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
      // wyświetla się opublikowana nazwa i wiek
      LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
      // liczba zapytań
      Session["nbRequêtes"] = (int)Session["nbRequêtes"] + 1;
      LabelNbRequetes.Text = Session["nbRequêtes"].ToString();
      // czy strona jest poprawna?
      Page.Validate();
      if (!Page.IsValid)
      {
...
      }
...
    }
  }
}
  • wiersz 16: zwiększamy licznik zapytań
  • wiersz 17: licznik jest wyświetlany na stronie

Oto przykładowe wykonanie:

2.6. Obsługa GET / POST podczas ładowania strony

Wspomnieliśmy, że istnieją dwa rodzaje żądań kierowanych do strony ASPX:

  • początkowe żądanie przeglądarki wysłane za pomocą polecenia HTTP lub GET. Serwer odpowiada, wysyłając żądaną stronę. Załóżmy, że ta strona jest formularzem o nazwie c.a.d oraz że na wysłanej stronie ASPX znajduje się tag <form runat="server"...>.
  • Kolejne żądania wysyłane przez przeglądarkę w odpowiedzi na określone działania użytkownika w formularzu. Przeglądarka wysyła wówczas żądanie o numerze HTTP oraz POST.

Niezależnie od tego, czy chodzi o żądanie GET, czy o żądanie POST, wykonywana jest metoda [Page_Load]. W przypadku GET metoda ta jest zazwyczaj wykorzystywana do zainicjowania strony wysyłanej do przeglądarki klienta. Następnie, dzięki mechanizmowi VIEWSTATE, strona pozostaje zainicjowana i jest modyfikowana wyłącznie przez procedury obsługi zdarzeń, które wywołują POST. Nie ma potrzeby ponownego inicjowania strony w Page_Load. Stąd wynika potrzeba, aby ta metoda rozróżniała, czy żądanie klienta jest typu GET, czy POST.

Przyjrzyjmy się poniższemu przykładowi. Do strony [Default.aspx] dodajemy listę rozwijaną. Zawartość tej listy zostanie zdefiniowana w menedżerze Page_Load żądania GET:

Lista rozwijana jest zadeklarowana w [Default.aspx.designer.cs] w następujący sposób:


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

Będziemy korzystać z następujących metod M i właściwości P klasy [DropDownList]:

Nazwa
Typ
Rola
Elementy
P
kolekcja typu ListItemCollection zawierająca elementy typu ListItem z listy rozwijanej
SelectedIndex
P
indeks (zaczynający się od 0) elementu wybranego z listy rozwijanej w momencie wysłania formularza
SelectedItem
P
element typu ListItem wybrany z listy rozwijanej w momencie wysłania formularza
SelectedValue
P
wartość typu string elementu typu ListItem wybranego z listy rozwijanej podczas wysyłania formularza. Wkrótce zdefiniujemy pojęcie wartości.

Klasa ListItem elementów listy rozwijanej służy do generowania tagów <option> z tagu HTML <select>:

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

W tagu <option>

  • textei to tekst wyświetlany na liście rozwijanej
  • vali to wartość wysyłana przez przeglądarkę, jeśli textei to tekst wybrany z listy rozwijanej

Każda opcja może zostać wygenerowana przez obiekt LisItem utworzony za pomocą konstruktora ListItem(string tekst, string wartość).

W [Default.aspx.cs] kod obsługi [Page_Load] zmienia się w następujący sposób:


    protected void Page_Load(object sender, EventArgs e)
    {
      // rejestruje się zdarzenie
      ...
      // pobierane są informacje z globalnej klasy aplikacji
      ...
      // inicjalizacja listy rozwijanej z nazwami wyłącznie podczas pierwszego wywołania GET
      if (!IsPostBack)
      {
        for (int i = 0; i < 3; i++)
        {
          DropDownListNoms.Items.Add(new ListItem("nom"+i,i.ToString()));
        }
      }
}
  • wiersz 8: klasa Page posiada atrybut IsPostBack typu logicznego. W rzeczywistości oznacza to, że żądanie użytkownika jest obiektem typu POST. Wiersze 10–13 są zatem wykonywane wyłącznie na początkowym GET klienta.
  • wiersz 12: do listy [DropDownListNoms] dodawany jest element typu ListItem (string tekst, string wartość). Tekst wyświetlany dla (i+1)-ego elementu będzie brzmiał nomi, a wartość przesłana dla tego elementu, jeśli zostanie on wybrany, będzie wynosić i.

Procedura [ButtonValider_Click] została zmodyfikowana w celu wyświetlenia wartości przesłanej przez listę rozwijaną:


    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // rejestruje się zdarzenie
...
      // wyświetla się wartości przesłane
      LabelPost.Text = string.Format("nom={0}, age={1}, combo={2}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim(), DropDownListNoms.SelectedValue);
      // liczba zapytań
...
}

W wierszu 6 wartość wyświetlana dla listy [DropDownListNoms] jest uzyskiwana na podstawie właściwości SelectedValue tej listy. Oto przykładowe wykonanie:

  • w [1] – zawartość listy rozwijanej po początkowym GET i tuż przed pierwszym POST
  • w [2] – strona po pierwszym POST.
  • w [3] – wartość przesłana dla listy rozwijanej. Odpowiada atrybutowi value elementu ListItem wybranego z listy.
  • w [4] – lista rozwijana. Zawiera te same elementy, co po początkowym GET. Wyjaśnia to mechanizm działania VIEWSTATE.

Aby zrozumieć interakcję między pozycją VIEWSTATE z listy DropDownListNoms a testem if (! IsPostBack) menedżera Page_Load z [Default.aspx], czytelnik powinien powtórzyć poprzedni test z następującymi ustawieniami:

Przypadek
DropDownListNoms.EnableViewState
test if(! IsPostBack) w Page_Load z [Default.aspx]
1
true
présent
2
false
présent
3
true
absent
4
false
absent

Różne testy dają następujące wyniki:

  1. tak jest w przypadku przedstawionym powyżej
  2. lista jest wypełniana podczas początkowego GET, ale nie podczas kolejnych POST. Ponieważ EnableViewState zwraca wartość fałszywą, lista jest pusta po każdym POST
  3. lista jest wypełniona zarówno po początkowym GET, jak i podczas kolejnych POST. Ponieważ EnableViewState jest równy vrai, po początkowym GET mamy 3 nazwy, po pierwszym POST – 6 nazw, po drugim POST – 9 nazw, ...
  4. Lista jest uzupełniana zarówno po początkowym GET, jak i podczas kolejnych POST. Ponieważ EnableViewState jest równy faux, lista jest uzupełniana tylko o 3 nazwy przy każdym zapytaniu, niezależnie od tego, czy jest to początkowe zapytanie GET, czy kolejne zapytania POST. Obserwujemy zachowanie podobne do przypadku 1. Istnieją zatem dwa sposoby uzyskania tego samego wyniku.

2.7. Obsługa VIEWSTATE elementów strony ASPX

Domyślnie wszystkie elementy strony ASPX mają właściwości od EnableViewState do True. Za każdym razem, gdy strona ASPX jest wysyłana do przeglądarki klienta, zawiera ona ukryte pole o nazwie __VIEWSTATE, którego wartością jest ciąg znaków kodujący zbiór wartości komponentów, których właściwości mają wartości od EnableViewState do True. Aby zminimalizować rozmiar tego ciągu znaków, można spróbować zmniejszyć liczbę komponentów, których właściwości mają wartości od EnableViewState do True.

Przypomnijmy, w jaki sposób komponenty strony o numerze ASPX uzyskują swoje wartości po zakończeniu operacji o numerze POST:

  1. strona o identyfikatorze ASPX jest instancjonowana. Komponenty są inicjowane przy użyciu ich wartości projektowych.
  2. wartość __VIEWSTATE przesłana przez przeglądarkę jest wykorzystywana do przypisania komponentom wartości, jakie miały one w momencie wysłania strony ASPX do przeglądarki poprzednim razem.
  3. wartości przesłane przez przeglądarkę są przypisywane do komponentów
  4. uruchamiane są procedury obsługi zdarzeń. Mogą one modyfikować wartości niektórych komponentów.

Z tej sekwencji wynika, że komponenty, które:

  • mają przesłaną wartość
  • mają swoje wartości zmienione przez procedurę obsługi zdarzenia

mogą mieć właściwość o wartości od EnableViewState do Faux, ponieważ ich wartość VIEWSTATE (krok 2) zostanie zmieniona w jednym z kroków 3 lub 4.

Lista komponentów naszej strony jest dostępna w [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;
    }
}

Wartość właściwości EnableViewState tych komponentów może wyglądać następująco:

Composant
Wartość przesłana
EnableViewState
Pourquoi
TextBoxNom
wartość wprowadzona w TextBox
False
wartość komponentu została zaksięgowana
TextBoxAge
to samo
  
RequiredFieldValidatorNom
brak
False
brak pojęcia wartości komponentu
RequiredFieldValidatorAge
to samo
  
RangeValidatorAge
to samo
  
LabelPost
brak
False
wartość jest ustalana przez procedurę obsługi zdarzenia
LabelValidation
to samo
  
LabelErreursSaisie
to samo
  
LabelGlobal
to samo
  
LabelNbRequetes
to samo
  
DropDownListNoms
„wartość” wybranego elementu
True
chcemy zachować zawartość listy na przestrzeni kolejnych zapytań bez konieczności jej ponownego generowania
ListBoxEvts
„value” wybranego elementu
False
zawartość listy jest generowana przez moduł obsługi zdarzeń
ButtonValider
nazwa przycisku
False
komponent zachowuje swoją wartość projektową

2.8. Przekierowanie z jednej strony na drugą

Do tej pory operacje GET i POST zawsze zwracały tę samą stronę [Default.aspx]. Rozważymy przypadek, w którym żądanie jest przetwarzane przez dwie kolejne strony ASPX, [Default.aspx] i [Page1.aspx], a to właśnie ta ostatnia jest zwracana klientowi. Ponadto zobaczymy, w jaki sposób strona [Default.aspx] może przekazywać informacje do strony [Page1.aspx] za pośrednictwem pamięci, którą nazwiemy pamięcią żądania.

Tworzymy stronę [Page1.aspx]:

  • w [1] dodajemy nowy element do projektu
  • w pliku [2] dodajemy element o nazwie [Page1.aspx] [3]
  • w [4] dodana strona
  • w [5], strona po wygenerowaniu

Kod źródłowy pliku [Page1.aspx] wygląda następująco:


<%@ 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>
  • wiersz 13: etykieta, która posłuży do wyświetlenia informacji przekazanej przez stronę [Default.aspx]
  • wiersz 15: link HTML prowadzący do strony [Default.aspx]. Gdy użytkownik kliknie ten link, przeglądarka wysyła żądanie dotyczące strony [Default.aspx] z operacją GET. Strona [Default.aspx] jest wówczas ładowana tak, jakby użytkownik wpisał jej adres URL bezpośrednio w przeglądarce.

Strona [Default.aspx] zostaje wzbogacona o nowy komponent typu LinkButton:

Kod źródłowy tego nowego komponentu jest następujący:


  <asp:LinkButton ID="LinkButtonToPage1" runat="server" CausesValidation="False" 
    EnableViewState="False" onclick="LinkButtonToPage1_Click">Forward vers Page1</asp:LinkButton>
  • CausesValidation="False": kliknięcie linku spowoduje przejście z POST do [Defaul.aspx]. Komponent [LinkButton] zachowuje się tak samo jak komponent [Button]. W tym przypadku nie chcemy, aby kliknięcie linku uruchamiało walidatory.
  • EnableViewState="False": nie ma potrzeby zachowywania stanu linku w kolejnych żądaniach. Zachowuje on swoje wartości projektowe.
  • onclick="LinkButtonToPage1_Click": nazwa metody, która w [Defaul.aspx.cs] obsługuje zdarzenie Click w komponencie LinkButtonToPage1.

Kod obsługi zdarzenia LinkButtonToPage1_Click wygląda następująco:


  // do Strony1
  protected void LinkButtonToPage1_Click(object sender, EventArgs e)
  {
    // umieszczamy informacje w kontekście
    Context.Items["msg1"] = "Message de Default.aspx pour Page1";
    // przekazujemy żądanie do Strony1
    Server.Transfer("Page1.aspx",true);
}

W wierszu 7 żądanie jest przekazywane do strony [Page1.aspx] za pomocą metody [Server.Transfer]. Drugi parametr metody, którym jest true, wskazuje, że należy przekazać do [Page1.aspx] wszystkie informacje, które zostały wysłane do [Default.aspx] podczas operacji POST. Dzięki temu na przykład metoda [Page1.aspx] ma dostęp do wartości przesłanych za pośrednictwem kolekcji o nazwie Request.Form. W wierszu 5 wykorzystano tzw. kontekst żądania. Dostęp do niego uzyskuje się poprzez właściwość Context klasy Page. Kontekst ten może służyć jako pamięć między różnymi stronami przetwarzającymi to samo żądanie, w tym przypadku [Default.aspx] i [Page1.aspx]. W tym celu wykorzystuje się słownik Items.

Gdy [Page1.aspx] jest ładowana przez operację Server.Transfer("Page1.aspx",true), wszystko przebiega tak, jakby [Page1.aspx] zostało wywołane przez GET z przeglądarki. Procedura Page_Load dla [Page1.aspx] jest wykonywana normalnie. Wykorzystamy to do wyświetlenia komunikatu umieszczonego przez [Default.aspx] w kontekście żądania:


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;
    }
  }
}

W wierszu 9 komunikat umieszczony przez [Default.aspx] w kontekście zapytania jest wyświetlany w Label1.

Oto przykładowe wykonanie:

  • na stronie [Default.aspx] [1] klikamy link [2], który prowadzi nas do strony Page1
  • w [3] wyświetla się strona Page1
  • po przejściu do [4] wyświetla się wiadomość utworzona w [Default.aspx] i wyświetlona przez [Page1.aspx]
  • w [5] strona URL wyświetlana w przeglądarce to strona [Default.aspx]

2.9. Przekierowanie jednej strony na inną

Przedstawiamy tutaj inną technikę, funkcjonalnie zbliżoną do poprzedniej: gdy użytkownik żąda strony [Default.aspx] poprzez stronę POST, otrzymuje w odpowiedzi inną stronę [Page2.aspx]. W poprzedniej metodzie żądanie użytkownika było przetwarzane kolejno przez dwie strony: [Default.aspx] i [Page1.aspx]. W przedstawianej teraz metodzie przekierowania strony występują dwa odrębne żądania przeglądarki:

  • na [1], przeglądarka wysyła żądanie POST do strony [Default.aspx]. Ta strona przetwarza żądanie i wysyła do przeglądarki tzw. odpowiedź przekierowującą. Odpowiedź ta jest prostym strumieniem HTTP (wiersze tekstu), nakazującym przeglądarce przekierowanie się na inny adres URL [Page2.aspx]. [Default.aspx] nie wysyła strumienia HTML w tej pierwszej odpowiedzi.
  • W przypadku [2] przeglądarka wysyła żądanie GET do strony [Page2.aspx]. Strona ta jest następnie wysyłana w odpowiedzi do przeglądarki.
  • Jeśli strona [Default.aspx] chce przekazać informacje do strony [Page2.aspx], może to zrobić za pośrednictwem sesji użytkownika. W przeciwieństwie do poprzedniej metody kontekst żądania nie ma tutaj zastosowania, ponieważ mamy do czynienia z dwoma odrębnymi żądaniami, a zatem z dwoma odrębnymi kontekstami. Należy zatem wykorzystać sesję użytkownika, aby umożliwić komunikację między stronami.

Podobnie jak w przypadku strony [Page1.aspx], dodajemy do projektu stronę [Page2.aspx]:

  • w [1] strona [Page2.aspx] została dodana do projektu
  • w [2] wygląd strony [Page2.aspx]
  • w [3], do strony [Default.aspx] dodajemy komponent LinkButton [4], który przekieruje użytkownika do [Page2.aspx].

Kod źródłowy pliku [Page2.aspx] jest analogiczny do kodu pliku [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>

W pliku [Default.aspx] dodanie komponentu LinkButton spowodowało wygenerowanie następującego kodu źródłowego:


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

To menedżer [LinkButtonToPage2_Click] zapewnia przekierowanie do [Page2.aspx]. Jego kod w pliku [Defaul.aspx.cs] wygląda następująco:


    protected void LinkButtonToPage2_Click(object sender, EventArgs e)
    {
      // umieszczamy komunikat w sesji
      Session["msg2"] = "Message de [Default.aspx] pour [Page2.aspx]";
      // przekierowujemy klienta do [Page2.aspx]
      Response.Redirect("Page2.aspx");
}
  • wiersz 4: umieszczamy komunikat w sesji użytkownika
  • wiersz 5: obiekt Response jest właściwością każdej strony ASPX. Przedstawia ona odpowiedź wysłaną do klienta. Posiada metodę Redirect, która sprawia, że odpowiedź wysłana do klienta będzie poleceniem przekierowania HTTP.

Gdy przeglądarka otrzyma polecenie przekierowania do [Page2.aspx], wykona na tej stronie operację GET. Na tej stronie zostanie wykonana metoda [Page_Load]. Wykorzystamy ją do pobrania komunikatu umieszczonego przez [Default.aspx] w sesji i wyświetlenia go. Kod [Page2.aspx.cs] wygląda następująco:


using System;

namespace Intro
{
  public partial class Page2 : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      // wyświetla się komunikat zapisany w sesji przez [Default.aspx]
      Label1.Text = Session["msg2"] as string;
    }
  }
}

Po uruchomieniu otrzymujemy następujące wyniki:

  • w [1] klikamy link przekierowujący do [Default.aspx]. Następuje przekierowanie POST na stronę [Default.aspx]
  • w [2] przeglądarka została przekierowana do [Page2.aspx]. Widać to po adresie URL wyświetlanym przez przeglądarkę. W poprzedniej metodzie tym adresem URL był [Default.aspx], ponieważ jedyne żądanie wysłane przez przeglądarkę dotyczyło właśnie tego adresu. W tym przypadku najpierw wysłano żądanie POST do [Default.aspx], a następnie, bez wiedzy użytkownika, drugie żądanie GET do [Page2.aspx].
  • W pliku [3] widać, że plik [Page2.aspx] poprawnie pobrał komunikat umieszczony przez plik [Default.aspx] w sesji.

2.10. Conclusion

Na podstawie kilku przykładów przedstawiliśmy pojęcia związane z ASP.NET, które będą nam przydatne w dalszej części dokumentu. To wprowadzenie nie pozwala jednak w pełni zrozumieć subtelności komunikacji klient–serwer w aplikacji internetowej. W tym celu warto zapoznać się z:

  • Programowanie ASP.NET [Développement WEB avec ASP.NET 1.1 ]