Skip to content

7. Komponenty serwerowe ASP – 1

7.1. Introduction

W niniejszym rozdziale opisujemy technologię zalecaną w ASP.NET do tworzenia interfejsu użytkownika. Wiemy, że przetwarzanie strony .aspx przez serwer internetowy przebiega w dwóch wyraźnie odrębnych fazach:

  1. najpierw następuje wykonanie kontrolera strony. Składa się on z kodu umieszczonego albo w samej stronie .aspx (rozwiązanie WebMatrix), albo w osobnym pliku (rozwiązanie Visual Studio.NET).
  2. Następnie wykonywany jest kod odpowiedzialny za prezentację strony .aspx, który jest przekształcany w kod HTML wysyłany do klienta.

Image

ASP.NET oferuje trzy biblioteki tagów do pisania kodu prezentacyjnego strony:

  1. klasyczne tagi HTML. Z nich korzystaliśmy do tej pory.
  2. tagi serwerowe HTML
  3. tagi webforms

Niezależnie od używanej biblioteki znaczników rola kontrolera strony pozostaje niezmienna. Musi on obliczać wartości parametrów dynamicznych pojawiających się w kodzie prezentacji. Do tej pory parametry te były proste: były to obiekty typu [String]. Jeśli więc w kodzie prezentacji występuje znacznik <%=nazwa%>:

  • kontroler strony deklaruje zmienną [nom] typu [String] i oblicza jej wartość
  • gdy kontroler strony zakończy swoją pracę, a kod prezentacji zostanie wykonany w celu wygenerowania odpowiedzi HTML, tag <%=nazwa%> zostaje zastąpiony wartością obliczoną przez kod kontrolera

Wiadomo, że podział na kontrolkę i prezentację jest arbitralny i że na tej samej stronie można mieszać kod kontrolki z kodem prezentacji. Wyjaśniliśmy, dlaczego ta metoda jest odradzana, i będziemy nadal przestrzegać podziału na kontrolkę i prezentację.

Tagi [HTML serveur] i [WebForms] umożliwiają wprowadzenie do kodu prezentacji obiektów bardziej złożonych niż prosty obiekt [String]. Czasami ma to rzeczywiste znaczenie. Weźmy na przykład formularz z listą. Lista ta powinna być wyświetlana klientowi za pomocą kodu HTML, który wygląda następująco:

<select name="uneListe" size="3">
    <option value="opt1">option1</option>
    <option value="opt2">option2</option>
    <option value="opt3" selected>option3</option>
</select>

Zawartość listy oraz opcja do wyboru są elementami dynamicznymi, dlatego muszą być generowane przez kontroler strony. Napotkaliśmy już ten problem i rozwiązaliśmy go, umieszczając w kodzie prezentacji tag

<%=uneListeHTML%>

Ten tag zostanie zastąpiony wartością [String] zmiennej [uneListeHTML]. Wartość ta, obliczona przez kontroler, powinna odpowiadać kodowi HTML z listy, c.a.d. „<select name=..>...</select>”. Nie jest to szczególnie trudne do wykonania i wydaje się eleganckim rozwiązaniem, które pozwala uniknąć umieszczania kodu generującego bezpośrednio w części prezentacyjnej strony. W tym przypadku należałoby wstawić do niej pętlę z testami, co znacznie by ją „zanieczyściło”. Niemniej jednak ta metoda ma jedną wadę. Podział strony na część sterującą i prezentacyjną służy również wyznaczeniu granic dwóch obszarów kompetencji:

  • programisty .NET, który zajmuje się kontrolerem strony
  • obszar kompetencji grafika, który zajmuje się częścią prezentacyjną strony

W tym przypadku widać, że generowanie kodu HTML listy zostało przeniesione do kontrolera. Grafik może chcieć wprowadzić zmiany w tym kodzie HTML, aby zmodyfikować „wizualny” wygląd listy. Będzie zmuszony do pracy w części [contrôleur], a tym samym do wyjścia poza swój obszar kompetencji, co wiąże się z ryzykiem nieumyślnego wprowadzenia błędów do kodu.

Biblioteki tagów serwerowych rozwiązują ten problem. Oferują one obiekt reprezentujący listę HTML. W ten sposób biblioteka [WebForms] udostępnia następujący tag:

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

Ten tag reprezentuje obiekt typu [ListBox], którym może manipulować kontroler strony. Obiekt ten posiada właściwości służące do reprezentowania różnych opcji z listy HTML oraz wskazania wybranej opcji. Kontroler strony nada zatem odpowiednie wartości tym właściwościom. Podczas wykonywania części prezentacyjnej zostanie wyświetlony tag

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

zostanie zastąpiony kodem HTML reprezentującym obiekty [uneListe] i c.a.d. kod „<select ..>...</select>”. Na razie nie ma zasadniczej różnicy w stosunku do poprzedniej metody, poza tym, że jest to sposób kodowania zorientowany obiektowo, co jest interesujące. Wróćmy do naszego grafika, który musi zmienić „wygląd” listy. Tagi serwerowe posiadają atrybuty stylów (BackColor, Bordercolor, BorderWidth, ...), które pozwalają określić wygląd odpowiadającego im obiektu HTML. W ten sposób możemy zapisać:


                <asp:ListBox id="ListBox1" runat="server" BackColor="#ffff99"></asp:ListBox></P>

Zaletą tego rozwiązania jest to, że grafik pozostaje w kodzie prezentacyjnym, wprowadzając te zmiany. Stanowi to niewątpliwą przewagę w porównaniu z poprzednią metodą. Biblioteki znaczników serwerowych ułatwiają zatem tworzenie części prezentacyjnej stron, którą w poprzednich rozdziałach nazwaliśmy interfejsem użytkownika. Celem niniejszego rozdziału jest ich przedstawienie. Zobaczymy, że biblioteki te oferują czasami złożone obiekty, takie jak kalendarze lub tabele powiązane ze źródłami danych. Są one rozszerzalne, c.a.d, co oznacza, że użytkownik może stworzyć własną bibliotekę tagów. Może w ten sposób stworzyć tag generujący baner na stronie. Wszystkie strony korzystające z tego tagu będą wówczas miały ten sam baner.

Generowanie kodu HTML dla danego tagu dostosowuje się do typu przeglądarki klienta. Gdy klient wysyła żądanie do serwera WWW, wśród swoich nagłówków HTTP przesyła nagłówek [User-Agent: xx], w którym [xx] identyfikuje klienta. Oto przykład:

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

Dzięki tej informacji serwer WWW może poznać możliwości klienta, w szczególności typ kodu HTML, który potrafi obsłużyć. Z biegiem czasu pojawiło się bowiem kilka wersji języka HTML. Nowsze przeglądarki obsługują najnowsze wersje tego języka, czego nie potrafią starsze przeglądarki. W zależności od nagłówka HTTP [User-Agent:], który klient mu przesłał, serwer wyśle mu wersję HTML, którą będzie w stanie zrozumieć. Jest to interesujący i przydatny pomysł, ponieważ dzięki temu programista nie musi martwić się o typ przeglądarki klienta korzystającego z jego aplikacji.

Wreszcie zaawansowane narzędzia typu IDE, takie jak Visual Studio.NET, WebMatrix itp., umożliwiają projektowanie interfejsu internetowego w stylu „Windows”. Narzędzia te, choć nie są niezbędne, stanowią jednak znaczącą pomoc dla programisty. Programista projektuje interfejs internetowy za pomocą komponentów graficznych, które umieszcza na tym interfejsie. Ma bezpośredni dostęp do właściwości każdego z komponentów interfejsu, które może w ten sposób konfigurować według własnego uznania. Właściwości te zostaną przekształcone w kodzie HTML odpowiedzialnym za prezentację interfejsu na atrybuty tagu <asp:> danego komponentu. Korzyścią dla programisty jest to, że nie musi on zapamiętywać ani listy, ani składni atrybutów każdego tagu. Jest to znacząca zaleta, gdy nie znamy dokładnie bibliotek tagów serwerowych oferowanych przez ASP.NET. Po opanowaniu tej składni niektórzy programiści mogą preferować bezpośrednie kodowanie tagów w kodzie prezentacyjnym strony, pomijając etap projektowania graficznego. W takiej sytuacji IDE nie jest już potrzebny. Wystarczy zwykły edytor tekstu. W zależności od sposobu pracy nacisk kładzie się wówczas na komponenty (korzystanie z IDE) lub tagi (korzystanie z edytora tekstu). Te dwa terminy są równoważne. Komponent to obiekt, którym będzie manipulował kod sterujący strony. IDE zapewnia nam dostęp do jego właściwości na etapie projektowania. Wartości nadane tym właściwościom są natychmiast przekształcane na atrybuty tagu komponentu w kodzie prezentacji. W fazie wykonywania kod sterujący stroną będzie manipulował komponentem i przypisywał wartości do niektórych jego właściwości. Kod prezentacyjny wygeneruje kod komponentu HTML, wykorzystując z jednej strony atrybuty ustalone podczas projektowania dla odpowiedniego tagu serwerowego, a z drugiej strony wartości właściwości komponentu obliczone przez kod sterujący.

7.2. Kontekst wykonywania przykładów

Zilustrujemy projektowanie interfejsów internetowych opartych na komponentach serwerowych za pomocą programów, których kontekst wykonania będzie w większości przypadków następujący:

  1. aplikacja internetowa będzie składać się z jednej strony P zawierającej formularz F,
  2. klient wyśle swoje pierwsze żądanie bezpośrednio do tej strony P. Będzie to polegało na wpisaniu adresu URL strony P w przeglądarce. Zostanie więc wysłane żądanie GET pod tym adresem URL P. Serwer dostarczy stronę P, a tym samym zawarty na niej formularz F,
  3. użytkownik wypełni go i wyśle, c.a.d. Wykonuje on czynność, która zmusi przeglądarkę do wysłania formularza F na serwer. Operacja przeglądarki o numerze POST będzie nadal skierowana do strony P. Serwer ponownie wyświetli stronę P wraz z formularzem F, którego zawartość mogła ulec zmianie w wyniku działania użytkownika.
  4. Następnie powtórzą się etapy 2 i 3.

Jest to bardzo specyficzny proces wykonania, poza którym niektóre koncepcje przedstawione poniżej przestają działać. Nie znajdujemy się już w kontekście architektury MVC, w której aplikacja wielostronicowa jest kontrolowana przez konkretną stronę, którą nazwaliśmy „kontrolerem aplikacji”. W tego typu architekturze formularze POST są kierowane do kontrolera, a nie do samych formularzy. Zobaczymy jednak, że utworzenie formularza z komponentami serwerowymi oznacza, że formularz ten jest wysyłany do samego siebie.

7.3. Komponent Label

7.3.1. Zastosowanie

Tag <asp:label> pozwala na wstawienie dynamicznego tekstu do kodu prezentacyjnego strony. Nie różni się on zatem niczym od używanego dotychczas tagu <%=zmienna%>. Analiza tego pierwszego tagu pozwoli nam poznać mechanizm działania tagów serwerowych. Tworzymy stronę zawierającą część kontrolną [form1.aspx.vb] oraz część prezentacyjną [form1.aspx]. Chodzi o wyświetlenie godziny:

Image

Problem ten został już omówiony w rozdziale 2 i zachęcamy czytelnika do zapoznania się z nim, jeśli chce dowiedzieć się, w jaki sposób został rozwiązany. Kod prezentacji [form1.aspx] wygląda następująco:


<%@ page src="form1.aspx.vb" inherits="form1" AutoEventWireup="false" %>
<HTML>
    <HEAD>
        <title>Webforms</title>
    </HEAD>
    <body>
        <asp:Label Runat="server" ID="lblHeure" />
    </body>
</HTML>

Wprowadzamy tag <asp:label>. W bibliotekach tagów atrybut [runat="server"] jest obowiązkowy. Atrybut ID identyfikuje komponent. Kontroler musi odwołać się do niego za pomocą tego identyfikatora. Kod kontrolera [form1.aspx.vb] wygląda następująco:


Imports System.Web.UI.WebControls

Public Class form1
    Inherits System.Web.UI.Page

    Protected lblHeure As Label

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Init
        ' zapisuje bieżące zapytanie w pliku request.txt w folderze strony
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub

    Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' umieszcza godzinę w pliku lblHeure
        lblHeure.Text = "Il est " + Date.Now.ToString("T")
    End Sub

End Class

Kontroler musi przypisać wartość do obiektu [lblHeure] typu [System.Web.UI.WebControls.Label]. Wszystkie obiekty wyświetlane przez tagi <asp:> należą do przestrzeni nazw [System.Web.UI.WebControls]. Można więc systematycznie zaimportować tę przestrzeń nazw:


Imports System.Web.UI.WebControls

Obiekt [Label] posiada różne właściwości, w tym właściwość [Text], która reprezentuje tekst, który zostanie wyświetlony przez odpowiedni tag <asp:label>. W tym przypadku do tej właściwości umieszczamy aktualną godzinę. Robimy to w procedurze [Form_Load] kontrolera, która jest zawsze wykonywana. W procedurze [Form_Init], która również jest zawsze wykonywana, ale przed procedurą [Form_Load], zapisujemy żądanie klienta w pliku [request.txt] znajdującym się w folderze aplikacji. Będziemy mieli okazję przeanalizować ten plik, aby zrozumieć niektóre aspekty działania stron wykorzystujących znaczniki serwerowe.

Obiekt [Label] posiada wiele właściwości, metod i zdarzeń. Zachęcamy czytelnika do zapoznania się z dokumentacją dotyczącą klasy [Label], aby je poznać. Tak będzie również w dalszej części. Dla każdego tagu przedstawiamy tylko te kilka właściwości, których potrzebujemy.

7.3.2. Testy

