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:
- 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).
- Następnie wykonywany jest kod odpowiedzialny za prezentację strony .aspx, który jest przekształcany w kod HTML wysyłany do klienta.

ASP.NET oferuje trzy biblioteki tagów do pisania kodu prezentacyjnego strony:
- klasyczne tagi HTML. Z nich korzystaliśmy do tej pory.
- tagi serwerowe HTML
- 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
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:
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
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:
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:
- aplikacja internetowa będzie składać się z jednej strony P zawierającej formularz F,
- 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,
- 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.
- 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:

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:

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

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


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:

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

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:

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:

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:

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:

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:

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:

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

Ta strona, zbudowana przy użyciu WebMatrix, składa się z trzech elementów:
nr | nazwa | typ | właściwości | rola |
1 | Przycisk | text=Przycisk1 | przycisk przesyłania | |
2 | Przycisk | text=Przycisk2 | przycisk przesyłania | |
3 | 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ę:

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
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ą:
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:
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ę:

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

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:

4321Ta strona, utworzona przy użyciu WebMatrix, zawiera następujące elementy:
nr | nazwa | typ | właściwości | rola |
1 | TextBox | AutoPostback=true Text= | pole wprowadzania danych | |
2 | TextBox | AutoPostback=false Text= | pole wprowadzania danych | |
3 | Etykieta | text= | komunikat informacyjny dotyczący zawartości pliku [TextBox1] | |
3 | 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ę:

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]:
Przeprowadźmy nasz pierwszy test. Wpiszmy tekst w pierwszym polu wprowadzania danych:

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

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:

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

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

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:

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

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:

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

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

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:

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:

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:
chcemy zachować stan tego komponentu, aby serwer wiedział, czy uległ on zmianie, czy nie | |
to samo | |
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] | |
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

, teraz otrzymujemy:
version 2

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:
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 |
<input type="text" ...> | |
<textarea>...</textarea> | |
<input type="password ...> |
Zastosowanie tych właściwości omówimy na podstawie poniższego przykładu [form5.aspx]:

nr | nazwa | typ | właściwości | rola |
1 | Przycisk | przycisk [submit] – służy do dodania zawartości [TextBox1] do zawartości [TextBox2] | ||
2 | TextBox | TextMode=Password Text= | pole z zabezpieczonym wprowadzaniem danych | |
3 | 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:

nr | nazwa | typ | właściwości | rola |
1 | DropDownList | AutoPostback=true EnableViewState=true | lista rozwijana | |
2 | 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]:
kolekcja typu [ListItemCollection] zawierająca elementy listy rozwijanej. Elementy tej kolekcji są typu [ListItem]. | |
liczba elementów w kolekcji [Items] | |
element nr i z listy – typu [ListItem] | |
aby dodać nowy element [ListItem] do kolekcji [Items] | |
aby usunąć wszystkie elementy z kolekcji [Items] | |
aby usunąć element nr i z kolekcji [Items] | |
pierwszy element [ListItem] z kolekcji [Items], którego właściwość [Selected] ma wartość „prawda” | |
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>:
Opisujemy niektóre właściwości i metody klasy [ListItem]:
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 | |
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 | |
tekst T opcji HTML <option value="V" [selected="selected"]>T</option> | |
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:
jest pierwszym elementem [ListItem] na liście, którego atrybut [Selected] ma wartość „true” | |
odpowiada części [texte] tagu HTML elementu <option value="...">tekst</option> wybranego przez użytkownika | |
odpowiada części [value] tagu HTML elementu <option value="...">tekst</option> wybranego przez użytkownika | |
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:

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