Umieszczamy pliki (form1.aspx, form1.aspx.vb) w folderze <application-path> i uruchamiamy Cassini z parametrami (<application-path>,/form1). Następnie wywołujemy adres URL [http://localhost/form1/form1.aspx]. Otrzymujemy następujący wynik:

Image

Kod HTML otrzymany przez przeglądarkę wygląda następująco:

<HTML>
    <HEAD>
        <title>Webforms</title>
    </HEAD>
    <body>
        <span id="lblHeure">Il est 19:39:37</span>
    </body>
</HTML>

Widać, że tag serwera


        <asp:Label Runat="server" ID="lblHeure" />

został przekształcony w następujący kod HTML:

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

To właśnie właściwość [Text] obiektu [lblHeure] została umieszczona pomiędzy tagami <span> i </span>. Żądanie wysłane przez klienta i zapisane w [request.txt] ma następującą postać:

GET /form1/form1.aspx HTTP/1.1
Cache-Control: max-age=0
Connection: keep-alive
Keep-Alive: 300
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0,1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

Wszystko jak zwykle.

7.3.3. Tworzenie aplikacji za pomocą WebMatrix

Kod prezentacji strony [form1.aspx] stworzyliśmy ręcznie. Metodę tę można zastosować, jeśli znamy znaczniki. Wystarczy wtedy prosty edytor tekstu, aby stworzyć interfejs użytkownika. Na początku często potrzebne jest narzędzie do projektowania graficznego połączone z automatycznym generowaniem kodu, ponieważ nie znamy jeszcze składni potrzebnych znaczników. Teraz tworzymy tę samą aplikację za pomocą narzędzia WebMatrix. Po uruchomieniu WebMatrix wybieramy opcję [File/New File]:

Image

Tworzymy stronę o nazwie ASP.NET o nazwie [form2.aspx]. Po zatwierdzeniu poprzedniego kreatora pojawia się okno projektowania strony [form2.aspx]:

Image

Image

Przypominamy, że WebMatrix umieszcza kod sterujący strony oraz kod prezentacji w tym samym pliku, w tym przypadku [form2.aspx]. Zakładka [All] przedstawia zawartość tego pliku tekstowego. Już teraz widać, że nie jest on pusty:

Image

Wadą tego typu narzędzi jest to, że często generują one zbędny kod. Tak jest w tym przypadku, gdzie plik WebMatrix wygenerował tag <form>, podczas gdy nie zamierzamy tworzyć formularza... Ponadto można zauważyć, że w dokumencie brakuje tagu <title>. Natychmiast rozwiązujemy te dwa problemy, uzyskując następującą nową wersję:

Image

To, co nazywamy kodem sterującym, zostanie umieszczone pomiędzy tagami <script> i </script>, co zapewnia przynajmniej wizualne oddzielenie dwóch rodzajów kodu: sterującego i prezentacyjnego. Wracamy do zakładki [Design], aby zaprojektować nasz interfejs. Lista komponentów jest dostępna w oknie narzędzi po lewej stronie okna projektowego:

Image

Okno narzędzi zapewnia dostęp do dwóch rodzajów komponentów:

  • komponenty [WebControls], które przekładają się na tagi <asp:>
  • komponenty [HTML Elements], które przekładają się na klasyczne tagi HTML. Można jednak dodać do atrybutów tagu HTML atrybut [runat="server"]. W takim przypadku tag HTML i jego atrybuty są dostępne dla kontrolera za pośrednictwem obiektu, którego właściwości odpowiadają właściwościom tagu HTML, który reprezentuje. Wcześniej nazywaliśmy te tagi tagami serwerowymi HTML.

Kliknijmy dwukrotnie na komponent [Label] na liście elementów sterujących [WebControls]. W zakładce [Design] otrzymujemy następujący wynik:

Image

W zakładce [All] kod wygląda teraz następująco:

<%@ Page Language="VB" %>
<html>
<head>
    <title>webforms</title>
</head>
<body>
    <asp:Label id="Label1" runat="server">Label</asp:Label>
</body>
</html>

Na pierwszy rzut oka widać, że zniknęła etykieta <script>. Została wygenerowana etykieta <asp:label>. Ma ona nazwę [Label1] i wartość [Label]. Wróćmy do zakładki [Design], aby zmodyfikować te dwie wartości. Klikamy raz na komponent [Label], aby wyświetlić okno właściwości tego komponentu w prawym dolnym rogu:

Image

Zachęcamy czytelnika do zapoznania się z właściwościami obiektu [Label]. Dwie z nich są dla nas tutaj istotne:

  • Text: jest to tekst, który ma wyświetlać etykieta – wpisujemy pusty ciąg znaków (c.a.d. nic)
  • ID: to jego identyfikator – wpisujemy lblHeure

Zakładka [Design] wygląda następująco:

Image

a kod dla [All] wygląda następująco:

<%@ Page Language="VB" %>
<html>
<head>
    <title>webforms</title>
</head>
<body>
    <asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>

Część dotycząca wyglądu strony jest gotowa. Pozostaje nam jeszcze napisać kod sterujący, który ma za zadanie umieścić godzinę we właściwości [Text] obiektu [lblHeure]. W zakładce [All] dodajemy następujący kod:


<%@ Page Language="VB" %>

<script runat="server">
    Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' zapisuje godzinę w pliku lblHeure
        lblHeure.Text = "Il est " + Date.Now.ToString("T")
    End Sub
</script>

<html>
<head>
    <title>webforms</title>
</head>
<body>
    <asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>

Należy zauważyć, że w kodzie kontrolera nie deklaruje się obiektu [lblHeure], tak jak to miało miejsce wcześniej:


    Protected lblHeure As New System.Web.UI.WebControls.Label

W rzeczywistości wszystkie komponenty serwerowe <asp:> w części prezentacyjnej są domyślnie deklarowane w części kodu sterującego. Dlatego ich dodatkowa deklaracja powoduje błąd kompilacji, wskazujący, że obiekt jest już zadeklarowany. Jesteśmy gotowi do uruchomienia. Wybieramy opcję [View/Start] lub skrót [F5]. Cassini uruchamia się automatycznie z następującymi parametrami:

Image

Akceptujemy te wartości. Domyślna przeglądarka systemu uruchamia się automatycznie, wyświetlając adres URL [http://localhost/form2.aspx]. Otrzymujemy następujący wynik:

Image

W dalszej części będziemy korzystać głównie z narzędzia WebMatrix, aby ułatwić tworzenie i testowanie krótkich programów, które będziemy pisać.

7.4. Komponent Literal

7.4.1. Zastosowanie

Tag <asp:literal> pozwala na wstawienie dynamicznego tekstu do kodu prezentacyjnego strony, podobnie jak tag <asp:label>. Jego głównym atrybutem jest [Text], który reprezentuje tekst, który zostanie wstawiony bez zmian do strumienia HTML strony. Ten tag jest wystarczający, jeśli nie zamierzamy formatować tekstu, który chcemy wstawić do strumienia HTML. W rzeczywistości, podczas gdy klasa [Label] umożliwia formatowanie za pomocą atrybutów takich jak [BorderColor, BorderWidth, Font, ...], klasa [Literal] nie posiada żadnego z tych atrybutów. Czytelnik może w całości wykorzystać poprzedni przykład, zastępując komponent [Label] komponentem [Literal].

7.5. Komponent Button

7.5.1. Zastosowanie

Tag <asp:Button> pozwala na wstawienie do formularza przycisku typu [Submit], który zapewnia obsługę zdarzeń podobną do tej spotykanej w aplikacjach systemu Windows. Właśnie tę kwestię chcemy tutaj omówić bardziej szczegółowo. Tworzymy następującą stronę [form3.aspx]:

Image

Ta strona, zbudowana przy użyciu WebMatrix, składa się z trzech elementów:

nr
nazwa
typ
właściwości
rola
1
Button1
Przycisk
text=Przycisk1
przycisk przesyłania
2
Button2
Przycisk
text=Przycisk2
przycisk przesyłania
3
lblInfo
Etykieta
text=
komunikat informacyjny

Kod wygenerowany przez WebMatrix dla tej części jest następujący:

<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
    <title>asp:button</title>
</head>
<body>
    <form runat="server">
        <p>
            <asp:Button id="Button1" runat="server" Text="Bouton1"></asp:Button>
            <asp:Button id="Button2" runat="server" Text="Bouton2"></asp:Button>
        </p>
        <p>
            <asp:Label id="lblInfo" runat="server"></asp:Label>
        </p>
    </form>
</body>
</html>

W tagach <asp:> odnajdujemy komponenty [Button] i [Label], które zostały wykorzystane podczas projektowania graficznego strony. Zwróćmy uwagę na tag <form runat="server">, który został wygenerowany automatycznie. Jest to tag serwerowy HTML, c.a.d oraz klasyczny tag HTML, reprezentowany jednak przez obiekt, którym może manipulować kontroler. Kod HTML tagu <form> zostanie wygenerowany na podstawie wartości, jaką kontroler nada temu obiektowi.

Dodajmy w części kodu poświęconej kontrolerowi procedurę [Page_Init], która obsługuje zdarzenie [Init] na stronie. Umieścimy w niej kod, który zapisuje żądanie klienta w pliku [request.txt]. Będzie nam to potrzebne do zrozumienia mechanizmu działania przycisków.

<script runat="server">
    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) 
        ' zapisuje bieżące żądanie w pliku request.txt w katalogu strony
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub
</script>

Należy zauważyć, że nie umieściliśmy klauzuli [Handles MyBase.Init] za deklaracją procedury [Page_Init]. W rzeczywistości zdarzenie [Init] obiektu [Page] ma domyślny moduł obsługi o nazwie [Page_Init]. Jeśli użyje się tej nazwy modułu obsługi, klauzula [Handles Page.Init] staje się zbędna. Jednak jej umieszczenie nie powoduje błędu.

7.5.2. Testy

Uruchamiamy aplikację pod nazwą WebMatrix za pomocą [F5]. Otrzymujemy następującą stronę:

Image

Kod HTML otrzymany przez przeglądarkę wygląda następująco:

<html>
<head>
    <title>asp:button</title>
</head>
<body>
    <form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />

        <p>
            <input type="submit" name="Button1" value="Bouton1" id="Button1" />
            <input type="submit" name="Button2" value="Bouton2" id="Button2" />

        </p>
        <p>
            <span id="lblInfo"></span>
        </p>
    </form>
</body>
</html>

Należy zwrócić uwagę na następujące kwestie:

  • tag <form runat="server"> został przekształcony w tag HTML
    <form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">

Ustalono dwa atrybuty: [method="post"] i [action="form3.aspx"]. Czy można nadać tym atrybutom różne wartości? Postaramy się wyjaśnić tę kwestię nieco dalej. Na razie należy zapamiętać, że formularz zostanie wysłany pod adres URL [form3.aspx]. Ustalono również dwa inne atrybuty: [name, id]. W większości przypadków są one ignorowane. Jeśli jednak strona zawiera kod JavaScript wykonywany po stronie przeglądarki, przydatny jest atrybut [name] tagu <form>.

  • Tagi <asp:button> zostały zamienione na tagi HTML przycisków [submit]. Kliknięcie dowolnego z tych przycisków spowoduje zatem wysłanie formularza [_ctl10] na adres URL [form3.aspx].
  • Tag <asp:label> został zamieniony na tag HTML <span>
  • zostało wygenerowane ukryte pole [__VIEWSTATE] z dziwną wartością:
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />

Pole to przedstawia w zakodowanej formie stan formularza wysłanego do klienta. Stan ten odzwierciedla wartości wszystkich elementów formularza. Ponieważ [__VIEWSTATE] stanowi część formularza, jego wartość zostanie przesłana wraz z resztą danych na serwer. Dzięki temu serwer będzie wiedział, który element formularza zmienił wartość, i ewentualnie podejmie odpowiednie działania. Będą one miały formę zdarzeń typu „TextBox o określonej nazwie zmienił wartość”.

7.5.3. Żądania klienta

Po wyświetleniu strony [form3.aspx] w przeglądarce, wywołajmy ją ponownie, a następnie przyjrzyjmy się żądaniu, które przeglądarka wysłała, aby ją uzyskać. Pamiętajmy, że nasza aplikacja zapisuje je w pliku [request.txt] w folderze aplikacji:

GET /form3.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

Mamy tu do czynienia z klasycznym kodem GET. Kliknijmy teraz przycisk [Bouton1] na stronie w przeglądarce. Na pierwszy rzut oka nic się nie dzieje. Wiemy jednak, że formularz został wysłany. Wskazuje na to kod HTML na stronie. Potwierdza to nowa treść [request.txt]:

POST /form3.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Content-Length: 80
Content-Type: application/x-www-form-urlencoded
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
Referer: http://localhost/form3.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

__VIEWSTATE=dDwxNTY0NjIwMjUwOzs%2B2mcnJczeuvF2PEfvmtv7uiUhWUw%3D&Button1=Bouton1

Pierwszy nagłówek HTTP wyraźnie wskazuje, że klient wysłał żądanie POST na adres URL [/form3.aspx]. Ostatni wiersz pokazuje przesłane wartości:

  • wartość ukrytego pola __VIEWSTATE
  • wartość przycisku, który został kliknięty

Jeśli klikniemy na [Bouton2], wartości przesłane przez przeglądarkę będą następujące:

__VIEWSTATE=dDwxNTY0NjIwMjUwOzs%2B2mcnJczeuvF2PEfvmtv7uiUhWUw%3D&Button2=Bouton2

Wartość pola ukrytego pozostaje niezmienna, jednak to wartość pola [Button2] została przesłana. Serwer może zatem ustalić, który przycisk został użyty. Wykorzysta tę informację do wywołania zdarzenia, które będzie mogło zostać przetworzone przez stronę po jej załadowaniu.

7.5.4. Obsługa zdarzenia Click obiektu Button

Przypomnijmy sobie, jak działa strona .aspx. Jest to obiekt pochodny od klasy [Page]. Nazwijmy tę klasę pochodną [unePage]. Gdy serwer otrzymuje żądanie dotyczące takiej strony, obiekt typu [unePage] jest instancjonowany za pomocą operacji new unePage(...). Następnie serwer generuje dwa zdarzenia o nazwach [Init] i [Load] w tej kolejności. Obiekt [unePage] może je obsługiwać, dostarczając procedury obsługi zdarzeń [Page_Init] i [Page_Load]. Następnie zostaną wygenerowane kolejne zdarzenia. Będziemy mieli okazję do tego wrócić. Jeśli żądanie klienta to POST, serwer wygeneruje zdarzenie [Click] związane z przyciskiem, który spowodował to zdarzenie POST. Jeśli klasa [unePage] przewidziała procedurę obsługi dla tego zdarzenia, zostanie ona wywołana. Przyjrzyjmy się temu mechanizmowi na przykładzie WebMatrix. W zakładce [Design] klasy [form3.aspx] kliknijmy dwukrotnie przycisk [Bouton1]. Zostaniemy wtedy automatycznie przeniesieni do zakładki [Code], do treści procedury o nazwie [Button1_Click]. Aby lepiej to zrozumieć, przejdźmy do zakładki [All] i przyjrzyjmy się całemu kodowi. Wprowadzono następujące zmiany:

<%@ Page Language="VB" %>
<script runat="server">

...
    Sub Button1_Click(sender As Object, e As EventArgs)

    End Sub

</script>
<html>
...
<body>
    <form runat="server">
...
            <asp:Button id="Button1" onclick="Button1_Click" runat="server" Text="Bouton1"></asp:Button>
    </form>
</body>
</html>

Do tagu <asp:Button> o wartości [Button1] dodano nowy atrybut [onclick="Button1_Click"]. Atrybut ten wskazuje procedurę odpowiedzialną za obsługę zdarzenia [Click] w obiekcie [Button1], w tym przypadku jest to procedura [Button1_Click]. Pozostaje nam tylko ją napisać:

    Sub Button1_Click(sender As Object, e As EventArgs)
        ' kliknięcie przycisku 1
        lblInfo.Text="Vous avez cliqué sur [Bouton1]"
    End Sub

Procedura umieszcza komunikat informacyjny w etykiecie [lblInfo]. Postępujemy w ten sam sposób w przypadku przycisku [Bouton2], aby uzyskać następującą nową stronę [form3.aspx]:

<%@ Page Language="VB" %>
<script runat="server">

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
        ' zapisuje bieżące żądanie w pliku request.txt w folderze strony
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub

    Sub Button1_Click(sender As Object, e As EventArgs)
        ' kliknięcie przycisku 1
        lblInfo.Text="Vous avez cliqué sur [Bouton1]"
    End Sub

    Sub Button2_Click(sender As Object, e As EventArgs)
        ' kliknięcie przycisku 2
        lblInfo.Text="Vous avez cliqué sur [Bouton2]"
    End Sub

</script>
<html>
<head>
    <title>asp:button</title>
</head>
<body>
    <form runat="server">
        <p>
            <asp:Button id="Button1" onclick="Button1_Click" runat="server" Text="Bouton1"></asp:Button>
            <asp:Button id="Button2" onclick="Button2_Click" runat="server" Text="Bouton2" BorderStyle="None"></asp:Button>
        </p>
        <p>
            <asp:Label id="lblInfo" runat="server"></asp:Label>
        </p>
    </form>
</body>
</html>

Uruchamiamy proces za pomocą [F5], aby uzyskać następującą stronę:

Image

Jeśli przyjrzymy się otrzymanemu kodowi HTML, zauważymy, że nie zmienił się on w porównaniu z kodem poprzedniej wersji strony:

<html>
<head>
    <title>asp:button</title>
</head>
<body>
    <form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />

        <p>
            <input type="submit" name="Button1" value="Bouton1" id="Button1" />
            <input type="submit" name="Button2" value="Bouton2" id="Button2" />
        </p>
        <p>
            <span id="lblInfo"></span>
        </p>
    </form>
</body>
</html>

Jeśli klikniemy na [Bouton1], otrzymamy następującą odpowiedź:

Image

Kod HTML otrzymany w tej odpowiedzi brzmi następująco:

<html>
<head>
    <title>asp:button</title>
</head>
<body>
    <form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwO3Q8O2w8aTwxPjs+O2w8dDw7bDxpPDU+Oz47bDx0PHA8cDxsPFRleHQ7PjtsPFZvdXMgYXZleiBjbGlxdcOpIHN1ciBbQm91dG9uMV07Pj47Pjs7Pjs+Pjs+Pjs+4oO98Vd244kj0lPMXReWOwJ1WW0=" />

        <p>
            <input type="submit" name="Button1" value="Bouton1" id="Button1" />
            <input type="submit" name="Button2" value="Bouton2" id="Button2" />
        </p>
        <p>
            <span id="lblInfo">Vous avez cliqué sur [Bouton1]</span>
        </p>
    </form>
</body>
</html>

Możemy zauważyć, że wartość ukrytego pola [__VIEWSTATE] uległa zmianie. Odzwierciedla to zmianę wartości komponentu [lblInfo].

7.5.5. Zdarzenia w cyklu życia aplikacji ASP.NET

Dokumentacja ASP.NET zawiera listę zdarzeń generowanych przez serwer w trakcie cyklu życia aplikacji:

  • podczas pierwszego żądania otrzymanego przez aplikację zostanie wygenerowane zdarzenie [Start] dotyczące obiektu [HttpApplication] aplikacji. Zdarzeniem tym można zarządzać za pomocą procedury [Application_Start] zawartej w pliku [global.asax] aplikacji.

Następnie nastąpi seria zdarzeń, które będą się powtarzać dla każdego otrzymanego żądania:

  • jeśli żądanie nie zawiera tokenu sesji, uruchamiana jest nowa sesja i generowane jest zdarzenie [Start] na obiekcie [Session] powiązanym z żądaniem. Zdarzeniem tym można zarządzać za pomocą procedury [Session_Start] z pliku [global.asax] aplikacji.
  • Serwer generuje zdarzenie [BeginRequest] na obiekcie [HttpApplication]. Można je obsłużyć za pomocą procedury [Application_BeginRequest] z pliku [global.asax] aplikacji.
  • Serwer ładuje stronę, o którą poproszono w żądaniu. Utworzy instancję obiektu [Page], a następnie wygeneruje dwa zdarzenia na tym obiekcie: [Init], a następnie [Load]. Te dwa zdarzenia mogą być obsługiwane przez procedury [Page_Init] i [Page_Load] na stronie.
  • Na podstawie otrzymanych wartości wysłanych serwer wygeneruje kolejne zdarzenia: [TextChanged] dla komponentu [TextBox], którego wartość uległa zmianie, [CheckedChanged] dla przycisku opcji, którego wartość uległa zmianie, [SelectedIndexChanged] dla listy, w której zmieniono wybrany element, ... Będziemy mieli okazję omówić główne zdarzenia dla każdego z komponentów serwerowych, które zamierzamy przedstawić. Każde zdarzenie E dotyczące obiektu o nazwie O może być obsługiwane przez procedurę o nazwie O_E.
  • Kolejność obsługi powyższych zdarzeń nie jest gwarantowana. Dlatego procedury obsługujące zdarzenia nie powinny opierać się na żadnych założeniach dotyczących tej kolejności. Można jednak mieć pewność, że zdarzenie [Click] związane z przyciskiem, które wywołało zdarzenie POST, jest obsługiwane jako ostatnie.
  • Gdy strona będzie gotowa, serwer wyśle ją do klienta. Wcześniej generuje zdarzenie [PreRender], które może być obsłużone przez procedurę [Page_PreRender] na stronie.
  • Po wysłaniu odpowiedzi HTML do klienta strona zostanie usunięta z pamięci. Z tej okazji wygenerowane zostaną dwa zdarzenia: [Unload] i [Disposed]. Strona może wykorzystać te zdarzenia do zwolnienia zasobów.

Aplikacja może również odbierać zdarzenia poza żądaniem klienta:

  • zdarzenie [End] dotyczące obiektu [Session] aplikacji występuje w momencie zakończenia sesji. Może to nastąpić na wyraźne żądanie kodu strony lub z powodu przekroczenia przyznanego czasu trwania sesji. Procedura [Session_End] w pliku [global.asax] obsługuje to zdarzenie. Zazwyczaj zwalnia się w niej zasoby uzyskane w [Session_Start].
  • Zdarzenie [End] dotyczące obiektu [HttpApplication] aplikacji występuje w momencie zakończenia działania aplikacji. Dzieje się tak zwłaszcza podczas wyłączania serwera WWW. Procedura [Application_End] z pliku [global.asax] obsługuje to zdarzenie. Zazwyczaj zwalnia ona zasoby uzyskane w procedurze [Application_Start].

Należy zwrócić uwagę na następujące kwestie:

  • poprzedni model zdarzeń opiera się na klasycznej wymianie danych klient-serwer HTTP. Widać to doskonale po przeanalizowaniu wymienianych nagłówków HTTP.
  • przetwarzanie poprzednich zdarzeń odbywa się zawsze po stronie serwera. Kliknięcie przycisku może oczywiście być przetworzone przez skrypt JavaScript po stronie serwera. Nie jest to jednak wówczas zdarzenie serwerowe i mamy tu do czynienia z technologią niezależną od ASP.NET.

W którym momencie przetwarzane są zdarzenia, niezależnie od tego, czy są one przetwarzane po stronie serwera (zdarzenia związane z komponentami serwerowymi), czy po stronie przeglądarki przez skrypty JavaScript?

Weźmy na przykład listę rozwijaną. Gdy użytkownik zmienia wybrany w niej element, zdarzenie (zmiana wybranego elementu) może zostać przetworzone lub nie, a jeśli zostanie przetworzone, może to nastąpić w różnych momentach.

  • Jeśli chcemy je przetworzyć natychmiast, mamy dwa rozwiązania:
    • może ono zostać przetworzone przez przeglądarkę za pomocą skryptu JavaScript. Serwer nie bierze wówczas udziału w tym procesie. Aby było to możliwe, strona musi dać się odtworzyć na podstawie wartości zawartych na stronie.
    • może zostać przetworzone przez serwer. W tym celu istnieje tylko jedno rozwiązanie: formularz musi zostać wysłany do serwera w celu przetworzenia. Mamy zatem operację [submit]. Zobaczymy, że w tym przypadku wykorzystujemy komponent serwerowy o nazwie [DropDownList] i ustawiamy jego atrybut [AutoPostBack] na wartość [true]. Oznacza to, że w przypadku zmiany elementu wybranego z listy rozwijanej formularz musi zostać natychmiast wysłany na serwer. W takim przypadku serwer generuje dla obiektu [DropDownList] kod HTML powiązany z funkcją JavaScript, której zadaniem jest wywołanie [submit], gdy tylko wystąpi zdarzenie „zmiana wybranego elementu”. Ten kod [submit] prześle formularz na serwer, a w formularzu tym zostaną umieszczone ukryte pola wskazujące, że kod [post] wynika ze zmiany wyboru na liście rozwijanej. Serwer wygeneruje wówczas zdarzenie [SelectedIndexChanged], które strona będzie mogła obsłużyć.
    • Jeśli chcemy je przetworzyć, ale nie od razu, ustawiamy atrybut [AutoPostBack] komponentu serwerowego [DropDownList] na wartość [false]. W takim przypadku serwer generuje dla obiektu [DropDownList] standardowy kod HTML odpowiadający liście <select> bez powiązanej funkcji JavaScript. Nie dzieje się więc nic, gdy użytkownik zmienia wybór z listy rozwijanej. Jednak gdy użytkownik zatwierdzi formularz, na przykład za pomocą przycisku [submit], serwer będzie w stanie rozpoznać, że nastąpiła zmiana wyboru. Widzieliśmy bowiem, że formularz wysłany do serwera zawierał ukryte pole [__VIEWSTATE], przedstawiające w zakodowanej formie stan wszystkich elementów wysłanego formularza. Gdy serwer otrzyma nowy formularz przesłany przez klienta, będzie mógł sprawdzić, czy w liście rozwijanej zmienił się wybrany element. Jeśli tak, wygeneruje zdarzenie [SelectedIndexChanged], które strona będzie mogła następnie obsłużyć. Aby odróżnić ten mechanizm od poprzedniego, niektórzy autorzy twierdzą, że zdarzenie „zmiana wyboru” zostało „zapisane w pamięci podręcznej” w momencie jego wystąpienia w przeglądarce. Zostanie ono przetworzone przez serwer dopiero wtedy, gdy przeglądarka prześle mu formularz, często w wyniku kliknięcia przycisku [submit].
    • Wreszcie, jeśli nie chcemy przetwarzać tego zdarzenia, należy ustawić atrybut [AutoPostBack] komponentu serwerowego [DropDownList] na wartość [false] i nie pisać obsługi jego zdarzenia [SelectedIndexChanged].

Po zrozumieniu mechanizmu obsługi zdarzeń programista nie będzie projektował aplikacji internetowej tak samo jak aplikacji dla systemu Windows. W rzeczywistości, o ile zmiana wyboru w polu kombi w aplikacji dla systemu Windows może posłużyć do natychmiastowej zmiany wyglądu formularza, w którym się ono znajduje, o tyle w aplikacji internetowej będzie się bardziej wahać przed natychmiastową obsługą tego zdarzenia, jeśli wiąże się to z wysłaniem formularza („post”) na serwer, a tym samym z wymianą danych między klientem a serwerem. Dlatego też właściwość [AutoPostBack] komponentów serwerowych jest domyślnie ustawiona na wartość [false]. Ponadto mechanizm [AutoPostBack], oparty na skryptach JavaScript generowanych automatycznie przez serwer WWW w formularzu wysyłanym do klienta, może być stosowany tylko wtedy, gdy mamy pewność, że przeglądarka klienta zezwoliła na wykonywanie skryptów JavaScript. Formularze są zatem często tworzone w następujący sposób:

  • komponenty serwerowe formularza mają właściwości od [AutoPostBack] do [false]
  • formularz zawiera jeden lub więcej przycisków odpowiedzialnych za wykonanie operacji [POST] na formularzu
  • w kodzie kontrolera strony zapisuje się procedury obsługi tylko tych zdarzeń, które mają być obsługiwane, najczęściej zdarzenia [Click] dotyczącego jednego z przycisków.

7.6. Komponent TextBox

7.6.1. Zastosowanie

Tag <asp:TextBox> pozwala na wstawienie pola wprowadzania danych do kodu prezentacyjnego strony. Tworzymy stronę [form4.aspx], aby uzyskać następujący wygląd:

Image

4321Ta strona, utworzona przy użyciu WebMatrix, zawiera następujące elementy:

nr
nazwa
typ
właściwości
rola
1
TextBox1
TextBox
AutoPostback=true
Text=
pole wprowadzania danych
2
TextBox2
TextBox
AutoPostback=false
Text=
pole wprowadzania danych
3
lblInfo1
Etykieta
text=
komunikat informacyjny dotyczący zawartości pliku [TextBox1]
3
lblInfo2
Etykieta
text=
komunikat informacyjny dotyczący zawartości pliku [TextBox2]

Kod wygenerowany przez WebMatrix dla tej części jest następujący:

<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
    <title>asp:textbox</title>
</head>
<body>
    <form runat="server">
        <p>
            Texte 1 :
            <asp:TextBox id="TextBox1" runat="server" AutoPostBack="True"></asp:TextBox>
        </p>
        <p>
            Texte 2 :
            <asp:TextBox id="TextBox2" runat="server"></asp:TextBox>
        </p>
        <p>
            <asp:Label id="lblInfo1" runat="server"></asp:Label>
        </p>
        <p>
            <asp:Label id="lblInfo2" runat="server"></asp:Label>
        </p>
    </form>
</body>
</html>

W zakładce [Design] kliknij dwukrotnie na komponent [TextBox1]. W ten sposób generowany jest szkielet menedżera zdarzenia [TextChanged] dla tego obiektu (zakładka [All]):

<%@ Page Language="VB" %>
<script runat="server">

    Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
    End Sub
</script>
<html>
...
<body>
...
            <asp:TextBox id="TextBox1" runat="server" AutoPostBack="True" OnTextChanged="TextBox1_TextChanged"></asp:TextBox>
        </p>
....
    </form>
</body>
</html>

Atrybut [OnTextChanged="TextBox1_TextChanged"] został dodany do tagu <asp:TextBox id="TextBox1"> w celu wskazania obsługi zdarzenia [TextChanged] na [TextBox1]. Teraz piszemy procedurę [TextBox1_Changed].

    Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
      ' zmiana tekstu
      lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
    End Sub

W procedurze zapisujemy w etykiecie [lblInfo1] komunikat sygnalizujący zdarzenie i wskazujący zawartość [TextBox1]. To samo robimy w przypadku [TextBox2]. Podajemy również godzinę, aby łatwiej śledzić przetwarzanie zdarzeń. Ostateczny kod [form4.aspx] wygląda następująco:

<%@ Page Language="VB" %>
<script runat="server">

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
        ' zapisuje bieżące zapytanie w pliku request.txt w folderze strony
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub

    Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
      ' zmiana tekstu
      lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
    End Sub

    Sub TextBox2_TextChanged(sender As Object, e As EventArgs)
      ' zmiana tekstu
      lblInfo2.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox2]. Texte 2=["+textbox2.Text+"]"
    End Sub

</script>
<html>
<head>
    <title>asp:textbox</title>
</head>
<body>
    <form runat="server">
        <p>
            Texte 1 :
            <asp:TextBox id="TextBox1" runat="server" AutoPostBack="True" OnTextChanged="TextBox1_TextChanged"></asp:TextBox>
        </p>
        <p>
            Texte 2 :
            <asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged"></asp:TextBox>
        </p>
        <p>
            <asp:Label id="lblInfo1" runat="server"></asp:Label>
        </p>
        <p>
            <asp:Label id="lblInfo2" runat="server"></asp:Label>
        </p>
    </form>
</body>
</html>

Dodaliśmy procedurę [Page_Init] w celu zapisania zapytania klienta, tak jak w poprzednim przykładzie.

7.6.2. Testy

Uruchamiamy aplikację pod numerem WebMatrix za pomocą [F5]. Otrzymujemy następującą stronę:

Image

Kod HTML otrzymany przez przeglądarkę wygląda następująco:

<html>
<head>
    <title>asp:textbox</title>
</head>
<body>
    <form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">
<input type="hidden" name="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE" value="dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u+/OrTD" />

<script language="javascript">
<!--
    function __doPostBack(eventTarget, eventArgument) {
        var theform = document._ctl0;
        theform.__EVENTTARGET.value = eventTarget;
        theform.__EVENTARGUMENT.value = eventArgument;
        theform.submit();
    }
// -->
</script>

        <p>
            Texte 1 :
            <input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1"></span>

        </p>
        <p>
            <span id="lblInfo2"></span>
        </p>
    </form>
</body>
</html>

W tym kodzie znajduje się wiele elementów wygenerowanych automatycznie przez serwer. Zwróćmy uwagę na następujące kwestie:

  • istnieją trzy ukryte pola: [__VIEWSTATE], z którym już się spotkaliśmy, [__EventTarget] oraz [__EventArgument]. Te dwa ostatnie pola służą do obsługi zdarzenia przeglądarki „change” w polu wprowadzania danych [TextBox1]
  • tagów serwerowych <asp:textbox> powstały tagi HTML <input type="text" ...>, które odpowiadają polom wprowadzania danych
  • tag serwera <asp:textbox id="TextBox1" AutoPostBack="true" ...> dał początek tagowi <input type="text" ...> posiadającemu atrybut [onchange="__doPostBack('TextBox1','')"]. Atrybut ten określa, że w przypadku zmiany zawartości [TextBox1] należy wykonać funkcję JavaScript [_doPostBack(...)]. Wygląda ona następująco:
<script language="javascript">
<!--
    function __doPostBack(eventTarget, eventArgument) {
        var theform = document._ctl0;
        theform.__EVENTTARGET.value = eventTarget;
        theform.__EVENTARGUMENT.value = eventArgument;
        theform.submit();
    }
// -->
</script>

Czym zajmuje się powyższa funkcja? Przypisuje ona wartość do każdego z dwóch ukrytych pól [__EventTarget] i [__EventArgument], a następnie wysyła formularz. Formularz ten jest zatem przesyłany na serwer. Takie jest działanie funkcji [AutoPostBack]. Zdarzenie przeglądarki „change” powoduje wykonanie kodu „__doPostBack('TextBox1','')”. Wynika z tego, że w wysłanym formularzu ukryte pole [__EventTarget] będzie miało wartość „TextBox1”, a ukryte pole [__EventArgument] – wartość „”. Dzięki temu serwer będzie wiedział, który komponent wywołał POST.

  • Tag serwerowy <asp:textbox id="TextBox2"...> spowodował wygenerowanie standardowego tagu <input type="text" ...>, ponieważ jego atrybut [AutoPostBack] nie był ustawiony na wartość [true].
  • Tag <form> wskazuje, że formularz zostanie wysłany na adres [form4.aspx]:
    <form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">

Przeprowadźmy nasz pierwszy test. Wpiszmy tekst w pierwszym polu wprowadzania danych:

Image

następnie umieśćmy kursor w drugim polu wprowadzania danych. Natychmiast pojawia się nowa strona:

Image

Co się stało? Kiedy kursor opuścił pierwsze pole wprowadzania danych, przeglądarka sprawdziła, czy jego zawartość uległa zmianie. Tak właśnie było. W związku z tym przeglądarka wygenerowała zdarzenie [change] w polu HTML [TextBox1]. Zauważyliśmy wówczas, że uruchomiła się funkcja JavaScript, która wysłała formularz do strony [form4.aspx]. Strona ta została zatem ponownie załadowana przez serwer. Wartości przesłane przez formularz pozwoliły serwerowi z kolei stwierdzić, że zawartość tagu serwerowego [TextBox1] uległa zmianie. W związku z tym po stronie serwera uruchomiono procedurę [TextBox1_Changed]. Umieściła ona komunikat w etykiecie [lblInfo1]. Po zakończeniu tej procedury wartość [form4.aspx] została wysłana do przeglądarki. Dlatego właśnie w polu [lblInfo1] znajduje się teraz tekst. Można jednak dziwić się, że w polu wprowadzania danych [TextBox1] znajduje się jakaś wartość. W rzeczywistości żadna procedura wykonywana po stronie serwera nie nadaje wartości temu polu. Jest to ogólny mechanizm działania formularzy internetowych ASP.NET: serwer zwraca formularz w stanie, w jakim go otrzymał. W tym celu ponownie przypisuje komponentom wartości, które zostały dla nich przesłane przez klienta. W przypadku niektórych komponentów klient nie wysyła żadnych wartości. Dotyczy to na przykład komponentów <asp:label>, które są przekształcane w tagi <span> o identyfikatorze HTML. Należy pamiętać, że formularz posiada ukryte pole o identyfikatorze [__VIEWSTATE], które reprezentuje stan formularza w momencie wysłania go do klienta. Stan ten jest sumą stanów wszystkich elementów formularza, w tym elementów <asp:label>, jeśli takie występują. Ponieważ ukryte pole [__VIEWSTATE] jest wysyłane przez przeglądarkę klienta, serwer jest w stanie odtworzyć poprzedni stan wszystkich elementów formularza. Pozostaje mu jedynie zmodyfikować te elementy, których wartości zostały zmienione przez POST.

Przyjrzyjmy się teraz w pliku [request.txt] żądaniu wysłanemu przez przeglądarkę:

POST /form4.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Content-Length: 137
Content-Type: application/x-www-form-urlencoded
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
Referer: http://localhost/form4.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u%2B%2FOrTD&TextBox1=premier+texte&TextBox2=

Wyraźnie widać kod POST oraz przesłane parametry. Wróćmy do naszej przeglądarki i wpiszmy tekst w drugim polu wprowadzania danych:

Image

Wróćmy do pola wprowadzania nr 1, aby wpisać nowy tekst: tym razem nic się nie dzieje. Dlaczego? Ponieważ pole wprowadzania danych [TextBox2] nie posiada właściwości [AutoPostBack] przy [true], a tag <input type="text"...> wygenerowany dla niego nie obsługuje zdarzenia [Change], co widać w jego kodzie HTML:

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

W związku z tym po opuszczeniu pola wprowadzania danych nr 2 nie następuje żadne zdarzenie. Teraz wprowadźmy nowy tekst w polu nr 1:

Image

Opuśćmy pole wprowadzania danych nr 1. Natychmiast wykrywane jest zdarzenie [Change] w tym polu, a formularz jest wysyłany na serwer, który zwraca następującą stronę:

Image

Co się stało? Po pierwsze, przeglądarka wysłała formularz. Informację tę można znaleźć w żądaniu klienta zapisanym w [request.txt]:

POST /form4.aspx HTTP/1.1
....
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTt0PDtsPGk8MT47PjtsPHQ8O2w8aTwxPjtpPDU%2BOz47bDx0PHA8cDxsPFRleHQ7PjtsPHByZW1pZXIgdGV4dGU7Pj47Pjs7Pjt0PHA8cDxsPFRleHQ7PjtsPDE4OjQyOjI5OiBldnQgW1RleHRDaGFuZ2VkXSBzdXIgW1RleHRCb3gxXS4gVGV4dGUgMT1bcHJlbWllciB0ZXh0ZV07Pj47Pjs7Pjs%2BPjs%2BPjs%2BxLOermpUUUz5rTAa%2FFsjda6lVmo%3D&TextBox1=troisi%C3%A8me+texte&TextBox2=second+texte

Serwer rozpoczyna od przywrócenia komponentom ich poprzednich wartości za pomocą ukrytego pola [__VIEWSTATE], które przesłał mu klient. Dzięki przesłanym polom [TextBox1] i [TextBox2] serwer przypisze do komponentów [TextBox1] i [TextBox2] wartości, które zostały mu przesłane. Dzięki temu mechanizmowi klient odtworzy formularz w takiej postaci, w jakiej został przesłany. Następnie, również dzięki przesłanym polom [__VIEWSTATE], [TextBox1] i [TextBox2] serwer wykryje, że wartości pól wprowadzania danych [TextBox1] i [TextBox2] uległy zmianie. W związku z tym wygeneruje zdarzenia [TextChanged] dla tych dwóch obiektów. Zostaną wykonane procedury [TextBox1_TextChanged] i [TextBox2_TextChanged], a etykiety [labelInfo1] i [labelInfo2] otrzymają nową wartość. Następnie zmodyfikowana w ten sposób strona [form4.aspx] jest wysyłana z powrotem do klienta.

Teraz ponownie modyfikujemy pole wprowadzania danych nr 1:

Image

Gdy przeniesiemy kursor poza pole nr 1, w przeglądarce występuje zdarzenie [Change]. Następnie przebiega sekwencja zdarzeń opisana wcześniej (wysłanie żądania z przeglądarki do serwera, ..., otrzymanie odpowiedzi z serwera). Otrzymujemy następującą odpowiedź:

Image

Na podstawie godziny wyświetlanej przy każdej z wiadomości widać, że na serwerze została wykonana jedynie procedura [TextBox1_Changed]. Procedura [TextBox2_TextChanged] nie została wykonana, ponieważ wartość [TextBox2] nie uległa zmianie. Na koniec wpiszmy nowy tekst w polu nr 2:

Image

Następnie umieśćmy kursor na polu nr 1, a potem ponownie na polu nr 2. Strona się nie zmienia. Dlaczego? Ponieważ nie zmieniamy wartości pola nr 1, zdarzenie przeglądarki [Change] nie występuje, gdy opuszczamy to pole. W rezultacie formularz nie jest wysyłany na serwer. Dlatego na stronie nic się nie zmienia. To właśnie fakt, że zawartość [lblInfo2] nie ulega zmianie, wskazuje, że nie ma zdarzenia POST. Gdyby taki istniał, serwer wykryłby, że zawartość pola [TextBox2] uległa zmianie i powinien odzwierciedlić ten fakt w polu [lblInfo2].

Z tego przykładu wynika, że nie ma sensu przenosić właściwości [AutoPostBack] z pliku [TextBox] do pliku [true]. W większości przypadków powoduje to niepotrzebną wymianę danych między klientem a serwerem.

7.6.3. Rola pola __VIEWSTATE

Zauważyliśmy, że serwer systematycznie umieszczał w generowanym przez siebie formularzu ukryte pole o nazwie __VIEWSTATE. Stwierdziliśmy, że pole to reprezentuje stan formularza i że jeśli zwróci się serwerowi to ukryte pole, serwer jest w stanie odtworzyć poprzednią wartość formularza. Stan formularza jest sumą stanów jego komponentów. Każdy z nich posiada właściwość o nazwie [EnableViewState] o wartości logicznej, wskazującą, czy stan danego elementu ma zostać umieszczony w ukrytym polu [__VIEWSATE], czy też nie. Domyślnie właściwość ta ma wartość [true], co powoduje, że stan wszystkich komponentów formularza jest umieszczany w polu [__VIEWSTATE]. Czasami nie jest to pożądane.