nr | nazwa | typ | właściwości | rola |
1 | 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 | CheckBox | Checked=false dla wszystkich CheckBoxA.Text=A CheckBoxB.Text=B CheckBoxC.Text=C | pola wyboru | |
3 | Przycisk | przycisk [submit] | ||
4 | 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>
<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:
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:
zwraca zbiór elementów podrzędnych z [Control], jeśli takie istnieją | |
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") | |
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].
kolekcja typu [ListItemCollection] zawierająca elementy listy rozwijanej. Elementy tej kolekcji są typu [ListItem]. | |
liczba elementów kolekcji [Items] | |
element nr i z listy – typu [ListItem] | |
aby dodać nowy element [ListItem] do kolekcji [Items] | |
aby usunąć wszystkie elementy z kolekcji [Items] | |
aby usunąć element nr i z kolekcji [Items] | |
pierwszy element [ListItem] z kolekcji [Items], którego właściwość [Selected] ma wartość „prawda” | |
numer poprzedniego elementu w kolekcji [Items] |
Niektóre właściwości są specyficzne dla klas [CheckBoxList] i [RadioButtonList]:
[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]:
Lub
Opisujemy niektóre właściwości i metody klasy [ListItem]:
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. | |
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 | |
tekst T opcji HTML <input type=".." value="V" [selected="selected"]>T | |
wartość atrybutu Value opcji HTML <input type=".." value="V" [selected="selected"]>T |
Proponujemy stworzyć następującą stronę [form8b.aspx]:

nr | nazwa | typ | właściwości | rola |
1 | RadioButtonList | EnableViewState=true RepeatDirection=horizontal | lista przycisków opcji | |
2 | CheckBoxList | EnableViewState=true RepeatDirection=horizontal | lista pól wyboru | |
3 | Przycisk | przycisk [submit], który wyświetla w [4] listę elementów wybranych z obu list | ||
4 | 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:

nr | nazwa | typ | właściwości | rola |
1 | Panel | EnableViewState=true | kontener elementów sterujących | |
2 | ListBox | EnableViewState=true | lista trzech wartości | |
3 | LinkButton | EnableViewState=false | link do ukrycia kontenera |
Gdy kontener jest ukryty, pojawia się nowy link:

nr | nazwa | typ | właściwości | rola |
4 | 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:

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

Użytkownik wypełnia formularz:

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

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:

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:
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 |
Panel | EnableViewState=true | widok formularza | |
RadioButton | EnableViewState=true GroupName=rdmarie | przyciski opcji | |
CheckBox | EnableViewState=true | pola wyboru | |
TextBox | EnableViewState=true | pole wprowadzania danych | |
TextBox | EnableViewState=true | chronione pole wprowadzania danych | |
TextBox | EnableViewState=true | pole wprowadzania danych wielowierszowe | |
DropDownList | EnableViewState=true | lista rozwijana | |
ListBox | EnableViewState=true SelectionMode=Single | lista z wyborem pojedynczym | |
Przycisk | EnableViewState=false | odznacza wszystkie elementy z lstSimple | |
ListBox | EnableViewState=true SelectionMode=Multiple | lista z możliwością wielokrotnego wyboru | |
Przycisk | EnableViewState=false | odznacza wszystkie elementy z lstMultiple | |
Etykieta | EnableViewState=true Visible=false | ukryte pole | |
HTML standard | wyświetla alert | ||
Przycisk | EnableViewState=false | przycisk [submit] w formularzu | |
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]:
- klient po raz pierwszy wywołuje stronę [form10.aspx]. Otrzymuje widok formularza
- 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.
- 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].
- 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 |
Panel | EnableViewState=false | widok informacji | |
ListBox | EnableViewState=false | lista informacji podsumowująca wartości wprowadzone przez użytkownika | |
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]:
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 są 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 są 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:

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:

Użytkownik wypełnia formularz:

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

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:

Są one mu sygnalizowane w widoku [erreurs]:

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:

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

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

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 |
Panel | EnableViewState=true | widok formularza | |
RadioButton | EnableViewState=true GroupName=rdmarie | przyciski opcji | |
TextBox | EnableViewState=true | liczba elementów podrzędnych | |
TextBox | EnableViewState=true | roczne wynagrodzenie | |
Przycisk | przycisk [submit] w formularzu – uruchamia obliczenie podatku | ||
Przycisk | Przycisk [submit] w formularzu – czyści formularz |
Kontener [panelerreurs]:
nazwa | typ | właściwości | rola |
Panel | EnableViewState=true | widok błędów | |
LinkButton | EnableViewState=true | link do formularza | |
Literal | kod HTML z listy błędów |
Kontener [panelsimulations]:
nazwa | typ | właściwości | rola |
Panel | EnableViewState=true | widok symulacji | |
LinkButton | EnableViewState=true | link do formularza | |
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:
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]:

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

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:

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.

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:

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:
![]() | |
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]:
|
Główny kontroler [main.aspx, main.aspx.vb] ma następujące parametry:
[main.aspx]
[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.