Przeprowadźmy kilka eksperymentów, aby lepiej zrozumieć rolę właściwości [EnableViewState]. Ustawmy tę właściwość na wartość [false] dla obu pól wprowadzania danych:

...
            <asp:TextBox id="TextBox1" runat="server" OnTextChanged="TextBox1_TextChanged" AutoPostBack="True" EnableViewState="False"></asp:TextBox>
...
            <asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged" EnableViewState="False"></asp:TextBox>
...

Uruchommy teraz aplikację i wpiszmy pierwszy tekst w polu nr 1, a następnie przejdźmy do pola nr 2. Następnie wysyłane jest żądanie POST do serwera i otrzymujemy następującą odpowiedź:

Image

Wpiszmy tekst w polu nr 2, zmieńmy tekst w polu nr 1, a następnie wróćmy do pola nr 2 (w tej kolejności). Do serwera wysyłane jest nowe żądanie o numerze POST i otrzymujemy następującą odpowiedź:

Image

Na razie wszystko wygląda tak samo jak wcześniej. Teraz zmieńmy zawartość pola nr 1, a następnie przejdźmy do pola nr 2. Wysyłane jest nowe żądanie POST. Odpowiedź serwera jest następująca:

Image

Tym razem nastąpiła zmiana. Serwer wykrył zdarzenie [TextChanged] w polu nr 2, ponieważ zmieniono czas [lblInfo2]. Jednak w rzeczywistości nie nastąpiła żadna zmiana. Wynika to z właściwości [EnableViewState=false] obiektu [TextBox2]. Powoduje ona, że serwer nie umieścił w polu [__VIEWSTATE] formularza poprzedniego stanu obiektu [TextBox2]. Oznacza to, że jako poprzedni stan przypisano mu pusty ciąg znaków. Kiedy wystąpiło zdarzenie POST spowodowane zmianą zawartości [TextBox1], serwer porównał aktualną wartość [TextBox2], która wynosiła [deux], z jej poprzednią wartością (pustym ciągiem znaków). Na tej podstawie stwierdził, że wartość [TextBox2] uległa zmianie i wygenerował zdarzenie [TextChanged] dla [TextBox2]. Można to zweryfikować, umieszczając pusty ciąg znaków w [TextBox2]. Zgodnie z powyższym wyjaśnieniem serwer nie powinien generować zdarzenia [TextChanged] dla [TextBox2]. Spróbujmy:

Image

Tak właśnie się stało. Czas i zawartość zdarzenia [lblInfo2] wskazują, że procedura [TextBox2_TextChanged] nie została wykonana. Mając to na uwadze, przyjrzyjmy się właściwości [EnableViewState] dla czterech elementów formularza:

TextBox1
chcemy zachować stan tego komponentu, aby serwer wiedział, czy uległ on zmianie, czy nie
TextBox2
to samo
lblInfo1
nie chcemy zachowywać stanu tego komponentu. Chcemy, aby tekst był przeliczany przy każdym nowym POST. Jeśli nie zostanie przeliczony, musi być pusty. Wszystko to można osiągnąć za pomocą [EnableViewState=false]
lblInfo2 
to samo

Nasza strona prezentacyjna wygląda teraz następująco:

...
            <asp:TextBox id="TextBox1" runat="server" OnTextChanged="TextBox1_TextChanged" AutoPostBack="True"></asp:TextBox>
...
            <asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged"></asp:TextBox>
....
            <asp:Label id="lblInfo1" runat="server" enableviewstate="False"></asp:Label>
...
            <asp:Label id="lblInfo2" runat="server" enableviewstate="False"></asp:Label>
...

Powtórzmy tę samą serię testów, co poprzednio. W miejscu, gdzie otrzymaliśmy ekran

version 1

Image

, teraz otrzymujemy:

version 2

Image

W tym etapie zmienialiśmy zawartość pola 1 bez zmiany zawartości pola 2, co powodowało, że procedura [TextBox2_TextChanged] nie była wykonywana po stronie serwera, co z kolei oznaczało, że pole [lblInfo2] nie otrzymywało nowej wartości. Jest ono zatem wyświetlane z poprzednią wartością. W wersji 1 ([EnableViewState=true]) tą poprzednią wartością była wprowadzona wartość. W wersji 2 ([EnableViewState=false]) tą poprzednią wartością jest pusty ciąg znaków.

Czasami nie ma potrzeby zachowywania poprzedniego stanu komponentów. Zamiast ustawiać [EnableViewState=false] dla każdego z nich, można zadeklarować, że strona nie powinna zachowywać swojego stanu. Odbywa się to w dyrektywie [Page] w kodzie prezentacji:

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

W tym przypadku, niezależnie od wartości właściwości [EnableViewState], stan komponentu nie jest zapisywany w ukrytym polu [__VIEWSTATE]. Wszystko przebiega wówczas tak, jakby jego poprzedni stan był pustym ciągiem znaków.

Wykorzystajmy teraz klienta [curl], aby wyjaśnić inne mechanizmy. Najpierw żądamy adresu URL [http://localhost/form4.aspx]:

dos>curl --include --url http://localhost/form4.aspx

HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Sun, 04 Apr 2004 17:51:14 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 1077
Connection: Close


<html>
<head>
    <title>asp:textbox</title>
</head>
<body>
    <form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">
<input type="hidden" name="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE" value="dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u+/OrTD" />

<script language="javascript">
<!--
    function __doPostBack(eventTarget, eventArgument) {
        var theform = document._ctl0;
        theform.__EVENTTARGET.value = eventTarget;
        theform.__EVENTARGUMENT.value = eventArgument;
        theform.submit();
    }
// -->
</script>

        <p>
            Texte 1 :
            <input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1"></span>
        </p>
        <p>
            <span id="lblInfo2"></span>
        </p>
    </form>
</body>
</html>

Otrzymujemy z serwera kod HTML z [form4.aspx]. Nie różni się on od tego, który otrzymała przeglądarka. Przypomnijmy tutaj żądanie wysłane przez przeglądarkę podczas wysyłania formularza:

POST /form4.aspx HTTP/1.1
Connection: keep-alive
...
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u%2B%2FOrTD&TextBox1=premier+texte&TextBox2=

Zróbmy to samo z POST, ale bez wysyłania pola [__VIEWSTATE]:

dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=pierwszy+tekst --data TextBox2=

...................
        <p>
            Texte 1 :
            <input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1">19:57:48: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
        </p>
        <p>
            <span id="lblInfo2"></span>
        </p>
..............

Należy zwrócić uwagę na następujące kwestie:

  • serwer wykrył zdarzenie [TextChanged] na [TextBox1], ponieważ wygenerował tekst [lblInfo1]. Brak [__VIEWSTATE] nie stanowił dla niego przeszkody. W przypadku jego braku serwer zakłada, że poprzednią wartością pola wprowadzania danych jest pusty ciąg znaków.
  • Udało mu się przenieść tekst przesłany dla [TextBox1] do atrybutu [value] tagu [TextBox1], tak aby pole [TextBox1] ponownie pojawiło się z wprowadzoną wartością. W tym celu nie potrzebuje on [__VIEWSTATE], a jedynie wartość przesłaną dla [TextBox1]

Teraz powtórzmy to samo zapytanie bez wprowadzania żadnych zmian. Otrzymujemy następującą nową odpowiedź:

dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=pierwszy+tekst --data TextBox2=


        <p>
            Texte 1 :
            <input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1">20:05:47: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
        </p>
        <p>
            <span id="lblInfo2"></span>
        </p>

Wobec braku [__VIEWSTATE] serwer nie był w stanie wykryć, że wartość pola [TextBox1] nie uległa zmianie. Postępuje zatem tak, jakby poprzednia wartość była pustym ciągiem znaków. W związku z tym wygenerował tutaj zdarzenie [TextChanged] na podstawie [TextBox1]. Wykonajmy ponownie to samo zapytanie, tym razem z pustym polem [TextBox1] i niepustym polem [TextBox2]:

dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox2=drugi+tekst --data TextBox1=
......
        <p>
            Texte 1 :
            <input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" value="second texte" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1"></span>
        </p>
        <p>
            <span id="lblInfo2">20:11:54: evt [TextChanged] sur [TextBox2]. Texte 2=[second texte]</span>
        </p>
......

Wobec braku [__VIEWSTATE] poprzednia wartość [TextBox1] została uznana za pusty ciąg znaków. Ponieważ wartość zapisana dla [TextBox1] również była pustym ciągiem znaków, zdarzenie [TextChanged] dotyczące [TextBox1] nie zostało wygenerowane. Procedura [TextBox1_TextChanged] nie została wykonana, w związku z czym pole [lblInfo1] nie otrzymało nowej wartości. Wiadomo, że w takim przypadku komponent zachowuje swoją poprzednią wartość. Jednak w tym przypadku tak nie jest – [lblInfo1] utraciło swoją poprzednią wartość. Stało się tak, ponieważ wartość ta jest wyszukiwana w [__VIEWSTATE]. Ponieważ to pole nie istnieje, do [lblInfo1] przypisano pusty ciąg znaków. W przypadku [TextBox2] serwer porównał jego wartość przesłaną ([second texte]) z poprzednią wartością. Ponieważ nie ma wpisu [__VIEWSTATE], ta poprzednia wartość jest równa pustemu ciągowi znaków. Ponieważ wartość przesłana [TextBox2] różniła się od pustego ciągu znaków, wygenerowano zdarzenie [TextChanged] dotyczące [TextBox2]. Procedura [TextBox2_TextChanged] została wykonana, a pole [lblInfo2] otrzymało nową wartość.

Można się zastanawiać, czy parametry [__EVENTTARGET] i [__EVENTARGUMENT] są rzeczywiście potrzebne. Nie wysyłając tych parametrów, serwer nie będzie wiedział, przez jakie zdarzenie wywołano [submit]. Spróbujmy:

dos>curl --include --url http://localhost/form4.aspx --data TextBox2=drugi+tekst --data TextBox1=pierwszy+tekst
..............................
        <p>
            Texte 1 :
            <input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1"></span>
        </p>
        <p>
            <span id="lblInfo2"></span>
        </p>
    </form>
.....................

Widać, że żadne zdarzenie [TextChanged] nie zostało przetworzone. Ponadto pola przesłane [TextBox1] i [TextBox2] nie odzyskują swoich wartości przesłanych. W rzeczywistości wszystko przebiega tak, jakby wykonano operację GET. Wszystko wraca do normy, jeśli w polach przesłanych znajduje się pole [__EVENTTARGET], nawet jeśli nie ma ono wartości:

dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET= --data TextBox2=drugi+tekst --data TextBox1=pierwszy+tekst
.......
        <p>
            Texte 1 :
            <input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" value="second texte" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1">20:34:14: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
        </p>
        <p>
            <span id="lblInfo2">20:34:14: evt [TextChanged] sur [TextBox2]. Texte 2=[second texte]</span>
        </p>
........

7.6.4. Inne właściwości komponentu TextBox

Komponent serwerowy [TextBox] umożliwia również generowanie tagów HTML <input type="password"..> oraz <textarea>..</textarea>, c.a.d. Tagów tych odpowiadają odpowiednio pole wprowadzania danych z zabezpieczeniem oraz wielowierszowe pole wprowadzania danych. Generowanie to jest kontrolowane przez właściwość [TextMode] komponentu [TextBox]. Ma ona trzy możliwe wartości:

wartość
wygenerowany tag HTML
SingleLine
<input type="text" ...>
MultiLine
<textarea>...</textarea>
Password
<input type="password ...>

Zastosowanie tych właściwości omówimy na podstawie poniższego przykładu [form5.aspx]:

Image

nr
nazwa
typ
właściwości
rola
1
btnAjouter
Przycisk
 
przycisk [submit] – służy do dodania zawartości [TextBox1] do zawartości [TextBox2]
2
TextBox1
TextBox
TextMode=Password
Text=
pole z zabezpieczonym wprowadzaniem danych
3
TextBox2
TextBox
TextMode=Wielowierszowy
Text=
łączy dane wprowadzone w [TextBox1]

Właściwość strony [EnableViewState] zostaje ustawiona na [false]. Po stronie serwera obsługujemy zdarzenie kliknięcia przycisku [btnAjouter]:

Sub btnAjouter_Click(sender As Object, e As EventArgs)
  ' do treści pliku [textBox1] dodajemy treść pliku [TextBox2]
  textbox2.text=textbox2.text + textbox1.text+controlchars.crlf
End Sub

Aby zrozumieć ten kod, należy przypomnieć sobie sposób przetwarzania zdarzenia POST w formularzu. Najpierw wykonywane są procedury [Page_Init] i [Page_Load]. Następnie uruchamiane są wszystkie procedury zdarzeń zapisane w pamięci podręcznej. Na koniec wykonywana jest procedura obsługująca zdarzenie, które wywołało [POST], w tym przypadku procedura [btnAjouter_Click]. Podczas wykonywania procedur obsługi zdarzeń wszystkie elementy strony, które miały wartość w POST, przyjęły tę wartość. Pozostałe elementy przywróciły swoje poprzednie wartości, jeśli ich właściwość [EnableViewState] miała wartość [true], lub swoje wartości projektowe, jeśli ich właściwość [EnableViewState] miała wartość [false]. W tym przypadku wartości pól [TextBox1] i [TextBox2] staną się częścią pola POST utworzonego przez klienta. Również w powyższym kodzie pole [textbox1.text] będzie miało wartość przesłaną przez klienta, podobnie jak pole [textbox2.text]. Procedura [btnAjouter_Click] umieszcza w polu [TextBox2] wartość przesłaną dla [TextBox2], dodaną do wartości przesłanej dla [TextBox1], a następnie dodaną do znacznika końca wiersza [ControlChars.CrLf] zdefiniowanego w przestrzeni nazw [Microsoft.VisualBasic]. Nie ma potrzeby importowania tej przestrzeni, ponieważ serwer WWW importuje ją domyślnie.

Ostateczny kod dla [form5.aspx] wygląda następująco:

<%@ Page Language="VB" EnableViewState="False" %>
<script runat="server">

    Sub btnAjouter_Click(sender As Object, e As EventArgs)
      ' dodajemy zawartość pliku [textBox1] do zawartości pliku [TextBox2]
      textbox2.text+=textbox1.text+controlchars.crlf
    End Sub

</script>
<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            <asp:Button id="btnAjouter" onclick="btnAjouter_Click" runat="server" Text="Ajouter" EnableViewState="False"></asp:Button>
            <asp:TextBox id="TextBox1" runat="server" TextMode="Password" Width="353px" EnableViewState="False"></asp:TextBox>
        </p>
        <p>
            <asp:TextBox id="TextBox2" runat="server" TextMode="MultiLine" Width="419px" Height="121px" EnableViewState="False"></asp:TextBox>
        </p>
    </form>
</body>
</html>

Nieco wyżej zamieszczono zrzut ekranu przedstawiający przebieg operacji.

7.7. Komponent DropDownList

Tag <asp:DropDownList> pozwala na wstawienie listy rozwijanej do kodu prezentacyjnego strony. Tworzymy stronę [form6.aspx], aby uzyskać następujący wygląd:

Image

nr
nazwa
typ
właściwości
rola
1
DropDownList1
DropDownList
AutoPostback=true
EnableViewState=true
lista rozwijana
2
lblInfo
Etykieta
EnableViewState=false
komunikat informacyjny

Wygenerowany kod prezentacji wygląda następująco:

Page Language="VB" %>
<script runat="server">

</script>
<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            <asp:DropDownList id="DropDownList1" runat="server" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
        </p>
        <p>
            <asp:Label id="lblInfo" runat="server" enableviewstate="False"></asp:Label>
        </p>
    </form>
</body>
</html>

Na razie lista rozwijana nie zawiera żadnych elementów. Wypełnimy ją w procedurze [Page_Load]. W tym celu musimy poznać niektóre właściwości i metody klasy [DropDownList]:

Items
kolekcja typu [ListItemCollection] zawierająca elementy listy rozwijanej. Elementy tej kolekcji są typu [ListItem].
Items.Count
liczba elementów w kolekcji [Items]
Items(i)
element nr i z listy – typu [ListItem]
Items.Add
aby dodać nowy element [ListItem] do kolekcji [Items]
Items.Clear
aby usunąć wszystkie elementy z kolekcji [Items]
Items.RemoveAt(i)
aby usunąć element nr i z kolekcji [Items]
SelectedItem
pierwszy element [ListItem] z kolekcji [Items], którego właściwość [Selected] ma wartość „prawda”
SelectedIndex
numer elementu [SelectedItem] w kolekcji [Items]

Elementy kolekcji [Items] należącej do klasy [DropDownList] są typu [ListItem]. Każdy element [ListItem] generuje tag HTML <option>:

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

Opisujemy niektóre właściwości i metody klasy [ListItem]:

ListItem(String texte, String value)
konstruktor – tworzy element [ListItem] z właściwościami [texte] i [value]. Element ListItem(T,V) spowoduje wygenerowanie znacznika HTML <option value="V">T</option>. Klasa [ListITem] pozwala zatem opisać elementy listy HTML
Selected
boolowski. Jeśli wartość jest prawdziwa, odpowiednia opcja na liście HTML będzie miała atrybut [selected="selected"]. Atrybut ten informuje przeglądarkę, że odpowiedni element na liście HTML
Text
tekst T opcji HTML <option value="V" [selected="selected"]>T</option>
Value
wartość V atrybutu [Value] opcji HTML <option value="V" [selected="selected"]>T</option>

Mamy wystarczającą ilość informacji, aby w procedurze [Page_Load] na stronie zapisać kod wypełniający listę rozwijaną [DropDownList1]:

    Sub Page_Load(sender As Object, e As EventArgs)
        ' wypełnia się pole rozwijane, jeśli jest to pierwsze wywołanie
        if not IsPostBack then
          dim valeurs() as String = {"1","2","3","4"}
            dim textes() as  String = {"un","deux","trois","quatre"}

            dim i as integer
            for i=0 to valeurs.length-1
                DropDownList1.Items.Add(new ListItem(textes(i),valeurs(i)))
            next
        end if
    end sub

Po zainicjowaniu komponentu [DropDownList1] jego tłumaczenie HTML będzie wyglądało następująco:

<select name="DropDownList1" id="DropDownList1" onchange="__doPostBack('DropDownList1','')" language="javascript">

    <option value="1">un</option>
    <option value="2">deux</option>
    <option value="3">trois</option>
    <option value="4">quatre</option>

</select>

Wiemy, że procedura [Page_Load] jest uruchamiana przy każdym wywołaniu strony [form6.aspx]. Strona ta jest wywoływana po raz pierwszy przez procedurę GET, a następnie przez procedurę POST za każdym razem, gdy użytkownik wybiera nowy element z listy rozwijanej. Czy kod odpowiedzialny za wypełnienie tej listy w [Page_Load] powinien być wykonywany za każdym razem? Odpowiedź zależy od atrybutu [EnableViewState] komponentu [DropDownList1]. Jeśli atrybut ten ma wartość „prawda”, wiadomo, że stan komponentu [DropDownList1] będzie zachowywany podczas kolejnych zapytań w polu ukrytym [__VIEWSTATE]. Stan ten obejmuje dwie rzeczy:

  • listę wszystkich wartości z listy rozwijanej
  • wartość elementu wybranego z tej listy

Kuszące wydaje się zatem ustawienie właściwości [EnableViewState] komponentu [DropDownList1] na wartość [true], aby uniknąć konieczności ponownego obliczania wartości, które mają zostać umieszczone na liście. Problem polega jednak na tym, że procedura [Page_Load] jest uruchamiana za każdym razem, gdy wywoływana jest strona [form6.aspx], więc wartości te i tak zostaną obliczone. Obiekt [Page], którego instancją jest [form6.aspx], posiada atrybut [IsPostBack] o wartości logicznej. Jeśli atrybut ten ma wartość „prawda”, oznacza to, że strona została wywołana przez POST. Wartość „fałsz” oznacza, że strona została wywołana przez obiekt GET. W naszym systemie komunikacji klient-serwer klient zawsze żąda od serwera tej samej strony [form6.aspx]. Za pierwszym razem żąda jej z identyfikatorem GET, a przy kolejnych próbach z identyfikatorem POST. Wynika z tego, że właściwość [IsPostBack] może posłużyć nam do wykrycia pierwszego wywołania GET przez klienta. Wartości listy rozwijanej generujemy wyłącznie podczas tego pierwszego wywołania. Przy kolejnych żądaniach wartości te będą generowane przez mechanizm [VIEWSTATE]. W innych sytuacjach zawartość listy może się różnić w zależności od żądania i wówczas musi być przeliczana przy każdym z nich. W takim przypadku należy ustawić atrybut [EnableViewState] tej listy na wartość [false], aby uniknąć niepotrzebnego podwójnego obliczania zawartości listy, chyba że konieczne jest poznanie elementów wybranych wcześniej z listy, ponieważ informacja ta jest przechowywana w [VIEWSTATE].

Atrybut [AutoPostBack] listy [DropDownList1] został ustawiony na wartość „prawda”. Oznacza to, że przeglądarka wyśle formularz, gdy tylko wykryje zdarzenie „zmiana wybranego elementu” na liście rozwijanej. Serwer z kolei wykryje, dzięki atrybutowi [VIEWSTATE] oraz przesłanym wartościom, że element wybrany w komponencie [DropDownList1] uległ zmianie. Następnie wywoła zdarzenie [SelectedIndexChanged] w tym komponencie. Zareagujemy na to za pomocą następującej procedury:

    Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
      ' zmiana wyboru
      lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
        " valeur=" + dropdownlist1.selecteditem.value + _
        " index="+ dropdownlist1.selectedindex.tostring
    End Sub

Podczas wykonywania tej procedury obiekt [DropDownList1] odzyskał swoje elementy typu [ListItem] dzięki obiektowi [VIEWSTATE]. Ponadto jeden z nich, typu [ListItem], ma atrybut [Selected] ustawiony na wartość „prawda” – jest to ten, którego wartość została przesłana przez przeglądarkę. Dostęp do tego elementu można uzyskać na kilka sposobów:

DropDownList1.SelectedItem
jest pierwszym elementem [ListItem] na liście, którego atrybut [Selected] ma wartość „true”
DropDownList1.SelectedItem.Text
odpowiada części [texte] tagu HTML elementu <option value="...">tekst</option> wybranego przez użytkownika
DropDownList1.SelectedItem.Value
odpowiada części [value] tagu HTML elementu <option value="...">tekst</option> wybranego przez użytkownika
DropdDownList1.SelectedIndex
numer w kolekcji [DropDownList1.Items] pierwszego elementu [ListItem], którego atrybut [Selected] ma wartość „true”

Ostateczny kod elementu [form6.aspx] wygląda następująco:

<%@ Page Language="VB" %>
<script runat="server">

    Sub Page_Load(sender As Object, e As EventArgs)
        ' należy wypełnić pole rozwijane, jeśli jest to pierwsze wywołanie
        if not IsPostBack then
          dim valeurs() as String = {"1","2","3","4"}
            dim textes() as  String = {"un","deux","trois","quatre"}

            dim i as integer
            for i=0 to valeurs.length-1
                DropDownList1.Items.Add(new ListItem(textes(i),valeurs(i)))
            next
        end if
    end sub

    Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
      ' zmiana wyboru
      lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
        " valeur=" + dropdownlist1.selecteditem.value + _
        " index="+ dropdownlist1.selectedindex.tostring
    End Sub

</script>
<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            <asp:DropDownList id="DropDownList1" runat="server" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
        </p>
        <p>
            <asp:Label id="lblInfo" runat="server" enableviewstate="False"></asp:Label>
        </p>
    </form>
</body>
</html>

7.8. Komponent ListBox

Tag <asp:ListBox> pozwala wstawić listę do kodu prezentacyjnego strony. Tworzymy [form7.aspx], aby uzyskać następujący wygląd:

Image

nr
nazwa
typ
właściwości
rola
1
txtSaisie
TextBox
EnableViewState=false
pole wprowadzania danych
2
btnAjouter
Przycisk
 
przycisk [submit], który przenosi do Listy 1 zawartość pola txtSaisie, o ile nie jest ono puste.
3
ListBox1
ListBox
EnableViewState=true
SelectionMode=Single
lista wartości z pojedynczym wyborem
4
ListBox2
ListBox
EnableViewState=true
SelectionMode=Multiple
lista wartości z możliwością wielokrotnego wyboru
5
btn1vers2
Przycisk
 
przycisk [submit], który przenosi do [liste 2] wybrany element z [liste 1]
6
btn2vers1
Przycisk
 
przycisk [submit], który przenosi do [liste 1] zaznaczone elementy z [liste 2]

Wygenerowany kod prezentacji wygląda następująco:

<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            Tapez un texte pour l'inclure dans Liste 1 :
            <asp:TextBox id="txtSaisie" runat="server" EnableViewState="False"></asp:TextBox>
        </p>
        <p>
            <asp:Button id="btnAjouter" onclick="btnAjouter_Click" runat="server" Text="Ajouter"></asp:Button>
        </p>
        <p>
            <table>
                <tbody>
                    <tr>
                        <td>
                            <p align="center">
                                Liste 1
                            </p>
                        </td>
                        <td>
                        </td>
                        <td>
                            <p align="center">
                                Liste 2
                            </p>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <asp:ListBox id="ListBox1" runat="server"></asp:ListBox>
                        </td>
                        <td>
                            <p>
                                <asp:Button id="btn1vers2" onclick="btn1vers2_Click" runat="server" Text="-->"></asp:Button>
                            </p>
                            <p>
                                <asp:Button id="btn2vers1" onclick="btn2vers1_Click" runat="server" Text="<--"></asp:Button>
                            </p>
                        </td>
                        <td>
                            <p>
                                <asp:ListBox id="ListBox2" runat="server" SelectionMode="Multiple"></asp:ListBox>
                            </p>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <p align="center">
                                <asp:Button id="btnRaz1" onclick="btnRaz1_Click" runat="server" Text="Effacer"></asp:Button>
                            </p>
                        </td>
                        <td>
                        </td>
                        <td>
                            <p align="center">
                                <asp:Button id="btnRaz2" onclick="btnRaz2_Click" runat="server" Text="Effacer"></asp:Button>
                            </p>
                        </td>
                    </tr>
                </tbody>
            </table>
        </p>
    </form>
</body>
</html>

Klasa [ListBox] wywodzi się z tej samej klasy [ListControl], co omówiona wcześniej klasa [DropDownList]. Znajdują się w niej wszystkie właściwości i metody omówione dla klasy [DropDownList], ponieważ w rzeczywistości należały one do klasy [ListControl]. Pojawia się nowa właściwość:

SelectionMode
określa tryb zaznaczania listy HTML <select>, która zostanie wygenerowana na podstawie tego komponentu. Jeśli SelectionMode=Single, wówczas można zaznaczyć tylko jeden element. Jeśli SelectionMode=Multiple, można zaznaczyć wiele elementów. W tym celu w tagu <select> listy HTML zostanie wygenerowany atrybut [multiple="multiple"].

Przeanalizujmy zdarzenia. Kliknięcie przycisku [Ajouter] zostanie obsłużone przez następującą procedurę [btnAjouter_Click]:

    Sub btnAjouter_Click(sender As Object, e As EventArgs)
      ' dodanie do listy 1
      dim texte as string=txtSaisie.text.trim
      if texte<> "" then ListBox1.Items.Add(New ListItem(texte))
      ' wyczyść txtSaisie
      txtSaisie.text=""
    End Sub

Jeśli tekst wprowadzony w [txtSaisie] nie jest ciągiem pustym ani białym, do listy [ListBox1] dodawany jest nowy element. Wiemy, że musimy dodać element typu [ListItem]. Wcześniej używaliśmy konstruktora [ListItem(T as String, V as String)] do wykonania podobnego zadania. Taki element generuje tag HTML [<option value="V">T</option>]. W tym przypadku używamy konstruktora [ListItem(T as String)], który generuje tagi HTML, [<option value="T">T</option>] oraz c.a.d. tekst opcji [T] jest wykorzystywany do utworzenia wartości opcji. Po dodaniu zawartości [txtSaisie] do listy [ListBox1] pole [txTSaisie] zostaje wyczyszczone.

Kliknięcia przycisków [Effacer] będą obsługiwane przez następujące procedury:

    Sub btnRaz1_Click(sender As Object, e As EventArgs)
      ' wyzerowanie listy 1
      ListBox1.Items.Clear
    End Sub

    Sub btnRaz2_Click(sender As Object, e As EventArgs)
      ' wyzerowanie listy 2
      ListBox2.Items.Clear
    End Sub

Kliknięcia przycisków służących do przechodzenia między listami są obsługiwane przez następujące procedury:

    Sub btn1vers2_Click(sender As Object, e As EventArgs)
      ' przeniesienie wybranego elementu z listy 1 do listy 2
      transfert(ListBox1,ListBox2)
    End Sub

    Sub btn2vers1_Click(sender As Object, e As EventArgs)
      ' przeniesienie wybranego elementu z listy 2 do listy 1
      transfert(ListBox2,ListBox1)
    End Sub

    sub transfert(l1 as listbox, l2 as listbox)
      ' przeniesienie wybranych elementów z listy 1 do listy 2
       ' coś do zrobienia?
      if l1.selectedindex=-1 then return
      dim i as integer
      ' zaczynamy od końca
      for i=l1.items.count-1 to 0 step -1
        ' zaznaczone?
        if l1.items(i).selected then
            ' już nie zaznaczone
            l1.items(i).selected=false
          ' przeniesienie do l2
          l2.items.add(l1.items(i))
          ' usunięcie z l1
          l1.items.removeAt(i)
        end if
      next
    end sub

Ponieważ oba przyciski pełnią tę samą funkcję, czyli przenoszą elementy z jednej listy do drugiej, można to sprowadzić do jednej procedury przenoszenia z dwoma parametrami:

  • l1 typu [ListBox], czyli lista źródłowa
  • l2 typu [ListBox], która jest listą docelową

Najpierw sprawdzamy, czy na liście l1 jest co najmniej jeden zaznaczony element; w przeciwnym razie nie ma nic do zrobienia. W tym celu sprawdzamy właściwość [l1.selectedindex], która reprezentuje numer pierwszego zaznaczonego elementu na liście. Jeśli nie ma żadnego, jego wartość wynosi -1. Jeśli w l1 jest co najmniej jeden zaznaczony element, przeprowadzamy przeniesienie do l2. W tym celu przeglądamy całą listę elementów l1 i dla każdego z nich sprawdzamy, czy jego atrybut [selected] ma wartość true. Jeśli tak, jego atrybut [selected] jest ustawiany na [false], a następnie element ten jest kopiowany do listy l2 i wreszcie usuwany z listy l1. Usunięcie to powoduje zmianę numeracji elementów listy l1. Dlatego właśnie lista elementów l1 jest przeglądana od końca. Gdybyśmy przeglądali ją od początku i usunęli element nr 10, element nr 11 stałby się elementem nr 10, a element nr 12 – elementem nr 11. Po przetworzeniu elementu nr 10 nasza pętla przebiegająca w porządku naturalnym przetworzy element nr 11, który – zgodnie z powyższym wyjaśnieniem – jest dawnym elementem nr 12. Element, który miał numer 11, a obecnie nosi numer 10, zostanie pominięty. Przeglądając elementy listy l1 w odwrotnym kierunku, unikamy tego problemu.

7.9. Komponenty CheckBox, RadioButton

Tagi <asp:RadioButton> i <asp:CheckBox> pozwalają na wstawienie odpowiednio przycisku opcji i pola wyboru do kodu prezentacji strony. Tworzymy stronę [form8.aspx], aby uzyskać następujący wygląd:

Image

nr
nazwa
typ
właściwości
rola
1
RadioButton1
RadioButton2
RadioButton3
RadioButton
RadioButton1.Checked=true
RadioButton1.Text=1
RadioButton2.Checked=false
RadioButton2.Text=2
RadioButton3.Checked=false
RadioButton3.Text=3
dla wszystkich 3 przycisków: GroupName=radio
przyciski opcji
2
CheckBoxA
CheckBoxB
CheckBoxC
CheckBox
Checked=false dla wszystkich
CheckBoxA.Text=A
CheckBoxB.Text=B CheckBoxC.Text=C
pola wyboru
3
btnEnvoyer
Przycisk
 
przycisk [submit]
4
lstInfos
ListBox
 
lista informacji

Aby przeglądarka traktowała te trzy przyciski opcji jako wzajemnie wykluczające się, należy zgrupować je w grupę przycisków opcji. Odbywa się to za pomocą atrybutu [GroupName] klasy [RadioButton]. W tej aplikacji nie ma potrzeby utrzymywania stanu strony. Dlatego dodajemy do strony atrybut [EnableViewState="false"]. Kod prezentacji wygląda następująco:

<html>
<head>
</head>
<body>
    <form id="frmControls" runat="server">
        <h3>Cases à cocher 
        </h3>
        <p>
            <asp:RadioButton id="RadioButton1" runat="server" Checked="True" EnableViewState="False" GroupName="radio" Text="1"></asp:RadioButton>
            <asp:RadioButton id="RadioButton2" runat="server" EnableViewState="False" GroupName="radio" Text="2"></asp:RadioButton>
            &nbsp;<asp:RadioButton id="RadioButton3" runat="server" EnableViewState="False" GroupName="radio" Text="3"></asp:RadioButton>
        </p>
        <p>
            <asp:CheckBox id="CheckBoxA" runat="server" EnableViewState="False" Text="A"></asp:CheckBox>
            <asp:CheckBox id="CheckBoxB" runat="server" EnableViewState="False" Text="B"></asp:CheckBox>
            <asp:CheckBox id="CheckBoxC" runat="server" EnableViewState="False" Text="C"></asp:CheckBox>
        </p>
        <p>
            <asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
            <asp:Button id="btnTree" onclick="btnTree_Click" runat="server" Text="Contrôles"></asp:Button>
        </p>
        <p>
            <asp:ListBox id="lstInfos" runat="server" EnableViewState="False" Rows="6" Height="131px"></asp:ListBox>
        </p>
    </form>
</body>
</html>

Musimy napisać procedurę [btnEnvoyer_Click], aby obsłużyć zdarzenie [Click] dla tego przycisku. Stan przycisku opcji lub pola wyboru określa jego atrybut [Checked] – jest to wartość logiczna typu boolowskiego, która przyjmuje wartość „prawda”, jeśli pole jest zaznaczone, a „fałsz” w przeciwnym razie. Wystarczy więc wpisać na liście [lstInfos] wartości atrybutu [Checked] dla sześciu przycisków opcji i pól wyboru. Ponieważ nie wiąże się to z żadnymi szczególnymi trudnościami, wprowadzimy pewną innowację:

<script runat="server">

    Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
      ' umieszczamy informacje w polu listy
      for each c as control in FindControl("frmControls").controls
          ' czy kontrolka jest pochodną CheckBox
          if TypeOf(c) is CheckBox then
              lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
          end if
      next
    End Sub

</script>

Stronę można postrzegać jako drzewiastą strukturę elementów sterujących. W naszym przykładzie strona zawiera teksty oraz elementy sterujące serwera. Teksty są traktowane jako specjalny element sterujący o nazwie [LiteralControl]. Każdy fragment tekstu tworzy ten element sterujący, nawet ciąg spacji między dwoma elementami sterującymi. Każdy element sterujący posiada atrybut ID, który go identyfikuje. To właśnie atrybut ID pojawia się w tagach:

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

Jeśli pominąć elementy sterujące [LiteralControl], analizowana strona zawiera następujące elementy sterujące:

  • [HtmlForm], który jest formularzem [ID=frmControls]. Ten z kolei jest kontenerem elementów sterujących. Zawiera on następujące elementy sterujące:

-- [ID=RadioButton1] typu [RadioButton]

-- [ID=RadioButton2] typu [RadioButton]

-- [ID=RadioButton3] typu [RadioButton]

-- [ID=CheckBoxA] typu [CheckBox]

-- [ID=CheckBoxA] typu [CheckBox]

-- [ID=CheckBoxA] typu [CheckBox]

-- [ID=btnEnvoyer] typu [Button]

Element kontrolny posiada następujące właściwości:

[Control].Controls
zwraca zbiór elementów podrzędnych z [Control], jeśli takie istnieją
[Control].FindControl(ID)
zwraca element identyfikowany przez ID, znajdujący się w katalogu głównym drzewa elementów podrzędnych [Control]. W powyższym przykładzie: Page.FindControl("frmControls") oznacza kontener [HtmlForm]. Aby dotrzeć do przycisku opcji [RadioButton1], należy wpisać
Page.FindControl("frmControls").FindControl("RadioButton1")
[Control].ID
identyfikator [Control]

Wróćmy do kodu procedury [btnEnvoyer_Click]:

    Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
      ' umieszczamy informacje w polu listy
      for each c as control in FindControl("frmControls").controls
          ' czy kontrolka jest pochodną CheckBox?
          if TypeOf(c) is CheckBox then
              lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
          end if
      next
    End Sub

Chcemy wyświetlić stan przycisków opcji i pól wyboru znajdujących się w formularzu. Przechodzimy przez wszystkie elementy sterujące w formularzu. Jeśli bieżący element sterujący jest typu pochodnego od [CheckBox], wyświetlamy jego właściwość [Checked]. Ponieważ klasa [RadioButton] jest pochodną klasy [CheckBox], test ten ma zastosowanie do obu typów elementów sterujących. Powyższy zrzut ekranu przedstawia przykładowe wyniki działania.

7.10. Komponenty CheckBoxList, RadioButtonList

Czasami chcemy, aby użytkownik dokonywał wyboru spośród wartości, których nie znamy w momencie projektowania strony. Wybory te pochodzą z pliku konfiguracyjnego, bazy danych itp. i są znane dopiero w momencie wykonywania. Istnieją rozwiązania tego problemu i mieliśmy z nimi do czynienia. Lista z pojedynczym wyborem sprawdza się dobrze, gdy użytkownik może dokonać tylko jednego wyboru, a lista z wielokrotnym wyborem – gdy może dokonać kilku wyborów. Z estetycznego punktu widzenia, a także jeśli liczba opcji nie jest duża, można rozważyć użycie przycisków opcji zamiast listy z pojedynczym wyborem lub pól wyboru zamiast listy z wielokrotnym wyborem. Jest to możliwe dzięki komponentom [CheckBoxList] i [RadioButtonList].

Klasy [CheckBoxList] i [RadioButtonList] wywodzą się z tej samej klasy [ListControl], co klasy [DropDownList] i [ListBox] omówione wcześniej. Znajdziemy więc niektóre właściwości i metody omówione dla tych klas, a które w rzeczywistości należały do klasy [ListControl].

Items
kolekcja typu [ListItemCollection] zawierająca elementy listy rozwijanej. Elementy tej kolekcji są typu [ListItem].
Items.Count
liczba elementów kolekcji [Items]
Items(i)
element nr i z listy – typu [ListItem]
Items.Add
aby dodać nowy element [ListItem] do kolekcji [Items]
Items.Clear
aby usunąć wszystkie elementy z kolekcji [Items]
Items.RemoveAt(i)
aby usunąć element nr i z kolekcji [Items]
SelectedItem
pierwszy element [ListItem] z kolekcji [Items], którego właściwość [Selected] ma wartość „prawda”
SelectedIndex
numer poprzedniego elementu w kolekcji [Items]

Niektóre właściwości są specyficzne dla klas [CheckBoxList] i [RadioButtonList]:

RepeatDirection
[horizontal] lub [vertical] dla list poziomych lub pionowych.

Elementy z kolekcji [Items] są typu [ListItem]. Każdy element typu [ListItem] spowoduje wygenerowanie innego tagu w zależności od tego, czy mamy do czynienia z obiektem typu [CheckBoxList], czy [RadioButtonList]:

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

Lub

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

Opisujemy niektóre właściwości i metody klasy [ListItem]:

ListItem(String texte, String value)
konstruktor – tworzy element [ListItem] z właściwościami „text” i „value”. Element ListItem(T,V) spowoduje wygenerowanie tagu HTML <input type="checkbox" value="V">T lub <input type="radio" value="V">T, w zależności od sytuacji.
Selected
wartość logiczna. Jeśli jest prawdziwa, odpowiednia opcja z listy HTML będzie miała atrybut [selected="selected"]. Atrybut ten informuje przeglądarkę, że odpowiedni element musi być zaznaczony na liście HTML
Text
tekst T opcji HTML <input type=".." value="V" [selected="selected"]>T
Value
wartość atrybutu Value opcji HTML <input type=".." value="V" [selected="selected"]>T

Proponujemy stworzyć następującą stronę [form8b.aspx]:

Image

nr
nazwa
typ
właściwości
rola
1
RadioButtonList1
RadioButtonList
EnableViewState=true
RepeatDirection=horizontal
lista przycisków opcji
2
CheckBoxList1
CheckBoxList
EnableViewState=true
RepeatDirection=horizontal
lista pól wyboru
3
btnEnvoyer
Przycisk
 
przycisk [submit], który wyświetla w [4] listę elementów wybranych z obu list
4
lstInfos
ListBox
EnableViewState=false
lista wartości

Kod wyświetlania strony wygląda następująco:

<%@ Page Language="VB" autoeventwireup="false" %>
<script runat="server">
...
</script>
<html>
<head>
</head>
<body>
    <form id="frmControls" runat="server">
        <h3>Listes de cases à cocher
        </h3>
        <p>
            <asp:RadioButtonList id="RadioButtonList1" runat="server" RepeatDirection="Horizontal"></asp:RadioButtonList>
        </p>
        <p>
            <asp:CheckBoxList id="CheckBoxList1" runat="server" RepeatDirection="Horizontal"></asp:CheckBoxList>
        </p>
        <p>
            <asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
        </p>
        <p>
            <asp:ListBox id="lstInfos" runat="server" EnableViewState="False" Rows="6"></asp:ListBox>
        </p>
    </form>
</body>
</html>

Kod kontrolny brzmi następująco:

<script runat="server">

    Sub Page_Load(sender As Object, e As EventArgs) handles MyBase.Load
        ' wypełnia się listy, jeśli jest to pierwsze wywołanie
        if not IsPostBack then
            ' teksty do listy RadioButton
          dim textesRadio() as String = {"1","2","3","4"}
          ' teksty do listy CheckBox
          dim textesCheckBox() as  String = {"un","deux","trois","quatre"}
                 ' wypełnienie listy opcji
            dim i as integer
            for i=0 to textesRadio.length-1
                RadioButtonList1.Items.Add(new ListItem(textesRadio(i)))
            next
             ' wybór elementu nr 1
            RadioButtonList1.SelectedIndex=1
            ' wypełnianie listy pól wyboru
            for i=0 to textesCheckBox.length-1
                CheckBoxList1.Items.Add(new ListItem(textesCheckBox(i)))
            next
        end if
    end sub


    Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
      ' umieszczamy informacje w polu listy lstinfos
      affiche(RadioButtonList1)
      affiche(CheckBoxList1)
    End Sub

    sub affiche(l1 as ListControl)
      ' wyświetla wartości wybranych elementów z l1
       ' coś do zrobienia?
      if l1.selectedindex=-1 then return
      dim i as integer
      ' zaczynamy od końca
      for i= 0 to l1.items.count-1
        ' zaznaczone?
        if l1.items(i).selected then
          lstInfos.Items.Add("["+TypeName(l1)+"] ["+l1.items(i).text+"] sélectionné")
        end if
      next
    end sub

</script>

W procedurze [Page_Load], która jest uruchamiana przy każdym wywołaniu strony, inicjowane są obie listy. Aby uniknąć ich inicjowania za każdym razem, wykorzystuje się właściwość [IsPostBack], dzięki czemu odbywa się to tylko przy pierwszym wywołaniu. Przy kolejnych wywołaniach listy zostaną automatycznie odświeżone przez mechanizm procedury [VIEWSTATE]. Po wyświetleniu strony użytkownik zaznacza niektóre pola wyboru i korzysta z przycisku [Envoyer]. Wartości formularza są wówczas przesyłane do samego formularza. Po wykonaniu procedury [Page_Load] uruchamiana jest procedura [btnEnvoyer_Click]. Ta z kolei wywołuje procedurę [affiche] w celu wypełnienia listy [lstInfos]. Procedura ta otrzymuje jako parametr obiekt typu [ListControl], co pozwala na przesłanie do niej zarówno obiektu [RadioButtonList], jak i obiektu [CheckBoxList] – klas pochodnych od [ListControl]. Lista [lstInfos] może mieć swój atrybut ustawiony na wartość od [EnableViewState] do [false], ponieważ jej stan nie musi być zachowywany między poszczególnymi żądaniami.

7.11. Komponenty Panel, LinkButton

Tag <asp:panel> pozwala na wstawienie kontenera elementów sterujących na stronę. Zaletą kontenera jest to, że niektóre z jego właściwości mają zastosowanie do wszystkich elementów sterujących, które zawiera. Dotyczy to jego właściwości [Visible]. Właściwość ta istnieje dla każdego elementu sterującego serwera. Jeśli kontener posiada właściwość [Visible=false], każdy z jego elementów sterujących będzie kontrolowany przez własną właściwość [Visible]. Jeśli posiada właściwość [Visible=false], wówczas kontener i cała jego zawartość nie są wyświetlane. Może to być prostsze niż zarządzanie właściwością [Visible] dla każdego z elementów sterujących w kontenerze.

Tag <asp:LinkButton> pozwala na wstawienie linku do kodu prezentacyjnego strony. Pełni ona rolę podobną do przycisku [Button]. Wywołuje ona bowiem zdarzenie POST po stronie klienta za pomocą powiązanej z nią funkcji JavaScript. Tworzymy stronę [form9.aspx], aby uzyskać następujący wygląd:

Image

nr
nazwa
typ
właściwości
rola
1
Panel1
Panel
EnableViewState=true
kontener elementów sterujących
2
ListBox1
ListBox
EnableViewState=true
lista trzech wartości
3
lnkCacher
LinkButton
EnableViewState=false
link do ukrycia kontenera

Gdy kontener jest ukryty, pojawia się nowy link:

Image

nr
nazwa
typ
właściwości
rola
4
lnkVoir
LinkButton
EnableViewState=false
link do wyświetlenia kontenera

Kod wyświetlania strony wygląda następująco:

<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            <asp:Panel id="Panel1" runat="server" BorderStyle="Ridge" BorderWidth="1px">
                <p>
                    Conteneur 
                </p>
                <p>
                    <asp:ListBox id="ListBox1" runat="server">
                        <asp:ListItem Value="1">un</asp:ListItem>
                        <asp:ListItem Value="2">deux</asp:ListItem>
                        <asp:ListItem Value="3" Selected="True">trois</asp:ListItem>
                    </asp:ListBox>
                </p>
            </asp:Panel>
        </p>
        <p>
            <asp:LinkButton id="lnkVoir" onclick="lnkVoir_Click" runat="server">Voir le conteneur</asp:LinkButton>
        </p>
        <p>
            <asp:LinkButton id="lnkCacher" onclick="lnkCacher_Click" runat="server">Cacher le conteneur</asp:LinkButton>
        </p>
    </form>
</body>
</html>

Warto zauważyć, że kod ten inicjuje listę [ListBox1], nadając jej trzy wartości. Obsługa zdarzeń [Clic] dla obu linków wygląda następująco:

<%@ Page Language="VB" %>
<script runat="server">

    Sub Page_Load(sender As Object, e As EventArgs)
...
    end sub

    Sub lnkVoir_Click(sender As Object, e As EventArgs)
        ' wyświetla kontener 1
        panel1.Visible=true
         ' zmienia linki
        lnkVoir.visible=false
        lnkCacher.visible=true
    End Sub

    Sub lnkCacher_Click(sender As Object, e As EventArgs)
        ' ukrywa kontener 1
        panel1.Visible=false
         ' zmienia linki
        lnkVoir.visible=true
        lnkCacher.visible=false
    End Sub
</script>

Do zainicjowania formularza użyjemy procedury [Page_Load]. Zrobimy to podczas pierwszego żądania (IsPostBack=false):

<%@ Page Language="VB" %>
<script runat="server">

    Sub Page_Load(sender As Object, e As EventArgs)
        ' za pierwszym razem
        if not IsPostBack then
            ' wyświetla się kontener
            lnkVoir_Click(nothing,nothing)
        end if
    end sub
.....
</script>

7.12. Aby kontynuować...

W poprzednich akapitach przedstawiono szereg komponentów serwerowych. Za każdym razem omówiono jedynie niektóre z ich właściwości. Aby pogłębić wiedzę na temat tych komponentów, czytelnik może skorzystać z różnych metod:

  • zapoznać się z właściwościami danego komponentu za pomocą pliku IDE, takiego jak WebMatrix. Plik ten przedstawia bowiem główne właściwości komponentów wykorzystywanych w formularzu
  • zapoznać się z dokumentacją .NET, aby poznać wszystkie klasy odpowiadające poszczególnym komponentom serwerowym. Jest to metoda preferowana w celu uzyskania pełnego opanowania komponentu. Znajdziemy w niej drzewo klas prowadzące do komponentów, a także właściwości, metody, konstruktory i zdarzenia każdej z nich. Ponadto dokumentacja zawiera czasami przykłady.

W tym rozdziale wykorzystaliśmy technikę „wszystko w jednym” ([WebMatrix]), czyli c.a.d, w ramach której umieściliśmy kod prezentacji i kod sterujący strony w tym samym pliku. Ogólnie rzecz biorąc, nie zalecamy tej metody, lecz stosowaną wcześniej metodę „[codebehind]”, w której oba rodzaje kodu umieszcza się w dwóch oddzielnych plikach. Przypominamy, że zaletą takiego rozdzielenia jest to, że kod sterujący można skompilować bez konieczności uruchamiania aplikacji internetowej. Ponadto nasze przykłady – jak wyjaśniliśmy na początku rozdziału – miały bardzo specyficzny charakter: składały się z jednej strony będącej formularzem, który był wymieniany między klientem a serwerem w kolejnych cyklach żądanie-odpowiedź, przy czym pierwsze żądanie klienta miało postać GET, a kolejne – POST.

7.13. Komponenty serwera i kontroler aplikacji

W poprzednich rozdziałach stworzyliśmy kilka aplikacji internetowych. Wszystkie zostały zbudowane zgodnie z architekturą MVC (Model-Widok-Kontroler), która dzieli aplikację na wyraźnie oddzielone bloki i ułatwia jej konserwację. Wówczas tworzyliśmy nasze interfejsy użytkownika przy użyciu standardowych tagów HTML. Biorąc pod uwagę to, co właśnie omówiliśmy, naturalnym krokiem jest teraz chęć wykorzystania komponentów serwerowych. Wróćmy do problemu, który już szczegółowo analizowaliśmy, a mianowicie obliczania podatku. Jego architektura MVC wyglądała następująco:

Image

Aplikacja posiada dwa widoki: [formulaire.aspx] oraz [erreurs.aspx]. Widok [formulaire.aspx] jest wyświetlany przy pierwszym wywołaniu adresu URL [main.aspx]:

Image

Użytkownik wypełnia formularz:

Image

i korzysta z przycisku [Calculer], aby uzyskać następującą odpowiedź:

Image

W aplikacji MVC każde żądanie musi przejść przez kontroler, w tym przypadku [main.aspx]. Oznacza to, że gdy formularz [formulaire.aspx] został wypełniony przez użytkownika, musi zostać przesłany do [main.aspx], a nie do [formulaire.aspx]. Nie jest to po prostu możliwe, jeśli tworzymy interfejs użytkownika [formulaire.aspx] przy użyciu komponentów serwerowych ASP. Aby to zweryfikować, utwórzmy formularz [formtest.aspx] z komponentem <asp:button>:

<%@ Page Language="VB" EnableViewState="false"%>
<html>
<head>
    <title>test</title>
</head>
<body>
    <form action="main.aspx" runat="server">
        <p>
            <asp:Button id="btnTest" runat="server" EnableViewState="false" Text="Test"></asp:Button>
        </p>
    </form>
</body>
</html>

Zwróćmy uwagę na atrybut [action="main.aspx"] tagu <form...>. Uruchommy tę aplikację. Strona główna zawiera tylko jeden przycisk:

Image

Przyjrzyjmy się kodowi HTML wysłanemu przez serwer:

<html>
<head>
    <title>test</title>
</head>
<body>
    <form name="_ctl0" method="post" action="formtest.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwtNTMwNzcxMzI0Ozs+" />

        <p>
            <input type="submit" name="btnTest" value="Test" id="btnTest" />
        </p>

    </form>
</body>
</html>

Widać, że kod POST z formularza odsyła do samego formularza [action="formtest.aspx"], podczas gdy w [formtest.aspx] wpisaliśmy tag HTML serwer:

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

Atrybut [runat="server"] tagu <form> jest narzucony przez wykorzystanie komponentów serwerowych. Jeśli nie umieścimy tego atrybutu, wystąpi błąd kompilacji. Po jego umieszczeniu atrybut [action] tagu <form> jest ignorowany. Serwer zawsze generuje atrybut [action], który odwołuje się do samego formularza. Wynika z tego, że w aplikacji MVC nie można używać formularzy utworzonych za pomocą tagu <form ... runat="server">. Tag ten jest jednak niezbędny dla wszystkich komponentów serwerowych ASP pobierających dane wprowadzone przez użytkownika. Można więc powiedzieć, że w aplikacji MVC nie można używać formularzy serwerowych ASP. To wielkie odkrycie. Jedną z mocnych stron marketingu ASP.NET jest bowiem to, że można tworzyć aplikacje internetowe tak samo jak aplikacje dla systemu Windows. Jest to prawdą, jeśli nasza aplikacja nie jest zgodna z architekturą MVC, ale tym bardziej prawdziwe, jeśli jest zgodna. Architektura MVC wydaje się jednak fundamentalnym pojęciem we współczesnym tworzeniu aplikacji internetowych, którego trudno zignorować.

Możliwe jest wykorzystanie architektury MVC w połączeniu z formularzami opartymi na komponentach ASP w aplikacjach zawierających niewiele różnych widoków dzięki następującemu zabiegowi:

  • aplikacja składa się z jednej strony, która pełni rolę kontrolera
  • widoki są odwzorowywane na tej stronie jako różne kontenery, po jednym kontenerze na widok. Aby wyświetlić widok, ujawnia się jego kontener i ukrywa pozostałe

Jest to eleganckie rozwiązanie, które teraz wdrażamy w kilku przykładach

7.14. Przykłady aplikacji MVC z komponentami serwerowymi ASP

7.14.1. Przykład 1

W tym pierwszym przykładzie wykorzystujemy komponenty serwerowe, które wcześniej przedstawiliśmy. Strona [form10.aspx] będzie wyglądać następująco:

Zrzut ekranu po lewej stronie powyżej przedstawia formularz w postaci, w jakiej jest wyświetlany klientowi. Klient wypełnia go i zatwierdza za pomocą kodu [Envoyer]. Serwer zwraca mu stronę zawierającą listę wprowadzonych wartości (zrzut ekranu po prawej stronie). Link umożliwia użytkownikowi powrót do formularza. Znajduje on formularz w stanie, w jakim go zatwierdził. Kod prezentacji dla [form10.aspx] jest następujący:

<html>
<head>
    <title>Exemple</title> <script language="javascript">
        function effacer(){
            alert("Vous avez cliqué sur [Effacer]")
        }
    </script>
</head>
<body>
    <p>
        Gestion d'un formulaire
    </p>
    <p>
        <hr />
    </p>
    <form runat="server">
        <p>
            <asp:Panel id="panelinfo" runat="server" EnableViewState="False">
                <p>
                    Liste des valeurs obtenues
                </p>
                <p>
                    <asp:ListBox id="lstInfos" runat="server" EnableViewState="False"></asp:ListBox>
                </p>
                <p>
                    <asp:LinkButton id="LinkButton1" onclick="LinkButton1_Click" runat="server">Retour au formulaire</asp:LinkButton>
                </p>
                <p>
                    <hr />
                </p>
            </asp:Panel>
        </p>
        <p>
            <asp:Panel id="panelform" runat="server" >
                <table>
                    <tbody>
                        <tr>
                            <td>
                                Etes-vous marié(e)</td>
                            <td>
                                <asp:RadioButton id="rdOui" runat="server"  GroupName="rdmarie"></asp:RadioButton>
                                Oui<asp:RadioButton id="rdNon" runat="server"  GroupName="rdmarie" Checked="True"></asp:RadioButton>
                                Non</td>
                        </tr>
                        <tr>
                            <td>
                                Cases à cocher</td>
                            <td>
                                <asp:CheckBox id="chk1" runat="server"></asp:CheckBox>
                                1<asp:CheckBox id="chk2" runat="server"></asp:CheckBox>
                                2<asp:CheckBox id="chk3" runat="server"></asp:CheckBox>
                                3</td>
                        </tr>
                        <tr>
                            <td>
                                Champ de saisie</td>
                            <td>
                                <asp:TextBox id="txtSaisie" runat="server"  MaxLength="20" Columns="20"></asp:TextBox>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Mot de passe</td>
                            <td>
                                <asp:TextBox id="txtmdp" runat="server"  MaxLength="10" Columns="10" TextMode="Password"></asp:TextBox>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Boîte de saisie</td>
                            <td>
                                <asp:TextBox id="txtArea" runat="server"  Columns="20" TextMode="MultiLine" Rows="3"></asp:TextBox>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Liste déroulante</td>
                            <td>
                                <asp:DropDownList id="cmbValeurs" runat="server"></asp:DropDownList>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Liste à choix unique</td>
                            <td>
                                <asp:ListBox id="lstSimple" runat="server"></asp:ListBox>
                                <asp:Button id="btnRazSimple" onclick="btnRazSimple_Click" runat="server" EnableViewState="False" Text="Raz"></asp:Button>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Liste à choix multiple</td>
                            <td>
                                <asp:ListBox id="lstMultiple" runat="server" SelectionMode="Multiple"></asp:ListBox>
                                <asp:Button id="razMultiple" onclick="razMultiple_Click" runat="server" EnableViewState="False" Text="Raz"></asp:Button>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Champ caché</td>
                            <td>
                                <asp:Label id="lblSecret" runat="server" visible="False"></asp:Label></td>
                        </tr>
                        <tr>
                            <td>
                                Bouton simple</td>
                            <td>
                                <input id="btnEffacer" onclick="effacer()" type="button" value="Effacer" /></td>
                        </tr>
                        <tr>
                            <td>
                                Bouton [reset]</td>
                            <td>
                                <input id="btnReset" type="reset" value="Rétablir" /></td>
                        </tr>
                        <tr>
                            <td>
                                Bouton [submit]</td>
                            <td>
                                <asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" EnableViewState="False" Text="Envoyer"></asp:Button>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </asp:Panel>
        </p>
    </form>
</body>
</html>

Strona zawiera dwa kontenery, po jednym dla każdego widoku: [panelform] dla widoku formularza oraz [panelinfo] dla widoku informacyjnego. Lista komponentów kontenera [panelForm] przedstawia się następująco:

nazwa
typ
właściwości
rola
panelform
Panel
EnableViewState=true
widok formularza
rdOui
rdNon
RadioButton
EnableViewState=true
GroupName=rdmarie
przyciski opcji
chk1
chk2
chk3
CheckBox
EnableViewState=true
pola wyboru
txtSaisie
TextBox
EnableViewState=true
pole wprowadzania danych
txtMdp
TextBox
EnableViewState=true
chronione pole wprowadzania danych
txtArea
TextBox
EnableViewState=true
pole wprowadzania danych wielowierszowe
cmbValeurs
DropDownList
EnableViewState=true
lista rozwijana
lstSimple
ListBox
EnableViewState=true
SelectionMode=Single
lista z wyborem pojedynczym
btnRazSimple
Przycisk
EnableViewState=false
odznacza wszystkie elementy z lstSimple
lstMultiple
ListBox
EnableViewState=true
SelectionMode=Multiple
lista z możliwością wielokrotnego wyboru
btnRazMultiple
Przycisk
EnableViewState=false
odznacza wszystkie elementy z lstMultiple
lblSecret
Etykieta
EnableViewState=true
Visible=false
ukryte pole
btnEffacer
HTML standard
 
wyświetla alert
btnEnvoyer
Przycisk
EnableViewState=false
przycisk [submit] w formularzu
btnReset
HTML standardowy
 
przycisk [reset] w formularzu

Rola właściwości [VIEWSTATE] dla komponentów jest tutaj istotna. Wszystkie komponenty z wyjątkiem przycisków muszą posiadać właściwość [EnableViewState=true]. Aby zrozumieć powód, należy przypomnieć sobie sposób działania aplikacji. Załóżmy, że pole [txtSaisie] posiada właściwość [EnableViewState=false]:

  1. klient po raz pierwszy wywołuje stronę [form10.aspx]. Otrzymuje widok formularza
  2. wypełnia go i wysyła za pomocą przycisku [Envoyer]. Pola wprowadzania danych są następnie przesyłane, a serwer przypisuje komponentom serwerowym przesłaną wartość lub ich stan [VIEWSTATE], jeśli taki istniał. W ten sposób polu [txtSaisie] przypisywana jest wartość wprowadzona przez użytkownika. Również na tym etapie jego stan [VIEWSTATE] nie ma żadnego znaczenia. W wyniku tej operacji wysyłany jest widok [informations], czyli w rzeczywistości nadal strona [form10.aspx], ale z innym wyświetlanym kontenerem.
  3. Użytkownik przegląda ten nowy widok i korzysta z linku [Retour au formulaire], aby powrócić do poprzedniego. Następnie wykonywane jest przekierowanie POST do [form10.aspx]. W tej sytuacji przesyłana jest co najwyżej jedna wartość: wartość wybrana przez użytkownika z listy informacji, która nie jest następnie wykorzystywana. W każdym razie nie ma przesłanego pola o numerze [txtSaisie].
  4. Serwer odbiera POST i przypisuje do komponentów serwera przesłaną wartość lub ich stan [VIEWSTATE], jeśli taki istniał. W tym przypadku [txtNom] nie ma przesłanej wartości. Jeśli jego atrybut [EnableViewState] ma wartość [false], zostanie mu przypisany pusty ciąg znaków. Ponieważ chcemy, aby miał on wartość wprowadzoną przez użytkownika, musi posiadać właściwość [EnableViewState=true].

Kontener [panelinfo] zawiera następujące elementy sterujące:

nazwa
typ
właściwości
rola
panelinfo
Panel
EnableViewState=false
widok informacji
lstInfos
ListBox
EnableViewState=false
lista informacji podsumowująca wartości wprowadzone przez użytkownika
LinkButton1
LinkButton
EnableViewState=false
link powrotny do formularza

Podczas testów, jeśli przyjrzymy się kodowi HTML wygenerowanemu przez powyższy kod prezentacji, możemy być zaskoczeni kodem wygenerowanym dla ukrytego pola [lblSecret]:

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

Komponent [lblSecret] nie jest przekształcany na kod HTML, ponieważ posiada właściwość [Visible=false]. Jednak ponieważ posiada on właściwość [EnableViewState=true], jego wartość zostanie mimo to zachowana w ukrytym polu [__VIEWSATE]. Dzięki temu będziemy w stanie ją odzyskać, co pokażą testy.

Pozostaje nam jeszcze napisać procedury obsługi zdarzeń. W [Page_Load] zainicjujemy formularz:

Sub page_Load(sender As Object, e As EventArgs)
         ' za pierwszym razem inicjuje się elementy
          ' przy kolejnych wywołaniach elementy te odzyskują swoje wartości za pomocą VIEWSTATE
         if IsPostBack then return
         ' inicjowanie formularza
          ' panelinfo nie jest wyświetlany
         panelinfo.visible=false
         ' panel formularza wyświetlony
         panelform.visible=true
         ' przyciski opcji
         rdNon.Checked=true
         ' pola wyboru
         chk2.Checked=true
          ' pole wprowadzania danych
         txtSaisie.Text="qqs mots"
          ' pole hasła
         txtMdp.Text="ceciestsecret"
         ' pole wprowadzania danych
         txtArea.Text="ligne"+ControlChars.CrLf+"ligne2"+ControlChars.CrLf
         ' lista rozwijana
         dim i as integer
         for i=1 to 4
             cmbValeurs.Items.Add(new ListItem("choix"+i.ToString,i.ToString))
         next
         cmbValeurs.SelectedIndex=1
         ' lista z pojedynczym wyborem
         for i=1 to 7
             lstSimple.Items.Add(new ListItem("simple"+i.ToString,i.ToString))
         next
         lstSimple.SelectedIndex=0
         ' lista z możliwością wielokrotnego wyboru
         for i=1 to 10
             lstMultiple.Items.Add(new ListItem("multiple"+i.ToString,i.ToString))
         next
         lstMultiple.Items(0).Selected=true
         lstMultiple.Items(2).Selected=true
         ' pole ukryte
         lblSecret.Text="secret"
End Sub

Kliknięcie przycisków [lstRazSimple] i [lstMultiple]:

Sub btnRazSimple_Click(sender As Object, e As EventArgs)
    ' lista pojedyncza typu raz
    lstSimple.SelectedIndex=-1
End Sub

Sub razMultiple_Click(sender As Object, e As EventArgs)
    ' pole tekstowe z wielokrotnym wyborem
    lstMultiple.SelectedIndex=-1
End Sub

Kliknięcie przycisku [Envoyer]:

Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
    ' panel informacyjny jest widoczny, a panel formularza jest ukryty
  panelinfo.Visible=true
  panelform.visible=false
  ' pobierane  wartości przesłane metodą POST i umieszczane w lstInfos
   ' przyciski opcji
  dim info as string="état marital : "+iif(rdoui.checked,"marié"," non marié")
  affiche(info)
   ' pola wyboru
  info=" cases cochées : "+iif(chk1.checked,"1 oui","1 non")+","+ _
      iif(chk2.checked,"2 oui","2 non")+","+iif(chk3.checked,"3 oui","3 non")
  affiche(info)
   ' pole tekstowe
  affiche("champ de saisie : " + txtSaisie.Text.Trim)
   ' hasło
  affiche("mot de passe : " + txtMdp.Text.Trim)
   ' pole wprowadzania danych
  dim lignes() as String
  lignes=new Regex("\r\n").Split(txtArea.Text.Trim)
  dim i as integer
  for i=0 to lignes.length-1
      lignes(i)="["+lignes(i).Trim+"]"
  next
  affiche("Boîte de saisie : " + String.Join(",",lignes))
   ' lista rozwijana
  affiche("éléments sélectionnés dans combo : "+selection(cmbValeurs))
   ' prosta lista
  affiche("éléments sélectionnés dans liste simple : "+selection(lstSimple))
   ' lista wielokrotna
  affiche("éléments sélectionnés dans liste multiple : "+selection(lstMultiple))
   ' ukryte pole
  affiche ("Champ caché : " + lblSecret.Text)
End Sub

sub affiche(msg as String)
    ' wyświetla komunikat w lstInfos
    lstInfos.Items.Add(msg)
end sub

       function selection(liste as ListControl) as string
           ' przeglądanie elementów listy
            ' w celu znalezienia tych, które  zaznaczone
           dim i as integer
           dim info as string=""
           for i=0 to liste.Items.Count-1
               if liste.Items(i).Selected then info+="[" + liste.Items(i).Text + "]"
           next
           return info
       end function

Na koniec kliknij link [Retour vers le formulaire ]:

Sub LinkButton1_Click(sender As Object, e As EventArgs)
    ' wyświetla się formularz i ukrywa panel informacyjny
    panelform.visible=true
    panelinfo.visible=false
End Sub

7.14.2. Przykład 2

W tym miejscu powracamy do aplikacji, którą już omówiliśmy, wykorzystującej standardowe formularze HTML. Aplikacja ta umożliwia przeprowadzanie symulacji obliczeń podatkowych. Opiera się ona na klasie [impot], której nie będziemy tutaj ponownie omawiać. Klasa ta wymaga danych, które pobiera ze źródła danych OLEDB. W tym przykładzie będzie to źródło ACCESS.

7.14.2.1. Struktura MVC aplikacji

Struktura MVC aplikacji wygląda następująco:

Image

Wszystkie trzy widoki zostaną wbudowane w kod prezentacyjny kontrolera [main.aspx] w postaci kontenerów. W związku z tym aplikacja ta posiada jedną stronę o nazwie [main.aspx].

7.14.2.2. Widoki aplikacji internetowej

Widok [formulaire] to formularz służący do wprowadzania informacji umożliwiających obliczenie podatku dla użytkownika:

Image

Użytkownik wypełnia formularz:

Image

Korzysta z przycisku [Envoyer], aby zlecić obliczenie swojego podatku. Otrzymuje następujący widok [simulations]:

Image

Powraca do formularza za pomocą powyższego linku. Znajduje go w stanie, w jakim go wypełnił. Może popełnić błędy przy wprowadzaniu danych:

Image

Są one mu sygnalizowane w widoku [erreurs]:

Image

Użytkownik wraca do formularza za pomocą powyższego linku. Formularz jest w takim samym stanie, w jakim został wypełniony. Użytkownik może przeprowadzić nowe symulacje:

Image

Otrzymuje wówczas widok [simulations] z kolejną symulacją:

Image

Wreszcie, jeśli źródło danych jest niedostępne, użytkownik zostanie o tym poinformowany w widoku [erreurs]:

Image

7.14.2.3. Kod prezentacji aplikacji

Przypomnijmy, że strona [main.aspx] zawiera wszystkie widoki. Jest to pojedynczy formularz z trzema kontenerami:

  • [panelform] dla widoku [formulaire]
  • [panelerreurs] dla widoku [erreurs]
  • [panelsimulations] dla widoku [simulations]

Powracamy do rozdzielenia kodu prezentacyjnego i kodu sterującego na dwa oddzielne pliki. Pierwszy z nich będzie znajdował się w pliku [main.aspx], a drugi w pliku [main.aspx.vb]. Kod z pliku [main.aspx] wygląda następująco:


<%@ page codebehind="main.aspx.vb" inherits="vs.main" AutoEventWireUp="false" %>
<HTML>
    <HEAD>
        <title>Calcul d'impôt </title>
    </HEAD>
    <body>
        <P>Calcul de votre impôt</P>
        <HR width="100%" SIZE="1">
        <FORM id="Form1" runat="server">
            <asp:panel id="panelform" Runat="server">
                <TABLE id="Table1" cellSpacing="1" cellPadding="1" border="0">
                    <TR>
                        <TD height="19">Etes-vous marié(e)</TD>
                        <TD height="19">
                            <asp:RadioButton id="rdOui" runat="server" GroupName="rdMarie"></asp:RadioButton>Oui
                            <asp:RadioButton id="rdNon" runat="server" GroupName="rdMarie" Checked="True"></asp:RadioButton>Non</TD>
                    </TR>
                    <TR>
                        <TD>Nombre d'enfants</TD>
                        <TD>
                            <asp:TextBox id="txtEnfants" runat="server" MaxLength="3" Columns="3"></asp:TextBox></TD>
                    </TR>
                    <TR>
                        <TD>Salaire annuel (euro)</TD>
                        <TD>
                            <asp:TextBox id="txtSalaire" runat="server" MaxLength="10" Columns="10"></asp:TextBox></TD>
                    </TR>
                </TABLE>
                <P>
                    <asp:Button id="btnCalculer" runat="server" Text="Calculer"></asp:Button>
                    <asp:Button id="btnEffacer" runat="server" Text="Effacer"></asp:Button></P>
            </asp:panel>
          <asp:panel id="panelerreurs" runat="server">
                <P>Les erreurs suivantes se sont produites :</P>
                <P>
                    <asp:Literal id="erreursHTML" runat="server"></asp:Literal></P>
                <P></P>
                <asp:LinkButton id="lnkForm1" runat="server">Retour au formulaire</asp:LinkButton>
            </asp:panel>
          <asp:panel id="panelsimulations" runat="server">
                <P>
                    <TABLE>
                        <TR>
                            <TH>
                                Marié</TH>
                            <TH>
                                Enfants</TH>
                            <TH>
                                Salaire annuel</TH>
                            <TH>
                                Impôt à payer (euro)</TH></TR>
                        <asp:Literal id="simulationsHTML" runat="server"></asp:Literal></TABLE>
                    <asp:LinkButton id="lnkForm2" runat="server">Retour au formulaire</asp:LinkButton></P>
            </asp:panel>
      </FORM>
    </body>
</HTML>

Wyodrębniliśmy trzy kontenery. Należy zauważyć, że wszystkie znajdują się w tagu <form runat="server">. Jest to obowiązkowe, ponieważ aby móc korzystać z zalet komponentów serwerowych, muszą one być umieszczone w takim tagu. Ważną kwestią do zapamiętania jest to, że mamy tutaj jeden formularz, który będzie wymieniany między klientem a serwerem WWW. Mamy więc do czynienia z konfiguracją wykorzystywaną w całym tym rozdziale poświęconym komponentom serwerowym. Przyjrzyjmy się bliżej komponentom każdego kontenera:

Kontener [panelform]:

nazwa
typ
właściwości
rola
panelform
Panel
EnableViewState=true
widok formularza
rdOui
rdNon
RadioButton
EnableViewState=true
GroupName=rdmarie
przyciski opcji
txtEnfants
TextBox
EnableViewState=true
liczba elementów podrzędnych
txtSalaire
TextBox
EnableViewState=true
roczne wynagrodzenie
btnCalculer
Przycisk
 
przycisk [submit] w formularzu – uruchamia obliczenie podatku
btnEffacer
Przycisk
 
Przycisk [submit] w formularzu – czyści formularz

Kontener [panelerreurs]:

nazwa
typ
właściwości
rola
panelerreurs
Panel
EnableViewState=true
widok błędów
lnkForm1
LinkButton
EnableViewState=true
link do formularza
erreursHTML
Literal
 
kod HTML z listy błędów

Kontener [panelsimulations]:

nazwa
typ
właściwości
rola
panelsimulations
Panel
EnableViewState=true
widok symulacji
lnkForm2
LinkButton
EnableViewState=true
link do formularza
simulationsHTML
Literal
 
Kod HTML z listy symulacji w tabeli HTML

7.14.2.4. Kod sterujący aplikacji

Kod kontrolny aplikacji znajduje się w plikach [global.asax.vb] i [main.aspx.vb]. Plik [global.asax] jest zdefiniowany w następujący sposób:

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

Plik [global.asax.vb] ma następującą treść:


Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Collections

Public Class Global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' tworzymy obiekt importu
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
            ' umieszczamy obiekt w aplikacji
            Application("objImpot") = objImpot
            ' brak błędu
            Application("erreur") = False
        Catch ex As Exception
            'wystąpił błąd, odnotowuje się go w aplikacji
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' początek sesji – tworzy się pustą listę symulacji
        Session.Item("simulations") = New ArrayList
    End Sub
End Class

Po uruchomieniu aplikacji (pierwsze żądanie skierowane do aplikacji) wykonywana jest procedura [Application_Start]. Ma ona na celu utworzenie obiektu typu [impot], pobierającego dane ze źródła OLEDB. Jeśli czytelnik nie pamięta, gdzie zdefiniowano tę klasę, powinien zapoznać się z rozdziałem 5. Utworzenie obiektu [impot] może się nie powieść, jeśli źródło danych jest niedostępne. W takim przypadku błąd jest zapisywany w aplikacji, aby wszystkie kolejne zapytania miały świadomość, że obiekt ten nie mógł zostać poprawnie zainicjowany. Jeśli utworzenie przebiega pomyślnie, utworzony obiekt [impot] jest również zapisywany w aplikacji. Będzie on wykorzystywany przez wszystkie zapytania dotyczące obliczania podatku. Gdy klient wysyła swoje pierwsze zapytanie, procedura [Application_Start] tworzy dla niego sesję. Sesja ta służy do przechowywania różnych symulacji obliczeń podatkowych, które klient będzie przeprowadzał. Symulacje te zostaną zapisane w obiekcie [ArrayList] powiązanym z kluczem sesji „simulations”. W momencie rozpoczęcia sesji klucz ten jest powiązany z pustym obiektem [ArrayList]. Informacje niezbędne dla aplikacji są umieszczone w jej pliku konfiguracyjnym [wenConfig]:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        <add key="chaineConnexion" value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\webforms\vs\impots5\impots.mdb" />
    </appSettings>
</configuration>

Klucz [chaineConnexion] określa ciąg połączenia ze źródłem OLEDB. Druga część kodu kontrolnego znajduje się w [main.aspx.vb]:


Imports System.Collections
Imports Microsoft.VisualBasic
Imports st.istia.univangers.fr
Imports System

Public Class main
    Inherits System.Web.UI.Page

    Protected WithEvents rdOui As System.Web.UI.WebControls.RadioButton
    Protected WithEvents rdNon As System.Web.UI.WebControls.RadioButton
    Protected WithEvents txtEnfants As System.Web.UI.WebControls.TextBox
    Protected WithEvents txtSalaire As System.Web.UI.WebControls.TextBox
    Protected WithEvents btnCalculer As System.Web.UI.WebControls.Button
    Protected WithEvents btnEffacer As System.Web.UI.WebControls.Button
    Protected WithEvents panelform As System.Web.UI.WebControls.Panel
    Protected WithEvents lnkForm1 As System.Web.UI.WebControls.LinkButton
    Protected WithEvents lnkForm2 As System.Web.UI.WebControls.LinkButton
    Protected WithEvents panelerreurs As System.Web.UI.WebControls.Panel
    Protected WithEvents panelsimulations As System.Web.UI.WebControls.Panel
    Protected WithEvents simulationsHTML As System.Web.UI.WebControls.Literal
    Protected WithEvents erreursHTML As System.Web.UI.WebControls.Literal

    ' zmienne lokalne

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
    End Sub

    Private Sub afficheFormulaire()
...
    End Sub

    Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
...
    End Sub

    Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
...
    End Sub

    Private Function checkData() As ArrayList
...
    End Function

    Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
....
    End Sub

    Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
...
    End Sub

    Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
...
    End Sub

    Private Sub razForm()
...
    End Sub
End Class

Le premier événement traité par le code est [Page_Load] :

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' najpierw sprawdzamy, w jakim stanie znajduje się aplikacja
        If CType(Application("erreur"), Boolean) Then
            ' aplikacja nie mogła się zainicjować
            ' wyświetla się widok błędów
            Dim erreurs As New ArrayList
            erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
            afficheErreurs(erreurs, "")
            Exit Sub
        End If
        ' brak błędów – przy pierwszym żądaniu wyświetlamy formularz
        If Not IsPostBack Then afficheFormulaire()
    End Sub

Należy pamiętać, że gdy procedura [Page_Load] jest wykonywana na kliencie POST, wszystkie komponenty formularza mają przypisaną wartość: albo wartość przesłaną przez klienta, jeśli taka istnieje, albo poprzednią wartość komponentu dzięki procedurze [VIEWSTATE]. W tym formularzu wszystkie komponenty posiadają właściwość [EnableViewState=true]. Przed rozpoczęciem przetwarzania żądania upewniamy się, że aplikacja została poprawnie zainicjowana. Jeśli tak nie jest, wyświetlamy widok [erreurs] za pomocą procedury [afficheErreurs]. Jeśli jest to pierwsze żądanie (IsPostBack=false), wyświetlamy widok [formulaire] za pomocą procedury [afficheFormulaire].

Procedura wyświetlająca widok [erreurs] jest następująca:


    Private Sub afficheErreurs(ByRef erreurs As ArrayList, ByRef lien As String)
        ' wyświetla się kontener błędów
        panelerreurs.Visible = True
        Dim i As Integer
        erreursHTML.Text = ""
        For i = 0 To erreurs.Count - 1
            erreursHTML.Text += "<li>" + erreurs(i).ToString + "</li>" + ControlChars.CrLf
        Next
        lnkForm1.Text = lien
        ' pozostałe kontenery są ukryte
        panelform.Visible = False
        panelsimulations.Visible = False
    End Sub

Procedura ma dwa parametry:

  • listę komunikatów o błędach w [erreurs]
  • tekst linku w [lien]

Kod HTML, który ma zostać wygenerowany dla listy błędów, jest umieszczony w literału [erreursHTML]. Tekst linku jest natomiast umieszczony we właściwości [Text] obiektu [LinkButton] widoku.

Procedura wyświetlająca widok [formulaire] jest następująca:


    Private Sub afficheFormulaire()
        ' wyświetla formularz
        panelform.Visible = True
        ' pozostałe kontenery są ukryte
        panelerreurs.Visible = False
        panelsimulations.Visible = False
    End Sub

Ta procedura służy wyłącznie do wyświetlenia kontenera [panelform]. Komponenty są wyświetlane wraz z ich aktualną lub poprzednią wartością (VIEWSTATE).

Gdy użytkownik kliknie przycisk [Calculer] w widoku [formulaire], wykonywane jest przejście z POST do [main.aspx]. Najpierw wykonywana jest procedura [Page_Load], a następnie procedura [btnCalculer_Click]:


    Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
        ' sprawdzana jest poprawność wprowadzonych danych
        Dim erreurs As ArrayList = checkData()
        ' w razie błędów prosimy o zgłoszenie
        If erreurs.Count <> 0 Then
            ' wyświetla się strona z komunikatami o błędach
            afficheErreurs(erreurs, "Retour au formulaire")
            Exit Sub
        End If
        ' brak błędów – obliczamy podatek
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        rdOui.Checked, CType(txtEnfants.Text, Integer), CType(txtSalaire.Text, Long))
        ' dodajemy wynik do istniejących symulacji
        Dim simulation() As String = New String() {CType(IIf(rdOui.Checked, "oui", "non"), String), _
         txtEnfants.Text.Trim, txtSalaire.Text.Trim, impot.ToString}
        ' dodaj wynik do istniejących symulacji
        Dim simulations As ArrayList = CType(Session.Item("simulations"), ArrayList)
        simulations.Add(simulation)
        ' umieszczamy symulacje w sesji i kontekście
        Session.Item("simulations") = simulations
        ' wyświetla się strona wyników
        afficheSimulations(simulations, "Retour au formulaire")
    End Sub

Procedura rozpoczyna się od sprawdzenia poprawności pól formularza za pomocą procedury [checkData], która zwraca listę [ArrayList] zawierającą komunikaty o błędach. Jeśli lista nie jest pusta, wyświetlany jest widok [erreurs], a procedura zostaje zakończona. Jeśli wprowadzone dane są prawidłowe, kwota podatku jest obliczana za pomocą obiektu typu [impot], który został zapisany w aplikacji podczas jej uruchomienia. Ta nowa symulacja jest dodawana do listy już wykonanych symulacji i zapisywana w sesji.

Funkcja [CheckData] sprawdza poprawność danych. Zwraca listę [ArrayList] zawierającą komunikaty o błędach; lista ta jest pusta, jeśli dane są poprawne:


    Private Function checkData() As ArrayList
        ' na początku brak błędów
        Dim erreurs As New ArrayList
        ' liczba dzieci
        Try
            Dim nbEnfants As Integer = CType(txtEnfants.Text, Integer)
            If nbEnfants < 0 Then Throw New Exception
        Catch
            erreurs.Add("Le nombre d'enfants est incorrect")
        End Try
        ' wynagrodzenie
        Try
            Dim salaire As Long = CType(txtSalaire.Text, Long)
            If salaire < 0 Then Throw New Exception
        Catch
            erreurs.Add("Le salaire annuel est incorrect")
        End Try
        ' wyświetla listę błędów
        Return erreurs
    End Function

Wreszcie widok [simulations] jest wyświetlany przez następującą procedurę [afficheSimulations]:


    Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
        ' wyświetla widok symulacji
        panelsimulations.Visible = True
        ' pozostałe kontenery są ukryte
        panelerreurs.Visible = False
        panelform.Visible = False
        ' zawartość widoku symulacji
        ' każda symulacja to tablica zawierająca 4 elementy typu string
        Dim simulation() As String
        Dim i, j As Integer
        simulationsHTML.Text = ""
        For i = 0 To simulations.Count - 1
            simulation = CType(simulations(i), String())
            simulationsHTML.Text += "<tr>"
            For j = 0 To simulation.Length - 1
                simulationsHTML.Text += "<td>" + simulation(j) + "</td>"
            Next
            simulationsHTML.Text += "</tr>" + ControlChars.CrLf
        Next
        ' link
        lnkForm2.Text = lien
    End Sub

Procedura ma dwa parametry:

  • listę symulacji w [simulations]
  • tekst linku w [lien]

Kod HTML, który ma zostać wygenerowany dla listy symulacji, jest umieszczony w literału [simulationsHTML]. Tekst linku jest natomiast umieszczony we właściwości [Text] obiektu [LinkButton] w widoku.

Gdy użytkownik kliknie przycisk [Effacer] w widoku [formulaire], uruchamia się procedura [btnEffacer_click] (zawsze po [Page_Load]):


    Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
        ' wyświetla pusty formularz
        razForm()
        afficheFormulaire()
    End Sub

    Private Sub razForm()
        ' czyści formularz
        rdOui.Checked = False
        rdNon.Checked = True
        txtEnfants.Text = ""
        txtSalaire.Text = ""
    End Sub

Powyższy kod jest na tyle prosty, że nie wymaga komentarzy. Pozostaje nam jeszcze obsłużyć kliknięcia w linki w widokach [erreurs] i [simulations]:


    Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
        ' wyświetla formularz
        afficheFormulaire()
    End Sub

    Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
        ' wyświetla formularz
        afficheFormulaire()
    End Sub

Obie procedury ograniczają się do wyświetlenia widoku [formulaire]. Wiemy, że pola w tym widoku otrzymają wartość, która będzie albo wartością przesłaną dla nich, albo ich poprzednią wartością. Ponieważ w tym przypadku widok POST klienta nie przesyła żadnych wartości dla pól formularza, pola te przywrócą swoje poprzednie wartości. Formularz jest zatem wyświetlany z wartościami wprowadzonymi przez użytkownika. Pamiętamy, że w wersji bez komponentów serwerowych sami zajmowaliśmy się tym przywracaniem wartości.

7.14.2.5. Testy

Wszystkie pliki niezbędne do działania aplikacji znajdują się w folderze <application-path>:
Folder [bin] zawiera bibliotekę DLL z klasami [impot], [impotsData], [impotsOLEDB] niezbędnymi dla działania aplikacji:

Czytelnik może, jeśli chce, ponownie zapoznać się z rozdziałem 5, w którym wyjaśniono sposób tworzenia powyższego pliku [impot.dll]. Następnie uruchamia się serwer Cassini z parametrami (<ścieżka-do-aplikacji>,/impots5). W przeglądarce wpisuje się adres URL [http://impots5/main.aspx]:

Image

Jeśli zmienimy nazwę pliku ACCESS [impots.mdb] na [impots1.mdb], wyświetli się następująca strona:

Image

7.14.3. Przykład 3

Na tych dwóch przykładach pokazaliśmy, że możliwe jest tworzenie aplikacji internetowych zgodnych z architekturą MVC przy użyciu komponentów serwerowych. Ostatni przykład pokazuje, że rozwiązanie z komponentami serwerowymi jest prostsze niż rozwiązanie wykorzystujące standardowe tagi HTML. W obu naszych przykładach była tylko jedna strona z wieloma widokami w obrębie tej samej strony. Można zastosować architekturę MVC z wieloma formularzami serwerowymi ASP, o ile fakt, że formularze te wysyłają wartości do siebie samych, nie stanowi problemu. Bardzo często ma to miejsce w przypadku aplikacji z menu. Weźmy następujący przykład:

Image

Na jednej stronie zebraliśmy linki do aplikacji, które stworzyliśmy do tej pory. Tego typu aplikacja dobrze nadaje się do architektury MVC. Różnica polega po prostu na tym, że zamiast jednego kontrolera mamy ich kilka.

Image

Kontroler [main.aspx] pełni rolę kontrolera głównego. To właśnie on jest wywoływany przez linki na stronie głównej aplikacji. Będzie on w stanie wykonać operacje wspólne dla wszystkich możliwych akcji, a następnie zleci wykonanie konkretnej akcji powiązanej z używanym linkiem. Następnie przekaże kontrolę jednemu z kontrolerów pomocniczych, odpowiedzialnemu za wykonanie tej akcji. Od tego momentu komunikacja odbywa się między klientem a tym konkretnym kontrolerem. Nie przechodzi się już przez główny kontroler [main.aspx]. Nie znajdujemy się więc już w ramach MVC z pojedynczym kontrolerem, który filtruje wszystkie żądania. Każdy z powyższych kontrolerów może prezentować wiele widoków za pomocą mechanizmu kontenerów w ramach jednej strony, którą przedstawiliśmy.

Fakt, że nie mamy już jednego kontrolera, który wybiera widoki do wysłania do klienta, wiąże się z pewnymi niedogodnościami. Weźmy na przykład obsługę błędów. Każda z akcji udostępnianych przez aplikację może wymagać wyświetlenia widoku błędów. Każdy kontroler [applix.aspx] będzie miał swój własny widok [erreurs], ponieważ jest to po prostu konkretny kontener strony kontrolera. Nie ma możliwości posiadania jednego widoku [erreurs], który byłby używany przez wszystkie poszczególne aplikacje. W rzeczywistości taki widok zazwyczaj zawiera link powrotny do błędnego formularza, a ten musi zostać przywrócony do stanu, w którym został zatwierdzony, aby umożliwić użytkownikowi poprawienie błędów. Przywrócenie to odbywa się za pomocą mechanizmu [VIEWSTATE], który nie działa między różnymi kontrolerami. Jeśli aplikacje są tworzone przez różne osoby, istnieje ryzyko, że strony błędów będą wyglądały inaczej w zależności od akcji wybranej przez użytkownika, co zakłóci spójność całej aplikacji. Nieco później zobaczymy, że ASP.NET oferuje rozwiązanie tego konkretnego problemu związanego ze wspólnym widokiem. Może on stać się przedmiotem nowego komponentu serwerowego, który sami tworzymy. Wystarczy użyć tego komponentu w różnych aplikacjach, aby zapewnić spójność całej aplikacji. Trudniejszy do opanowania jest problem kolejności akcji. Gdy wszystkie żądania przechodzą przez jeden kontroler, może on sprawdzić, czy żądana akcja jest zgodna z poprzednią. Ten kod kontrolny znajduje się w jednym miejscu. W tym przypadku trzeba go rozdzielić między różne kontrolery, co komplikuje utrzymanie całej aplikacji.

Wróćmy do naszej powyższej aplikacji. Jej strona startowa to klasyczna strona HTML:

<html>
    <head>
        <TITLE>Composants ASP Serveur</TITLE>
        <meta name="pragma" content="no-cache">
    </head>
    <frameset rows="130,*" frameborder="0">
        <frame name="banner" src="bandeau.htm" scrolling="no">
        <frameset cols="200,*">
            <frame name="contents" src="options.htm">
            <frame name="main" src="main.htm">
        </frameset>
        <noframes>
            <p id="p1">
                Ce jeu de frames HTML affiche plusieurs pages Web. Pour afficher ce jeu de 
                frames, utilisez un navigateur Web qui prend en charge HTML 4.0 et version 
                ultérieure.
            </p>
        </noframes>
    </frameset>
</html>

Ta strona główna składa się z trzech ramek o nazwach „banner”, „contents” i „main”:

Strona [bandeau.htm] umieszczona w ramce [banner] wygląda następująco:

Image

Jej kod HTML wygląda następująco:


<html>
    <head>
        <META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE" />
        <title>bandeau</title>
    </head>
    <body>
        <P>
            <TABLE>
                <TR>
                    <TD><IMG alt="logo université d'angers" src="univ01.gif"></TD>
                    <TD>Composants serveurs ASP</TD>
                </TR>
            </TABLE>
        </P>
        <HR>
    </body>
</html>

Strona [options.htm] znajduje się w pasku [contents]. Jest to zbiór linków:


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

Wszystkie linki prowadzą do głównego kontrolera [main.aspx], a parametr [action] określa, jakie działanie ma zostać wykonane. Żądamy, aby miejsce docelowe linków zostało wyświetlone w ramce [main] (target="main").

Pierwszą stroną wyświetlaną w ramce [main] jest [main.htm]:


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

Główny kontroler [main.aspx, main.aspx.vb] ma następujące parametry:

[main.aspx]

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

[main.aspx.vb]

Public Class main

Dziedziczy po System.Web.UI.Page

Private Sub Page_Load(ByVal nadawca As System.Object, ByVal odbiorca As System.EventArgs) Za pomocą uchwytu MyBase.Load

' pobieramy akcję do wykonania

Dim action As String

If Request.QueryString("action") Is Nothing Then

action = "label"

Else

action = Request.QueryString("action").ToString.ToLower

End If

' wykonujemy akcję

Select Case action

Case "label"

Server.Transfer("form2.aspx")

Case „button”

Server.Transfer("form3.aspx")

Case „textbox1”

Server.Transfer („form4.aspx”)

Case „textbox2”

Server.Transfer („form5.aspx”)

Case „dropdownlist”

Server.Transfer („form6.aspx”)

Pole „listbox”

Server.Transfer („form7.aspx”)

Pole „casesacocher”

Server.Transfer („form8.aspx”)

Pole „listecasesacocher”

Server.Transfer („form8b.aspx”)

Pole „panel”

Server.Transfer („form9.aspx”)

Przypadek Else

Server.Transfer("form2.aspx")

Koniec wyboru

Koniec procedury

End Class

Nasz kontroler jest prosty. W zależności od wartości parametru [action] przekazuje obsługę żądania do odpowiedniej strony. Nie wnosi on żadnej wartości dodanej w porównaniu ze stroną HTML zawierającą linki. Niemniej jednak wystarczyłoby dodać stronę uwierzytelniającą, aby dostrzec jego zalety. Gdyby użytkownik musiał się uwierzytelnić (login, hasło), aby uzyskać dostęp do aplikacji, kontroler [main.aspx] byłby dobrym miejscem do sprawdzenia, czy uwierzytelnienie zostało przeprowadzone.