8. Komponenty serwera ASP – 2
8.1. Introduction
Kontynuujemy pracę nad interfejsem użytkownika, zapoznając się z:
- komponentów do walidacji danych
- sposób powiązania danych z komponentami serwerowymi
- komponenty serwerowe HTML
8.2. Komponenty do walidacji danych
8.2.1. Wprowadzenie
W aplikacjach opartych na formularzach niezwykle ważne jest sprawdzanie poprawności danych wprowadzanych do formularzy. W przykładzie dotyczącym obliczania podatku mieliśmy następujący formularz:

Po otrzymaniu wartości z tego formularza musimy sprawdzić poprawność wprowadzonych danych: liczba dzieci i wynagrodzenie muszą być liczbami całkowitymi dodatnimi lub zerowymi. Jeśli tak nie jest, formularz jest odsyłany do klienta w takiej postaci, w jakiej został wypełniony, wraz z komunikatami wskazującymi błędy. Zająłem się tym przypadkiem za pomocą dwóch widoków, jednego dla powyższego formularza, a drugiego dla błędów:

ASP.NET oferuje komponenty zwane komponentami walidacyjnymi, które umożliwiają sprawdzenie następujących przypadków:
Komponent | Rola |
sprawdza, czy pole nie jest puste | |
porównuje dwie wartości ze sobą | |
sprawdza, czy wartość mieści się w przedziale | |
sprawdza, czy pole spełnia wyrażenie regularne | |
umożliwia programiście zdefiniowanie własnych reguł walidacji – ten komponent mógłby zastąpić wszystkie pozostałe | |
umożliwia zebranie komunikatów o błędach wygenerowanych przez poprzednie kontrole w jednym miejscu na stronie |
Teraz przedstawimy każdy z tych komponentów.
8.2.2. RequiredFieldValidator
Tworzymy następującą stronę [requiredfieldvalidator1.aspx]:
![]() |
nr | nazwa | typ | właściwości | rola |
1 | TextBox | EnableViewState=true | pole wprowadzania danych | |
2 | RequiredFieldValidator | EnableViewState=false EnableClientScript=true ErrorMessage=Pole [nazwa] jest obowiązkowe | komponent walidacji | |
3 | Przycisk | EnableViewState=false CausesValidation=true | przycisk [submit] |
Pole [RequiredFieldValidator1] służy do wyświetlania komunikatu o błędzie, jeśli pole [txtNom] jest puste lub zawiera ciąg spacji. Jego właściwości są następujące:
pole, którego wartość musi być sprawdzana przez komponent. Co oznacza wartość komponentu? Jest to wartość atrybutu [value] odpowiadającego tagowi HTML. W przypadku [TextBox] będzie to zawartość pola wprowadzania danych, a w przypadku [DropDownList] – wartość wybranego elementu. | |
wartość logiczna – wartość „prawda” oznacza, że zawartość poprzedniego pola musi zostać sprawdzona również po stronie klienta. W takim przypadku formularz zostanie wysłany przez przeglądarkę tylko wtedy, gdy nie zawiera on błędów. Niemniej jednak testy walidacji są przeprowadzane również na serwerze na wypadek, gdyby klientem nie była na przykład przeglądarka. | |
komunikat o błędzie, który komponent ma wyświetlić w przypadku wykrycia błędu |
Weryfikacja danych na stronie za pomocą kontroli walidacyjnych odbywa się tylko wtedy, gdy przycisk lub link, który wywołał element [POST] na stronie, posiada właściwość [CausesValidation=true]. [true] jest wartością domyślną tej właściwości.
Uruchommy tę aplikację. Najpierw użyjemy przeglądarki o identyfikatorze [Mozilla]:

Kod HTML otrzymany przez [Mozilla] wygląda następująco:
<html>
<head>
</head>
<body>
<form name="_ctl0" method="post" action="requiredfieldvalidator1.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNDI1MDc1NTU1Ozs+SGtdZvVxefDCDxnsqbDnqCaROsk=" />
<p>
Demande du dossier de candidature au DESS
</p>
<fieldset>
<legend>[--Identité--]</legend>
<table>
<tbody>
<tr>
<td>
Nom*</td>
<td>
<input name="txtNom" type="text" id="txtNom" />
</td>
</tr>
</tbody>
</table>
</fieldset>
<p>
<input type="submit" name="btnEnvoyer" value="Envoyer" onclick="if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate(); " language="javascript" id="btnEnvoyer" />
</p>
</form>
</body>
</html>
Widać, że przycisk [btnEnvoyer] jest powiązany z funkcją JavaScript poprzez swój atrybut [onclick]. Można również zauważyć, że strona nie zawiera żadnego kodu [Javascript]. Test [typeof(Page_ClientValidate) == 'function'] zakończy się niepowodzeniem, a funkcja JavaScript nie zostanie wywołana. Formularz zostanie wówczas przesłany na serwer, który przeprowadzi kontrolę poprawności danych. Użyjmy przycisku [Envoyer] bez podawania nazwy. Odpowiedź serwera jest następująca:

Co się stało? Formularz został wysłany na serwer. Serwer uruchomił kod wszystkich komponentów walidacyjnych znajdujących się na stronie. W tym przypadku jest tylko jeden. Jeśli choćby jeden komponent walidacyjny wykryje błąd, serwer zwraca formularz w stanie, w jakim został wypełniony (w rzeczywistości dla komponentów o identyfikatorze [EnableViewState=true], wraz z komunikatem o błędzie dla każdego komponentu walidacyjnego, który wykrył błąd). Komunikat ten stanowi atrybut [ErrorMessage] komponentu walidacyjnego. Właśnie ten mechanizm obserwujemy powyżej. Jeśli wpiszemy coś w polu [nom], komunikat o błędzie nie pojawi się już podczas walidacji formularza:

Teraz skorzystajmy z przeglądarki Internet Explorer i wywołajmy adres URL [http://localhost/requiredfieldvalidator1.aspx]. Otrzymujemy następującą stronę:

Kod HTML otrzymany przez IE wygląda następująco:
<html>
<head>
</head>
<body>
<form name="_ctl0" method="post" action="requiredfieldvalidator1.aspx" language="javascript" onsubmit="ValidatorOnSubmit();" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNDI1MDc1NTU1Ozs+SGtdZvVxefDCDxnsqbDnqCaROsk=" />
<script language="javascript" src="/aspnet_client/system_web/1_1_4322/WebUIValidation.js"></script>
<p>
Demande du dossier de candidature au DESS
</p>
<fieldset>
<legend>[--Identité--]</legend>
<table>
<tbody>
<tr>
<td>
Nom*</td>
<td>
<input name="txtNom" type="text" id="txtNom" />
<span id="RequiredFieldValidator1" controltovalidate="txtNom" errormessage="Le champ [nom] est obligatoire" evaluationfunction="RequiredFieldValidatorEvaluateIsValid" initialvalue="" style="color:Red;visibility:hidden;">Le champ [nom] est obligatoire</span>
</td>
</tr>
</tbody>
</table>
</fieldset>
<p>
<input type="submit" name="btnEnvoyer" value="Envoyer" onclick="if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate(); " language="javascript" id="btnEnvoyer" />
</p>
<script language="javascript">
<!--
var Page_Validators = new Array(document.all["RequiredFieldValidator1"]);
// -->
</script>
<script language="javascript">
<!--
var Page_ValidationActive = false;
if (typeof(clientInformation) != "undefined" && clientInformation.appName.indexOf("Explorer") != -1) {
if (typeof(Page_ValidationVer) == "undefined")
alert("Impossible de trouver la bibliothèque de scripts /aspnet_client/system_web/1_1_4322/WebUIValidation.js. Essayez de placer ce fichier manuellement ou effectuez une réinstallation en exécutant 'aspnet_regiis -c'.");
else if (Page_ValidationVer != "125")
alert("Cette page utilise une version incorrecte de WebUIValidation.js. La page requiert la version 125. La bibliothèque de scripts est " + Page_ValidationVer + ".");
else
ValidatorOnLoad();
}
function ValidatorOnSubmit() {
if (Page_ValidationActive) {
ValidatorCommonOnSubmit();
}
}
// -->
</script>
</form>
</body>
</html>
Widać, że ten kod jest znacznie dłuższy niż ten otrzymany przez [Mozilla]. Nie będziemy wchodzić w szczegóły. Różnica wynika z faktu, że serwer dołączył funkcje JavaScript do wysłanego dokumentu HTML. Dlaczego istnieją dwa różne kody HTML, skoro adres URL żądany przez obie przeglądarki jest taki sam? Mieliśmy już okazję wspomnieć, że technologia ASP.NET sprawia, że serwer dostosowuje dokument HTML wysyłany do klienta do możliwości tego klienta. Na rynku dostępne są różne przeglądarki, które nie wszystkie mają takie same możliwości. Na przykład przeglądarki Microsoft i Netscape nie stosują tego samego modelu obiektowego dla otrzymywanego dokumentu. W związku z tym kod JavaScript służący do manipulowania tym dokumentem po stronie przeglądarki różni się w obu przeglądarkach. Podobnie producenci tworzą kolejne wersje swoich przeglądarek, nieustannie ulepszając ich możliwości. W związku z tym dokument HTML, który jest rozumiany przez IE5, może nie być rozumiany przez IE3. Powyżej przedstawiono przykład takiego dostosowania serwera do klienta. Z przyczyny, która nie została tutaj szczegółowo omówiona, serwer WWW uznał, że klient [Mozilla] nie posiada możliwości obsługi kodu JavaScript służącego do walidacji. W związku z tym kod ten nie został umieszczony w wysłanym do niego dokumencie HTML, a walidacja odbyła się po stronie serwera. W przypadku pliku [IE6] kod JavaScript został dołączony do wysłanego dokumentu HTML, co widać na pierwszy rzut oka. Aby zobaczyć, jak to działa, przeprowadźmy następujący eksperyment:
- zatrzymajmy serwer WWW
- użyjmy przycisku [Envoyer] bez wypełniania pola [nom]
Otrzymujemy następującą nową stronę:

To rzeczywiście przeglądarka wyświetliła komunikat o błędzie. Serwer jest bowiem wyłączony. Można się o tym przekonać, wpisując coś w polu [nom] i klikając przycisk potwierdzenia. Tym razem otrzymujemy następującą odpowiedź:

Co się stało? Przeglądarka wykonała kod JavaScript odpowiedzialny za sprawdzanie poprawności. Kod ten stwierdził, że strona jest poprawna. Przeglądarka wysłała więc formularz do serwera WWW, który był wyłączony. Przeglądarka to wykryła i wyświetliła powyższą stronę. Jeśli ponownie uruchomimy serwer, wszystko wraca do normy.
Co można wyciągnąć z tego przykładu?
- korzyści płynące z koncepcji komponentu walidacyjnego, który umożliwia odesłanie klientowi każdego nieprawidłowego formularza
- korzyści płynące z kodu [VIEWSTATE], który umożliwia odesłanie formularza w takiej postaci, w jakiej został wypełniony
- zdolność serwera do dostosowania się do klienta. Klient jest identyfikowany na podstawie nagłówka HTTP [User-Agent:], który wysyła do serwera. Nie jest to więc serwer, który „zgaduje”, z kim ma do czynienia. Ta zdolność dostosowywania się jest bardzo przydatna dla programisty, który nie musi martwić się o typ klienta swojej aplikacji.
8.2.3. CompareValidator
Tworzymy następującą stronę [comparevalidator1.aspx]:

nr | nazwa | typ | właściwości | rola |
1 | DropDownList | EnableViewState=true | lista rozwijana | |
2 | DropDownList | EnableViewState=true | lista rozwijana | |
3 | CompareValidator | EnableViewState=false EnableClientScript=true ErrorMessage=Nieprawidłowy wybór 1 WartośćDoPorównania=? Operator=NieRównaSię Typ=ciąg znaków | komponent walidacyjny | |
4 | CompareValidator | EnableViewState=false EnableClientScript=true ErrorMessage=Nieprawidłowy wybór 2 ControlToCompare=? Operator=Nie równe Typ=ciąg znaków | komponent walidacji | |
5 | Przycisk | EnableViewState=false CausesValidation=true | przycisk [submit] |
Komponent [CompareValidator] służy do porównywania dwóch wartości. Dostępne operatory to [Equal, NotEqual, LessThan, LessThanEqual, GreaterThan, GreaterThanEqual]. Pierwsza wartość jest ustalana przez właściwość [ControlToValidate], a druga przez [ValueToCompare], jeśli poprzednia wartość ma być porównywana ze stałą, lub przez [ControlToCompare], jeśli ma być porównywana z wartością innego komponentu. Istotne właściwości komponentu [CompareValidator] są następujące:
pole, którego zawartość ma być sprawdzana przez komponent | |
wartość logiczna – wartość „prawda” oznacza, że zawartość poprzedniego pola musi być sprawdzana również po stronie klienta | |
komunikat o błędzie, który komponent ma wyświetlić w przypadku wykrycia błędu | |
wartość, z którą należy porównać wartość pola [ControlToValidate] | |
komponent, z którego wartością należy porównać wartość pola [ControlToValidate] | |
operator porównania między tymi dwiema wartościami | |
typ porównywanych wartości |
Kod prezentacji tej strony jest następujący:
<html>
<head>
</head>
<body>
<form runat="server">
<p>
Demande du dossier de candidature au DESS
</p>
<fieldset>
<legend>[--Options du DESS pour lesquelles vous candidatez--]</legend>
<table>
<tbody>
<tr>
<td>
Option1*</td>
<td>
Option2</td>
</tr>
<tr>
<td>
<asp:DropDownList id="cmbChoix1" runat="server">
<asp:ListItem Value="?" Selected="True">?</asp:ListItem>
<asp:ListItem Value="Automatique">Automatique</asp:ListItem>
<asp:ListItem Value="Informatique">Informatique</asp:ListItem>
</asp:DropDownList>
</td>
<td>
<asp:DropDownList id="cmbChoix2" runat="server">
<asp:ListItem Value="?">?</asp:ListItem>
<asp:ListItem Value="Automatique">Automatique</asp:ListItem>
<asp:ListItem Value="Informatique">Informatique</asp:ListItem>
</asp:DropDownList>
</td>
</tr>
<tr>
<td>
<asp:CompareValidator id="CompareValidator1" runat="server" ErrorMessage="Choix 1 invalide" ControlToValidate="cmbChoix1" ValueToCompare="?" Operator="NotEqual"></asp:CompareValidator>
</td>
<td>
<asp:CompareValidator id="CompareValidator2" runat="server" ErrorMessage="Choix 2 invalide" ControlToValidate="cmbChoix2" Operator="NotEqual" ControlToCompare="cmbChoix1"></asp:CompareValidator>
</td>
</tr>
</tbody>
</table>
</fieldset>
<p>
<asp:Button id="btnEnvoyer" runat="server" Text="Envoyer"></asp:Button>
</p>
</form>
</body>
</html>
Ograniczenia dotyczące walidacji strony są następujące:
- wartość wybrana w [cmbChoix1] musi różnić się od ciągu znaków „?”. Oznacza to następujące właściwości dla komponentu [CompareValidator1]: Operator=NotEqual, ValueToCompare=?, Typ=ciąg znaków
- wartość wybrana w [cmbChoix2] musi różnić się od wartości wybranej w [cmbChoix1]. Oznacza to następujące właściwości dla komponentu [CompareValidator2]: Operator=NotEqual, ControlToCompare=cmbChoix1, Typ=ciąg znaków
Uruchamiamy tę aplikację. Oto przykład strony otrzymanej podczas wymiany danych między klientem a serwerem:

Jeśli ta sama strona jest wyświetlana w przeglądarce Internet Explorer 6, zawiera ona kod JavaScript, który powoduje nieco inne działanie. Ewentualne błędy są sygnalizowane natychmiast po zmianie wartości przez użytkownika w jednej z list rozwijanych. Stanowi to poprawę komfortu użytkowania.
8.2.4. CustomValidator, RangeValidator
Tworzymy następującą stronę [customvalidator1.aspx]:

nr | nazwa | typ | właściwości | rola |
DropDownList | EnableViewState=true | lista rozwijana | ||
CompareValidator | EnableViewState=false EnableClientScript=true ErrorMessage=Nieprawidłowy dyplom Operator=Nierówne ValueToCompare=? Type=String | komponent walidacyjny kontrolki [1] | ||
TextBox | EnableViewState=false | pole wprowadzania danych | ||
CustomValidator | EnableViewState=false EnableClientScript=true ErrorMessage=Nieprawidłowa precyzja dyplomu ClientValidationFunction=chkAutreDiplome | komponent walidacji pola [3] | ||
TextBox | EnableViewState=false | pole wprowadzania danych | ||
RangeValidator | EnableViewState=false EnableClientScript=true ErrorMessage=Rok uzyskania dyplomu musi mieścić się w przedziale [1990,2004] MinValue=1990 MaxValue=2004 Typ=Liczba całkowita ControlToValidate=txtAnDiplome | komponent walidacji pola [5] | ||
RequiredFieldValidator | EnableViewState=false EnableClientScript=true ErrorMessage=Wymagany rok uzyskania dyplomu ControlToValidate=txtAnDiplome | komponent sprawdzający poprawność pola [5] | ||
Przycisk | EnableViewState=false CausesValidation=true | przycisk [submit] |
Pole [RangeValidator] służy do sprawdzania, czy wartość elementu sterującego mieści się w przedziale między wartościami [MinValue] a [MaxValue]. Jego właściwości są następujące:
pole, którego wartość musi być sprawdzana przez komponent | |
wartość logiczna – wartość „prawda” oznacza, że zawartość poprzedniego pola musi być sprawdzana również po stronie klienta | |
komunikat o błędzie, który komponent ma wyświetlić w przypadku wykrycia błędu | |
minimalna wartość pola, które ma zostać sprawdzone | |
maksymalna wartość pola, które ma zostać sprawdzone | |
typ wartości pola, które ma zostać sprawdzone |
Pole [CustomValidator] umożliwia przeprowadzanie walidacji, których nie potrafią wykonać komponenty walidacyjne oferowane przez ASP.NET. Weryfikacja ta jest przeprowadzana przez funkcję napisaną przez programistę. Jest ona wykonywana po stronie serwera. Ponieważ kontrola może być przeprowadzana również po stronie klienta, programista może być zmuszony do opracowania funkcji JavaScript, którą dołączy do dokumentu HTML. Jej właściwości są następujące:
pole, którego wartość musi zostać sprawdzona przez komponent | |
wartość logiczna – wartość „true” oznacza, że zawartość poprzedniego pola musi być sprawdzana również po stronie klienta | |
komunikat o błędzie, który komponent ma wyświetlić w przypadku wykrycia błędu | |
funkcja do wykonania po stronie klienta |
Kod wyświetlania strony wygląda następująco:
<%@ Page Language="VB" %>
<script runat="server">
...
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p align="left">
Demande du dossier de candidature au DESS
</p>
<fieldset>
<legend>[--Votre dernier diplôme--]</legend>
<table>
<tbody>
<tr>
<td>
Diplôme*</td>
<td>
<asp:DropDownList id="cmbDiplomes" runat="server">
<asp:ListItem Value="?">?</asp:ListItem>
<asp:ListItem Value="[Autre]">[Autre]</asp:ListItem>
<asp:ListItem Value="Maîtrise">Maîtrise</asp:ListItem>
<asp:ListItem Value="DESS">DESS</asp:ListItem>
<asp:ListItem Value="DEA">DEA</asp:ListItem>
</asp:DropDownList>
</td>
<td>
Si [Autre], précisez</td>
<td>
<asp:TextBox id="txtAutreDiplome" runat="server" EnableViewState="False"></asp:TextBox>
</td>
</tr>
<tr>
<td>
</td>
<td>
<p>
<asp:CompareValidator id="CompareValidator2" runat="server" ErrorMessage="Diplôme invalide" ControlToValidate="cmbDiplomes" ValueToCompare="?" Operator="NotEqual" ></asp:CompareValidator>
</p>
</td>
<td>
</td>
<td>
<asp:CustomValidator id="CustomValidator1" runat="server" ErrorMessage="Précision diplôme invalide" OnServerValidate="CustomValidator1_ServerValidate_1" EnableViewState="False" ClientValidationFunction="chckAutreDiplome"></asp:CustomValidator>
</td>
</tr>
<tr>
<td>
Année d'obtention*</td>
<td colspan="3">
<asp:TextBox id="txtAnDiplome" runat="server" Columns="4"></asp:TextBox>
<asp:RangeValidator id="RangeValidator1" runat="server" ErrorMessage="Année diplôme doit être dans l'intervalle [1990,2004]" ControlToValidate="txtAnDiplome" MinimumValue="1990" MaximumValue="2004" Type="Integer"></asp:RangeValidator>
<asp:RequiredFieldValidator id="RequiredFieldValidator2" runat="server" ErrorMessage="Année diplôme requise" ControlToValidate="txtAnDiplome"></asp:RequiredFieldValidator>
</td>
</tr>
</tbody>
</table>
</fieldset>
<p>
<asp:Button id="btnEnvoyer" runat="server" Text="Envoyer"></asp:Button>
</p>
</form>
</body>
</html>
Atrybut [OnServerValidate] komponentu [CustomValidator] pozwala określić funkcję odpowiedzialną za walidację po stronie serwera. Powyżej komponent [CustomValidator1] wywołuje następującą procedurę [CustomValidator1_ServerValidate_1]:
<%@ Page Language="VB" %>
<script runat="server">
Sub CustomValidator1_ServerValidate_1(sender As Object, e As ServerValidateEventArgs)
' pole [txtAutreDiplome] musi być wypełnione, jeśli [cmbDiplomes]=[autre]
e.isvalid=not (cmbDiplomes.selecteditem.text="[Autre]" and txtAutreDiplome.text.trim="") _
and not (cmbDiplomes.selecteditem.text<>"[Autre]" and cmbDiplomes.selecteditem.text<>"?" and txtAutreDiplome.text.trim<>"")
End Sub
</script>
....
Funkcja walidacji powiązana z komponentem [CustomValidator] przyjmuje dwa parametry:
- sender: obiekt, który wywołał zdarzenie
- e: zdarzenie. Procedura musi ustawić atrybut [e.IsValid] na wartość „true”, jeśli sprawdzone dane są poprawne, a w przeciwnym razie na wartość „false”.
W tym przypadku przeprowadzane są następujące weryfikacje:
- lista rozwijana [cmbDiplomes] nie może mieć wartości [?]. Jest to sprawdzane przez komponent [CompareValidator2]
- użytkownik wybiera dyplom z listy rozwijanej [cmbDiplomes]. Jeśli jego dyplom nie występuje na liście, ma możliwość wybrania opcji [Autre] z listy. Następnie musi podać swój dyplom w polu wprowadzania danych [txtAutreDiplome]. Jeśli w polu [cmbDiplomes] wybrano opcję [Autre], wówczas pole [txtAutreDiplome] nie może być puste. Jeśli w polu [cmbDiplomes] nie wybrano ani [Autre], ani [?], wówczas pole [txtAutreDiplome] musi być puste. Jest to sprawdzane przez funkcję powiązaną z komponentem [CustomValidator1].
- Rok uzyskania dyplomu musi mieścić się w przedziale [1900-2004]. Jest to sprawdzane przez [RangeValidator1]. Jeśli jednak użytkownik nie wprowadzi żadnej wartości w polu, funkcja walidacyjna [RangeValidator1] nie jest wykorzystywana. Dlatego dodaje się komponent [RequiredFieldValidator2] w celu sprawdzenia obecności zawartości. Jest to ogólna zasada. Zawartość pola nie jest sprawdzana, jeśli jest puste. Jedynym przypadkiem, w którym jest sprawdzana, jest sytuacja, gdy pole jest powiązane z komponentem [RequiredFieldValidator].
Oto przykład działania w przeglądarce [Mozilla]:

Jeśli korzystamy z przeglądarki [IE6], możemy dodać funkcję walidacji po stronie klienta dla komponentu [CustomValidator1]. W tym celu komponent ten posiada następujące właściwości:
- EnableClientScript=true
- ClientValidationFunction=chkAutreDiplome
Funkcja [chkAutreDiplome ] wygląda następująco:
<head>
<meta http-equiv="pragma" content="no-cache" />
<script language="javascript">
function chkAutreDiplome(source,args){
// sprawdza poprawność pola txtAutreDiplome
with(document.frmCandidature){
diplome=cmbDiplomes.options[cmbDiplomes.selectedIndex].text;
args.IsValid= !(diplome=="[Autre]" && txtAutreDiplome.value=="")
&& ! (diplome!="[Autre]" && diplome!="?" && txtAutreDiplome.value!="");
}
}
</script>
</head>
Funkcja [chkAutreDiplome] przyjmuje te same dwa parametry, co funkcja serwerowa [CustomValidator1_ServerValidate_1]:
- źródło: obiekt, który wywołał zdarzenie
- args: zdarzenie. Posiada ono atrybut [IsValid], który musi zostać ustawiony przez funkcję na wartość „true”, jeśli zweryfikowane dane są poprawne. Wartość „false” spowoduje wyświetlenie w miejscu komponentu walidacyjnego powiązanego z nim komunikatu o błędzie, a formularz nie zostanie wysłany na serwer.
8.2.5. RegularExpressionValidator
Tworzymy następującą stronę [regularexpressionvalidator1.aspx]:

nr | nazwa | typ | właściwości | rola |
TextBox | EnableViewState=true | pole wprowadzania danych | ||
RequiredFieldValidator | ControlToValidate=txtMel Display=Dynamic | komponent walidacji kontrolki [1] | ||
RegularExpressionValidator | ControlToValidate=txtMel Display=Dynamic | komponent walidacji kontrolki [1] | ||
Przycisk | EnableViewState=false CausesValidation=true | przycisk [submit] |
W tym przypadku chcemy sprawdzić format adresu e-mail. Robimy to za pomocą komponentu [RegularExpressionValidator], który pozwala zweryfikować poprawność pola przy użyciu wyrażenia regularnego. Przypomnijmy, że wyrażenie regularne to wzorzec ciągu znaków. Sprawdzamy więc zawartość pola pod kątem zgodności z tym wzorcem. Ponieważ zawartość pola nie jest sprawdzana, jeśli jest pusta, potrzebujemy również komponentu [RequiredFieldValidator], aby sprawdzić, czy adres e-mail nie jest pusty. Klasa [RegularExpressionValidator] ma właściwości analogiczne do klas już omówionych komponentów:
pole, którego wartość musi być sprawdzana przez komponent | |
wartość logiczna – wartość „prawda” oznacza, że zawartość poprzedniego pola musi być sprawdzana również po stronie klienta | |
komunikat o błędzie, który komponent ma wyświetlić w przypadku wykrycia błędu | |
wyrażenie regularne, z którym zostanie porównana zawartość pola [ControlToValidate] | |
tryb wyświetlania: Static: pole jest zawsze widoczne, nawet jeśli nie wyświetla komunikatu o błędzie; Dynamic: pole jest widoczne tylko wtedy, gdy występuje komunikat o błędzie; None: komunikat o błędzie nie jest wyświetlany. Pole to istnieje również w przypadku innych komponentów, ale do tej pory nie było wykorzystywane. |
Wzorcowy wyrażenie regularne adresu e-mail mogłoby wyglądać następująco: \s*[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+\s*
Adres e-mail ma postać [champ1.champ2....@champA.champB...]. Przed znakiem @ musi znajdować się co najmniej jedno pole, a za nim co najmniej dwa pola. Pola te składają się ze znaków alfanumerycznych oraz znaku -. Znak alfanumeryczny jest reprezentowany przez symbol \w. Sekwencja [\w-] oznacza znak \w lub znak -. Znak + po sekwencji S oznacza, że sekwencja ta może powtórzyć się 1 lub więcej razy, znak * oznacza, że może powtórzyć się 0 lub więcej razy, a znak ? oznacza, że może powtórzyć się 0 lub 1 raz.
Pole adresu e-mail odpowiada wzorcowi [\w-]+. Fakt, że przed znakiem @ musi znajdować się co najmniej jedno pole, a po nim co najmniej dwa, oddzielone znakiem ., odpowiada wzorcowi: [\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+. Można pozwolić użytkownikowi na umieszczanie spacji przed i po adresie. Zostaną one usunięte podczas przetwarzania. Ciąg spacji, który może być pusty, jest reprezentowany przez wzorzec \s*. Stąd wyrażenie regularne adresu e-mail: \s*[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+\s*.
Kod wyświetlania strony wygląda zatem następująco:
<html>
<head>
</head>
<body>
<form runat="server">
<p align="left">
Demande du dossier de candidature au DESS
</p>
<fieldset>
<legend>[--Votre adresse électronique où sera envoyé le dossier de candidature--]</legend>
<table>
<tbody>
<tr>
<td>
<asp:TextBox id="txtMel" runat="server" Columns="60"></asp:TextBox>
</td>
</tr>
<tr>
<td>
<p>
<asp:RequiredFieldValidator id="RequiredFieldValidator3" runat="server" Display="Dynamic" ControlToValidate="txtMel" ErrorMessage="Votre adresse électronique est requise"></asp:RequiredFieldValidator>
</p>
<p>
<asp:RegularExpressionValidator id="RegularExpressionValidator1" runat="server" Display="Dynamic" ControlToValidate="txtMel" ErrorMessage="Votre adresse électronique n'a pas un format valide" ValidationExpression="\s*[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+\s*"></asp:RegularExpressionValidator>
</p>
</td>
</tr>
</tbody>
</table>
</fieldset>
<p>
<asp:Button id="btnEnvoyer" runat="server" Text="Envoyer"></asp:Button>
</p>
</form>
</body>
</html>
8.2.6. ValidationSummary
Ze względów estetycznych warto zebrać komunikaty o błędach w jednym miejscu, tak jak w poniższym przykładzie [summaryvalidator1.aspx], gdzie na jednej stronie zebraliśmy wszystkie poprzednie przykłady:

Wszystkie kontrolki walidacyjne mają następujące właściwości:
- [Display=Dynamic], co sprawia, że kontrolki nie zajmują miejsca na stronie, jeśli ich komunikat o błędzie jest pusty
- [EnableClientScript=false], aby uniemożliwić jakiekolwiek sprawdzanie poprawności po stronie klienta
- [Text=*]. Będzie to komunikat wyświetlany w przypadku błędu, natomiast zawartość atrybutu [ErrorMessage] jest wyświetlana przez kontrolkę [ValidationSummary], którą przedstawiamy poniżej.
Ten zestaw elementów kontrolnych umieszczono w komponencie o nazwie [Panel], zwanym [vueFormulaire]. W innym komponencie o nazwie [Panel], zwanym [vueErreurs], umieszczamy kontrolkę [ValidationSummary]:

nr | nazwa | typ | właściwości | rola |
1 | ValidationSummary | HeaderText=Wystąpiły następujące błędy, EnableClientScript=false, ShowSummary=true | wyświetla błędy wszystkich elementów sprawdzających poprawność danych na stronie | |
2 | LinkButton | CausesValidation=false | link powrotny do formularza |
W przypadku linku [lnkErreursToFormulaire] nie ma potrzeby włączania walidacji, ponieważ link ten nie przesyła żadnych wartości do sprawdzenia.
Gdy wszystkie dane są prawidłowe, użytkownikowi wyświetlany jest następujący widok [infos]:

1
nr | nazwa | typ | właściwości | rola |
1 | Etykieta | komunikat informacyjny dla użytkownika |
Kod prezentacji tej strony jest następujący:
<html>
<head>
</head>
<body>
<form runat="server">
<p align="left">
Demande du dossier de candidature au DESS
</p>
<p>
<hr />
<asp:panel id="vueErreurs" runat="server">
<p align="left">
<asp:ValidationSummary id="ValidationSummary1" runat="server" ShowMessageBox="True" BorderColor="#C04000" BorderWidth="1px" BackColor="#FFFFC0" HeaderText="Les erreurs suivantes se sont produites"></asp:ValidationSummary>
</p>
<p>
<asp:LinkButton id="lnkErreursToFormulaire" onclick="lnkErreursToFormulaire_Click" runat="server" CausesValidation="False">Retour au formulaire</asp:LinkButton>
</p>
</asp:panel>
<asp:panel id="vueFormulaire" runat="server">
....
<asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
</p>
</asp:panel>
<asp:panel id="vueInfos" runat="server">
<asp:Label id="lblInfo" runat="server"></asp:Label>
</asp:panel>
</form>
</body>
</html>
Oto kilka przykładów uzyskanych wyników. Sprawdzamy widok [formulaire] bez wprowadzania wartości:

Przycisk [Envoyer] zwraca następującą odpowiedź:

Wyświetlono widok [erreurs]. Jeśli skorzystamy z linku powrotnego do formularza, znajdziemy go w następującym stanie:

Jest to widok [formulaire]. Warto zwrócić uwagę na znak [*] obok błędnych danych. Wyświetlono pole [Text] z kontrolami walidacji. Jeśli poprawnie wypełnimy pola, otrzymamy widok [infos]:

Kod kontrolny strony wygląda następująco:
<%@ Page Language="VB" %>
<script runat="server">
' procedura wykonywana podczas ładowania strony
Sub page_Load(sender As Object, e As EventArgs)
' przy pierwszym żądaniu wyświetlany jest widok [formulaire]
if not ispostback then
afficheVues(true,false,false)
end if
end sub
sub afficheVues(byval formulaireVisible as boolean, _
erreursVisible as boolean, infosVisible as boolean)
' zestaw widoków
vueFormulaire.visible=formulaireVisible
vueErreurs.visible=erreursVisible
vueInfos.visible=infosVisible
end sub
Sub CustomValidator1_ServerValidate(sender As Object, e As ServerValidateEventArgs)
...
End Sub
Sub CustomValidator2_ServerValidate(sender As Object, e As ServerValidateEventArgs)
...
End Sub
Sub CustomValidator1_ServerValidate_1(sender As Object, e As ServerValidateEventArgs)
...
End Sub
Sub lnkErreursToFormulaire_Click(sender As Object, e As EventArgs)
' wyświetla widok formularza
afficheVues(true,false,false)
' ponownie przeprowadza kontrole poprawności
Page.validate
End Sub
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' czy strona jest poprawna?
if not Page.IsValid then
' wyświetla się widok [erreurs]
afficheVues(false,true,false)
else
' w przeciwnym razie widok [infos]
lblInfo.Text="Le dossier de candidature au DESS IAIE a été envoyé à l'adresse ["+ _
txtMel.Text + "]. Nous vous en souhaitons bonne réception.<br><br>Le secrétariat du DESS."
afficheVues(false,false,true)
end if
end sub
</script>
<html>
...
</html>
- W procedurze [Page_Load], uruchamianej przy każdym żądaniu klienta, wyświetlamy widok [formulaire], a pozostałe są ukrywane. Odbywa się to wyłącznie przy pierwszym żądaniu. Procedura wywołuje procedurę pomocniczą [afficheVues], do której przekazywane są trzy wartości logiczne, decydujące o tym, czy trzy widoki mają zostać wyświetlone, czy nie.
- W momencie wywołania procedury [btnEnvoyer_Click] weryfikacje danych zostały już przeprowadzone. Przycisk [btnEnvoyer] posiada właściwość [CausesValidation=true], która wymusza tę weryfikację danych. Wszystkie kontrole walidacji zostały przeprowadzone, a ich właściwość [IsValid] została ustawiona. Wskazuje ona, czy dane zweryfikowane przez kontrolę były prawidłowe, czy nie. Ponadto ustawiono również właściwość [IsValid] samej strony. Ma ona wartość [vrai] tylko wtedy, gdy właściwość [IsValid] wszystkich kontroli walidacyjnych na stronie ma wartość [vrai]. W ten sposób procedura [btnEnvoyer_Click] rozpoczyna się od sprawdzenia, czy strona jest poprawna, czy nie. Jeśli jest nieprawidłowa, wyświetlany jest widok [erreurs]. Zawiera ona kontrolkę [ValidationSummary], która przejmuje atrybuty [ErrorMessage] ze wszystkich kontrolek (patrz zrzut ekranu powyżej). Jeśli strona jest poprawna, wyświetlany jest widok [infos] wraz z komunikatem informacyjnym.
- Procedura [lnkErreursToFormulaire_Click] odpowiada za wyświetlenie widoku [formulaire] w stanie, w jakim został on zatwierdzony. Wszystkie pola wprowadzania danych w widoku [formulaire], które posiadają właściwość [EnableViewState=true], są automatycznie odświeżane. Co ciekawe, stan komponentów walidacyjnych nie jest przywracany. W rzeczywistości można by oczekiwać, że po powrocie do widoku [erreurs] błędne pola kontrolne wyświetlą swoje pole [Text]. Tak się jednak nie dzieje. W związku z tym wymusiliśmy walidację danych, korzystając z metody [Page.Validate] na tej stronie. Należy to zrobić po wyświetleniu panelu [vueFormulaire]. Łącznie mamy więc dwie walidacje. W praktyce należy tego unikać. W tym przypadku przykład pozwolił nam wprowadzić nowe pojęcia dotyczące walidacji strony.
8.3. Komponenty ListControl i powiązania danych
Niektóre z analizowanych komponentów serwerowych umożliwiają wyświetlanie listy wartości (DropDownList, ListBox). Inne, których jeszcze nie przedstawiliśmy, pozwalają na wyświetlanie wielu list wartości w tabelach HTML. W przypadku wszystkich z nich możliwe jest programowe przypisywanie wartości z list, jedna po drugiej, do powiązanego komponentu. Możliwe jest również przypisywanie do tych komponentów bardziej złożonych obiektów, takich jak obiekty typu [Array], [ArrayList], [DataSet], [HashTable], ... co upraszcza kod powiązujący dane z komponentem. Takie powiązanie nazywamy powiązaniem danych.
Wszystkie komponenty wywodzące się z klasy [ListControl] mogą być powiązane z listą danych. Są to komponenty [DropDownList], [ListBox], [CheckButtonList], [RadioButtonList]. Każdy z tych komponentów może być powiązany ze źródłem danych. Może to być różne źródło: [Array], [ArrayList], [DataTable], [DataSet], [HashTable],...., ogólnie rzecz biorąc, obiekt implementujący jeden z interfejsów IEnumerable, ICollection, IListSource. Przedstawimy tylko kilka z nich. Obiekt [DataSet] jest obrazem relacyjnej bazy danych. Jest to zatem zbiór tabel połączonych relacjami. Obiekt [DataTable] reprezentuje taką tabelę. Źródło danych nadaje właściwości [Text] i [Value] każdemu z elementów [Item] obiektu [ListControl]. Jeśli T jest wartością [Text], a V wartością [Value], to tag HTML generowany dla każdego elementu [ListControl] ma następującą postać:
<option value="V">T</option> | |
<input type="checkbox" value="V">T | |
<input type="radio" value="V">T |
Powiązanie komponentu [ListControl] ze źródłem danych odbywa się za pomocą następujących właściwości:
źródło danych [Array], [ArrayList], [DataTable], [DataSet], [HashTable], ... | |
w przypadku, gdy źródłem danych jest [DataSet], oznacza nazwę tabeli, która ma służyć jako źródło danych. Rzeczywistym źródłem danych jest wówczas tabela. | |
w przypadku, gdy źródłem danych jest tabela ([DataTable], [DataSet]), oznacza nazwę kolumny tabeli, z której pochodzą wartości dla pola [Text] w elementach [ListControl] | |
w przypadku, gdy źródłem danych jest tabela ([DataTable], [DataSet]), oznacza nazwę kolumny tabeli, która dostarczy wartości do pola [Value] elementów [ListControl] |
Powiązanie komponentu [ListControl] ze źródłem danych nie powoduje zainicjowania komponentu wartościami ze źródła danych. Odbywa się to w ramach operacji [ListControl].DataBind.
W zależności od rodzaju źródła danych powiązanie go z komponentem [ListControl] przebiega w różny sposób:
[ListControl].DataSource=A pola [Text] i [Value] elementów [ListControl] będą miały jako wartości elementy z A | |
[ListControl].DataSource=AL pola [Text] i [Value] elementów z [ListControl] będą miały wartości elementów z AL | |
[ListControl].DataSource = DT, [ListControl].DataTextField = „col1”, [ListControl]. DatavalueField = „col2” gdzie col1 i col2 to dwie kolumny tabeli DT. Pola [Text] i [Value] elementów z tabeli [ListControl] będą miały wartości z kolumn col1 i col2 tabeli DT | |
[ListControl].DataSource=DS, [ListControl].DataSource="table", gdzie „table” to nazwa jednej z tabel z DS. [ListControl].DataTextField = „col1”, [ListControl]. DatavalueField = „col2”, gdzie col1 i col2 to dwie kolumny tabeli „table”. Pola [Text] i [Value] elementów [ListControl] będą miały wartości odpowiadające kolumnom col1 i col2 tabeli „table” | |
[ListControl].DataSource=HT, [ListControl].DataTextField = „key”, [ListControl]. DatavalueField ="value", gdzie [key] i [value] to odpowiednio klucze i wartości HT. |
Zastosujemy te informacje w praktyce na poniższym przykładzie dotyczącym [databind1.aspx]:
![]() |
Mamy tu pięć powiązań danych, z których każde zawiera cztery elementy kontrolne typu [DropDownList], [ListBox], [CheckBoxList] oraz [RadioButtonList]. Pięć analizowanych powiązań różni się źródłami danych:
połączenie | źródło danych |
Tablica | |
ArrayList | |
DataTable | |
DataSet | |
HashTable |
8.3.1. Kod prezentacji komponentów
Kod prezentacji elementów sterujących połączenia 1 jest następujący:
<td>
<asp:DropDownList id="DropDownList1" runat="server"></asp:DropDownList>
</td>
<td>
<asp:ListBox id="ListBox1" runat="server" SelectionMode="Multiple"></asp:ListBox>
</td>
<td>
<asp:CheckBoxList id="CheckBoxList1" runat="server"></asp:CheckBoxList>
</td>
<td>
<asp:RadioButtonList id="RadioButtonList1" runat="server"></asp:RadioButtonList>
</td>
Numery połączeń od 2 do 5 są identyczne, z wyjątkiem numeru połączenia.
8.3.2. Powiązanie z źródłem danych typu tablica
Połączenie czterech elementów sterujących [ListBox] przedstawionych powyżej odbywa się w następujący sposób w procedurze [Page_Load] kodu sterującego:
' dane globalne
dim textes() as string={"un","deux","trois","quatre"}
dim valeurs() as string={"1","2","3","4"}
dim myDataListe as new ArrayList
dim myDataTable as new DataTable("table1")
dim myDataSet as new DataSet
dim myHashTable as new HashTable
' procedura wykonywana podczas ładowania strony
Sub page_Load(sender As Object, e As EventArgs)
if not IsPostBack then
' tworzymy źródła danych, które zostaną powiązane z komponentami
createDataSources
' powiązanie z tablicą [Array]
bindToArray
' powiązanie z listą [ArrayList]
bindToArrayList
' powiązanie z tabelą [DataTable]
bindToDataTable
' powiązanie z grupą danych [DataSet]
bindToDataSet
' powiązanie ze słownikiem [HashTable]
bindToHashTable
end if
End Sub
sub createDataSources
' tworzy źródła danych, które zostaną powiązane z komponentami
' arraylist
dim i as integer
for i=0 to textes.length-1
myDataListe.add(textes(i))
next
' tabela danych
' definiuje się jej dwie kolumny
myDataTable.Columns.Add("id",Type.GetType("System.Int32"))
myDataTable.Columns.Add("texte",Type.GetType("System.String"))
' wypełnia się tabelę
dim ligne as DataRow
for i=0 to textes.length-1
ligne=myDataTable.NewRow
ligne("id")=i
ligne("texte")=textes(i)
myDataTable.Rows.Add(ligne)
next
' zbiór danych – jedna tabela
myDataSet.Tables.Add(myDataTable)
' tablica skojarzona
for i=0 to textes.length-1
myHashTable.add(valeurs(i),textes(i))
next
end sub
' powiązanie tablicowe
sub bindToArray
' powiązanie z komponentami
with DropDownList1
.DataSource=textes
.DataBind
end with
with ListBox1
.DataSource=textes
.DataBind
end with
with CheckBoxList1
.DataSource=textes
.DataBind
end with
with RadioButtonList1
.DataSource=textes
.DataBind
end with
' wybór elementów
ListBox1.Items(1).Selected=true
ListBox1.Items(3).Selected=true
CheckBoxList1.Items(0).Selected=true
CheckBoxList1.Items(3).Selected=true
DropDownList1.SelectedIndex=2
RadioButtonList1.SelectedIndex=1
end sub
sub bindToArrayList
....
end sub
sub bindToDataTable
...
end sub
sub bindToDataSet
...
end sub
Powiązanie elementów kontrolnych z danymi odbywa się tutaj tylko przy pierwszym zapytaniu. Następnie elementy kontrolne zachowają swoje wartości dzięki mechanizmowi [VIEWSTATE]. Powiązanie odbywa się w procedurze [bindToArray]. Ponieważ źródło danych jest typu [Array], inicjowane jest wyłącznie pole [DataSource] komponentów [ListControl]. Wypełnienie kontrolki wartościami z powiązanego źródła danych odbywa się za pomocą metody [ListControl].DataBind. Dopiero potem obiekty [ListControl] zawierają elementy. Wówczas można wybrać niektóre z nich.
8.3.3. Powiązanie ze źródłem danych typu ArrayList
Źródło danych [myDataListe] jest inicjowane w procedurze [createDataSources]:
' dane globalne
dim textes() as string={"un","deux","trois","quatre"}
dim myDataListe as new ArrayList
..
' procedura wykonywana podczas ładowania strony
Sub page_Load(sender As Object, e As EventArgs)
if not IsPostBack then
' tworzy się źródła danych, które zostaną powiązane z komponentami
createDataSources
...
end if
End Sub
sub createDataSources
' tworzy źródła danych, które zostaną powiązane z komponentami
' arraylist
dim i as integer
for i=0 to textes.length-1
myDataListe.add(textes(i))
next
...
end sub
Powiązanie czterech elementów kontrolnych [ListBox] z powiązaniem 2 odbywa się w następujący sposób w procedurze [bindToArrayList] kodu kontrolnego:
' powiązanie listy tablicowej
sub bindToArrayList
' powiązanie z komponentami
with DropDownList2
.DataSource=myDataListe
.DataBind
end with
with ListBox2
.DataSource=myDataListe
.DataBind
end with
with CheckBoxList2
.DataSource=myDataListe
.DataBind
end with
with RadioButtonList2
.DataSource=myDataListe
.DataBind
end with
' wybór elementów
ListBox2.Items(1).Selected=true
ListBox2.Items(3).Selected=true
CheckBoxList2.Items(0).Selected=true
CheckBoxList2.Items(3).Selected=true
DropDownList2.SelectedIndex=2
RadioButtonList2.SelectedIndex=1
end sub
Ponieważ źródło danych ma typ [ArrayList], inicjowane jest tylko pole [DataSource] komponentów [ListControl].
8.3.4. Źródło danych typu DataTable
Źródło danych [myDataTable] jest inicjowane w procedurze [createDataSources]:
' dane globalne
dim textes() as string={"un","deux","trois","quatre"}
dim myDataTable as new DataTable("table1")
...
' procedura wykonywana podczas ładowania strony
Sub page_Load(sender As Object, e As EventArgs)
if not IsPostBack then
' tworzy się źródła danych, które zostaną powiązane z komponentami
createDataSources
...
end if
End Sub
sub createDataSources
' tworzy źródła danych, które zostaną powiązane z komponentami
...
' tabela danych
' definiuje się jej dwie kolumny
myDataTable.Columns.Add("id",Type.GetType("System.Int32"))
myDataTable.Columns.Add("texte",Type.GetType("System.String"))
' wypełniamy tabelę
dim ligne as DataRow
for i=0 to textes.length-1
ligne=myDataTable.NewRow
ligne("id")=i
ligne("texte")=textes(i)
myDataTable.Rows.Add(ligne)
next
...
end sub
Zaczynamy od utworzenia obiektu [DataTable] zawierającego dwie kolumny: [id] i [texte]. Kolumna [id] będzie zasilać pole [Value] elementów z [ListControl], a kolumna [texte] – ich pola [Text]. Tworzenie tabeli [DataTable] z dwiema kolumnami odbywa się w następujący sposób:
myDataTable.Columns.Add("id",Type.GetType("System.Int32"))
myDataTable.Columns.Add("texte",Type.GetType("System.String"))
Tworzymy zatem tabelę z dwiema kolumnami:
- pierwsza o nazwie „id” jest typu całkowitoliczbowego
- druga, o nazwie „tekst”, jest typu ciąg znaków
Po utworzeniu struktury tabeli można ją wypełnić następującym kodem:
dim ligne as DataRow
for i=0 to textes.length-1
ligne=myDataTable.NewRow
ligne("id")=i
ligne("texte")=textes(i)
myDataTable.Rows.Add(ligne)
next
Kolumna [id] będzie zawierała liczby całkowite z tabeli [0,1,..,n], natomiast kolumna [texte] będzie zawierała wartości z tabeli [data]. Po wykonaniu tej czynności tabela [dataListe] zostanie wypełniona. Powiązanie czterech elementów sterujących [ListBox] w powiązaniu 3 odbywa się w następujący sposób w procedurze [bindToDataTable] kodu sterującego:
sub bindToDataTable
' powiązanie z komponentami
with DropDownList3
.DataSource=myDataTable
.DataValueField="id"
.DataTextField="texte"
.DataBind
end with
with ListBox3
.DataSource=myDataTable
.DataValueField="id"
.DataTextField="texte"
.DataBind
end with
with CheckBoxList3
.DataSource=myDataTable
.DataValueField="id"
.DataTextField="texte"
.DataBind
end with
with RadioButtonList3
.DataSource=myDataTable
.DataValueField="id"
.DataTextField="texte"
.DataBind
end with
' wybór elementów
ListBox3.Items(1).Selected=true
ListBox3.Items(3).Selected=true
CheckBoxList3.Items(0).Selected=true
CheckBoxList3.Items(3).Selected=true
DropDownList3.SelectedIndex=2
RadioButtonList3.SelectedIndex=1
end sub
Każdy komponent [ListControl] jest powiązany ze źródłem [myDataTable] poprzez przypisanie dla każdego z nich:
Tabela [myDataTable] stanowi źródło danych. Kolumna [id] tej tabeli będzie zasilać pola [Value] elementów komponentów, natomiast kolumna [texte] będzie zasilać ich pola [Text].
8.3.5. Źródło danych typu DataSet
Źródło danych [myDataSet] jest inicjowane w procedurze [createDataSources]:
' dane globalne
dim myDataTable as new DataTable("table1")
dim myDataSet as new DataSet
...
' procedura wykonywana podczas ładowania strony
Sub page_Load(sender As Object, e As EventArgs)
if not IsPostBack then
' tworzy się źródła danych, które zostaną powiązane z komponentami
createDataSources
...
end if
End Sub
sub createDataSources
' tworzy źródła danych, które zostaną powiązane z komponentami
' zbiór danych – pojedyncza tabela
myDataSet.Tables.Add(myDataTable)
...
end sub
Obiekt [DataSet] reprezentuje zbiór tabel typu [DataTable]. Dodajemy wcześniej utworzoną tabelę [myDataTable] do tabeli [DataSet]. Powiązanie czterech elementów sterujących [ListBox] w powiązaniu 4 odbywa się w następujący sposób w procedurze [bindToDataSet] kodu sterującego:
sub bindToDataSet
' powiązanie z komponentami
with DropDownList4
.DataSource=myDataSet
.DataMember="table1"
.DataValueField="id"
.DataTextField="texte"
.DataBind
end with
with ListBox4
.DataSource=myDataSet
.DataMember="table1"
.DataValueField="id"
.DataTextField="texte"
.DataBind
end with
with CheckBoxList4
.DataSource=myDataSet
.DataMember="table1"
.DataValueField="id"
.DataTextField="texte"
.DataBind
end with
with RadioButtonList4
.DataSource=myDataSet
.DataMember="table1"
.DataValueField="id"
.DataTextField="texte"
.DataBind
end with
' wybór elementów
ListBox4.Items(1).Selected=true
ListBox4.Items(3).Selected=true
CheckBoxList4.Items(0).Selected=true
CheckBoxList4.Items(3).Selected=true
DropDownList4.SelectedIndex=2
RadioButtonList4.SelectedIndex=1
end sub
Każdy komponent [ListControl] jest powiązany ze źródłem danych w następujący sposób:
Grupa danych [myDataSet] stanowi źródło danych. Ponieważ może ona obejmować kilka tabel, w polu [DataMember] należy określić nazwę tabeli, z której będziemy korzystać. Kolumna [id] tej tabeli będzie zasilać pola [Value] elementów komponentów, natomiast kolumna [texte] będzie zasilać ich pola [Text].
8.3.6. Źródło danych typu HashTable
Źródło danych [myHashTable] jest inicjowane w procedurze [createDataSources]:
' dane globalne
dim textes() as string={"un","deux","trois","quatre"}
dim valeurs() as string={"1","2","3","4"}
dim myHashTable as new HashTable
...
' procedura wykonywana podczas ładowania strony
Sub page_Load(sender As Object, e As EventArgs)
if not IsPostBack then
' tworzy się źródła danych, które zostaną powiązane z komponentami
createDataSources
...
end if
End Sub
sub createDataSources
' tworzy źródła danych, które zostaną powiązane z komponentami
...
' tablica skrótów
for i=0 to textes.length-1
myHashTable.add(valeurs(i),textes(i))
next
end sub
Słownik [myHashTable] można postrzegać jako tabelę składającą się z dwóch kolumn o nazwach „key” i „value”. Kolumna [key] zawiera klucze słownika, a kolumna [value] – wartości z nimi powiązane. Kolumna [key] składa się tutaj z zawartości tablicy [valeurs], a kolumna [value] – z zawartości tablicy [textes]. Powiązanie tego źródła z kontrolkami odbywa się w procedurze [bindToHashTable]:
sub bindToHashTable
' powiązanie z komponentami
with DropDownList5
.DataSource=myHashTable
.DataValueField="key"
.DataTextField="value"
.DataBind
end with
with ListBox5
.DataSource=myHashTable
.DataValueField="key"
.DataTextField="value"
.DataBind
end with
with CheckBoxList5
.DataSource=myHashTable
.DataValueField="key"
.DataTextField="value"
.DataBind
end with
with RadioButtonList5
.DataSource=myHashTable
.DataValueField="key"
.DataTextField="value"
.DataBind
end with
' wybór elementów
ListBox5.Items(1).Selected=true
ListBox5.Items(3).Selected=true
CheckBoxList5.Items(0).Selected=true
CheckBoxList5.Items(3).Selected=true
DropDownList5.SelectedIndex=2
RadioButtonList5.SelectedIndex=1
end sub
Dla każdego z komponentów połączenie tworzy się za pomocą instrukcji:
Źródłem danych jest słownik [myHashTable]. Wartości elementów sterujących pochodzą z kolumny [key] słownika, a teksty z kolumny [value]. Wstawianie elementów słownika do elementów sterujących odbywa się w kolejności kluczy, która z zasady jest losowa.
8.3.7. Wytyczne dotyczące importowania przestrzeni nazw
Pewna liczba przestrzeni nazw jest automatycznie importowana na stronę ASP.NET. Nie dotyczy to jednak przestrzeni nazw „System.Data”, w której znajdują się klasy [DataTable] i [DataSet]. Należy zatem zaimportować tę klasę. Odbywa się to w następujący sposób:
<%@ Page Language="VB" %>
<%@ import Namespace="System.Data" %>
<script runat="server">
...
</script>
<html>
...
</html>
8.4. Komponent DataGrid i powiązanie danych
Komponent [DataGrid] umożliwia wyświetlanie danych w formie tabel, ale jego możliwości wykraczają daleko poza zwykłe wyświetlanie:
- oferuje możliwość precyzyjnego dostosowania „wyglądu” tabeli
- umożliwia aktualizację źródła danych
Komponent [DataGrid] jest zarówno potężnym, jak i złożonym komponentem. Przedstawimy go krok po kroku.
8.4.1. Wyświetlanie źródła danych typu Array, ArrayList, DataTable, DataSet
Komponent [DataGrid] umożliwia wyświetlanie w tabeli HTML źródeł danych typu [Array], [ArrayList], [DataTable] oraz [DataSet]. W przypadku tych czterech typów danych wystarczy powiązać źródło z właściwością [DataSource] komponentu [DataGrid]:
źródło danych [Array], [ArrayList], [DataTable], [DataSet], ... | |
w przypadku, gdy źródłem danych jest plik [DataSet], pole to określa nazwę tabeli, która ma służyć jako źródło danych. Wówczas rzeczywistym źródłem danych jest tabela. Jeśli pole to pozostaje puste, wyświetlane są wszystkie tabele z pliku [DataSet]. |
Przedstawiamy teraz stronę [datagrid1.aspx], która pokazuje powiązanie pliku [DataGrid] z czterema różnymi źródłami danych:

Strona zawiera cztery komponenty [DataGrid] utworzone z [WebMatrix] w następujący sposób. Komponent umieszcza się w odpowiednim miejscu na karcie [Design]:

Następnie rysowana jest ogólna tabela HTML. Właściwości elementu [DataGrid] można zdefiniować na etapie projektowania. Właśnie to robimy w tym przypadku, konfigurując jego właściwości formatowania. W tym celu zaznaczamy element [DataGrid], który ma zostać skonfigurowany. Jego właściwości pojawiają się w oknie w prawym dolnym rogu:

Skorzystamy z dwóch powyższych linków. Link [Générateur de propriétés] zapewnia dostęp do głównych właściwości elementu [DataGrid]:

Odznaczamy opcję [Afficher l'en-tête] dla czterech komponentów [DataGrid] i zatwierdzamy stronę. Drugi link [Mise en forme automatique] pozwala wybrać jeden z kilku stylów dla tabeli HTML, która zostanie wyświetlona:

Wybieramy [couleur i] dla elementu [DataGrid] o numerze i. Te wybory projektowe są odzwierciedlone w kodzie prezentacji strony:
<html>
<head>
</head>
<body>
<form runat="server">
<p>
Liaison de données avec un DataGrid
</p>
<hr />
<p>
<table>
<tbody>
</tbody>
</table>
<table border="1">
<tbody>
<tr>
<td>
Array</td>
<td>
ArrayList</td>
<td>
DataTable</td>
<td>
DataSet</td>
</tr>
<tr>
<td>
<asp:DataGrid id="DataGrid1" runat="server" ShowHeader="False" CellPadding="4" BackColor="White" BorderColor="#CC9966" BorderWidth="1px" BorderStyle="None">
<FooterStyle forecolor="#330099" backcolor="#FFFFCC"></FooterStyle>
<HeaderStyle font-bold="True" forecolor="#FFFFCC" backcolor="#990000"></HeaderStyle>
<PagerStyle horizontalalign="Center" forecolor="#330099" backcolor="#FFFFCC"></PagerStyle>
<SelectedItemStyle font-bold="True" forecolor="#663399" backcolor="#FFCC66"></SelectedItemStyle>
<ItemStyle forecolor="#330099" backcolor="White"></ItemStyle>
</asp:DataGrid>
</td>
<td>
<asp:DataGrid id="DataGrid2" runat="server" ShowHeader="False" CellPadding="4" BackColor="White" BorderColor="#3366CC" BorderWidth="1px" BorderStyle="None">
<FooterStyle forecolor="#003399" backcolor="#99CCCC"></FooterStyle>
<HeaderStyle font-bold="True" forecolor="#CCCCFF" backcolor="#003399"></HeaderStyle>
<PagerStyle horizontalalign="Left" forecolor="#003399" backcolor="#99CCCC" mode="NumericPages"></PagerStyle>
<SelectedItemStyle font-bold="True" forecolor="#CCFF99" backcolor="#009999"></SelectedItemStyle>
<ItemStyle forecolor="#003399" backcolor="White"></ItemStyle>
</asp:DataGrid>
</td>
<td>
<asp:DataGrid id="DataGrid3" runat="server" ShowHeader="False" CellPadding="3" BackColor="#DEBA84" BorderColor="#DEBA84" BorderWidth="1px" BorderStyle="None" CellSpacing="2">
<FooterStyle forecolor="#8C4510" backcolor="#F7DFB5"></FooterStyle>
<HeaderStyle font-bold="True" forecolor="White" backcolor="#A55129"></HeaderStyle>
<PagerStyle horizontalalign="Center" forecolor="#8C4510" mode="NumericPages"></PagerStyle>
<SelectedItemStyle font-bold="True" forecolor="White" backcolor="#738A9C"></SelectedItemStyle>
<ItemStyle forecolor="#8C4510" backcolor="#FFF7E7"></ItemStyle>
</asp:DataGrid>
</td>
<td>
<asp:DataGrid id="DataGrid4" runat="server" ShowHeader="False" CellPadding="3" BackColor="White" BorderColor="#E7E7FF" BorderWidth="1px" BorderStyle="None" GridLines="Horizontal">
<FooterStyle forecolor="#4A3C8C" backcolor="#B5C7DE"></FooterStyle>
<HeaderStyle font-bold="True" forecolor="#F7F7F7" backcolor="#4A3C8C"></HeaderStyle>
<PagerStyle horizontalalign="Right" forecolor="#4A3C8C" backcolor="#E7E7FF" mode="NumericPages"></PagerStyle>
<SelectedItemStyle font-bold="True" forecolor="#F7F7F7" backcolor="#738A9C"></SelectedItemStyle>
<AlternatingItemStyle backcolor="#F7F7F7"></AlternatingItemStyle>
<ItemStyle forecolor="#4A3C8C" backcolor="#E7E7FF"></ItemStyle>
</asp:DataGrid>
</td>
</tr>
</tbody>
</table>
</p>
<asp:Button id="Button1" runat="server" Text="Envoyer"></asp:Button>
</form>
</body>
</html>
W trybie projektowania ustaliliśmy jedynie właściwości formatowania. To w kodzie sterującym przypisujemy dane do czterech komponentów:
<%@ Page Language="VB" %>
<%@ import Namespace="system.data" %>
<script runat="server">
' dane ogólne
dim textes1() as string={"un","deux","trois","quatre"}
dim textes2() as string={"one","two","three","for"}
dim valeurs() as string={"1","2","3","4"}
dim myDataListe as new ArrayList
dim myDataTable as new DataTable("table1")
dim myDataSet as new DataSet
' procedura wykonywana podczas ładowania strony
Sub page_Load(sender As Object, e As EventArgs)
if not IsPostBack then
' tworzy się źródła danych, które zostaną powiązane z komponentami
createDataSources
' powiązanie z tablicą [Array]
bindToArray
' powiązanie z listą [ArrayList]
bindToArrayList
' powiązanie z tabelą [DataTable]
bindToDataTable
' powiązanie z grupą danych [DataSet]
bindToDataSet
end if
End Sub
sub createDataSources
' tworzy źródła danych, które zostaną powiązane z komponentami
' arraylist
dim i as integer
for i=0 to textes1.length-1
myDataListe.add(textes1(i))
next
' tabela danych
' definiuje się jej dwie kolumny
myDataTable.Columns.Add("id",Type.GetType("System.Int32"))
myDataTable.Columns.Add("texte1",Type.GetType("System.String"))
myDataTable.Columns.Add("texte2",Type.GetType("System.String"))
' wypełnia się tabelę
dim ligne as DataRow
for i=0 to textes1.length-1
ligne=myDataTable.NewRow
ligne("id")=valeurs(i)
ligne("texte1")=textes1(i)
ligne("texte2")=textes2(i)
myDataTable.Rows.Add(ligne)
next
' zbiór danych – jedna tabela
myDataSet.Tables.Add(myDataTable)
end sub
' powiązanie tablicy
sub bindToArray
with DataGrid1
.DataSource=textes1
.DataBind
end with
end sub
' powiązanie listy tablic
sub bindToArrayList
with DataGrid2
.DataSource=myDataListe
.DataBind
end with
end sub
' powiązanie z tabelą danych
sub bindToDataTable
with DataGrid3
.DataSource=myDataTable
.DataBind
end with
end sub
' powiązanie z zestawem danych
sub bindToDataSet
with DataGrid4
.DataSource=myDataSet
.DataBind
end with
end sub
</script>
<html>
...
</html>
Kod jest dość podobny do tego z poprzedniego przykładu, więc nie będziemy go szczegółowo omawiać. Zwróćmy jednak uwagę na sposób powiązania danych. Dla każdego z czterech elementów sterujących wystarczy następująca sekwencja:
gdzie [source de données] ma postać [Array] lub [ArrayList] lub [DataTable] lub [DataSet]. Widać zatem, że mamy tu do czynienia z potężnym narzędziem do wyświetlania danych w tabelach:
- formatowanie odbywa się za pomocą [WebMatrix] lub dowolnego innego IDE na etapie projektowania
- powiązanie danych odbywa się w kodzie. W przypadku pliku [WebMatrix] można je wykonać na etapie projektowania, jeśli źródłem danych jest serwer bazy danych SQL lub baza danych ACCESS. W takim przypadku na arkuszu należy umieścić komponent typu [SqlDataSource] lub [AccessDataSource]. Komponent ten można połączyć na etapie projektowania z fizycznym źródłem danych, którym jest baza serwera SQL lub baza ACCESS, w zależności od sytuacji. Jeśli do właściwości [DataSource] komponentu [DataGrid] przypiszemy obiekt [SqlDataSource] lub [AccessDataSource] powiązany z fizycznym źródłem danych, wówczas w trybie projektowania pojawią się rzeczywiste dane w komponencie [DataGrid].
8.5. ViewState – komponenty list danych
W tym miejscu chcemy wyjaśnić mechanizm działania [VIEWSATE] w przypadku komponentów list danych. Można się wahać między dwiema metodami utrzymania stanu komponentu listy danych pomiędzy dwoma żądaniami klienta:
- ustawienie jego atrybutu [VIEWSTATE] na wartość „true”
- ustawienie jego atrybutu [VIEWSTATE] na wartość „false” i zapisanie jego źródła danych w sesji, aby móc powiązać komponent z tym źródłem podczas następnego żądania.
Poniższy przykład ilustruje niektóre aspekty działania mechanizmu [VIEWSTATE] w kontenerach danych. Przy pierwszym żądaniu klienta aplikacja wyświetla następujący widok:
![]() |
nr | nazwa | typ | właściwości | rola |
1 | DataGrid | EnableViewState=true | wyświetla źródło danych S | |
2 | DataGrid | EnableViewState=true | wyświetla to samo źródło S co [DataGrid1] | |
3 | Przycisk | EnableViewState=false | przycisk [submit] |
Komponent [DataGrid1] jest obsługiwany przez mechanizm [VIEWSTATE]. Chcemy ustalić, czy ten mechanizm, który odświeża wyświetlanie komponentu [DataGrid1] przy każdym żądaniu, odświeża również jego źródło danych. W tym celu źródło to jest powiązane z komponentem [DataGrid2]. Generowanie tego komponentu przy każdym żądaniu odbywa się poprzez jawne powiązanie ze źródłem danych komponentu [DataGrid1]. Posiada on również atrybut [EnableViewState] powiązany z [vrai].
Kod prezentacji aplikacji [main.aspx] wygląda następująco:
<%@ page src="main.aspx.vb" inherits="main" autoeventwireup="false" %>
<HTML>
<HEAD>
<title></title>
</HEAD>
<body>
<form runat="server">
<table>
<tr>
<td align="center">DataGrid 1</td>
<td align="center">
DataGrid 2</td>
</tr>
<tr>
<td>
<asp:DataGrid id="DataGrid1" runat="server" ...>
<SelectedItemStyle ...></SelectedItemStyle>
....
</asp:DataGrid></td>
<td>
<asp:DataGrid id="Datagrid2" runat="server" ...>
....
</asp:DataGrid></td>
</tr>
</table>
<asp:Button id="Button1" runat="server" Text="Envoyer"></asp:Button>
</form>
</body>
</HTML>
Kontroler [main.aspx.vb] ma następujące parametry:
Imports System.Data
Public Class main
Inherits System.Web.UI.Page
Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
Protected WithEvents Datagrid2 As System.Web.UI.WebControls.DataGrid
Protected WithEvents Button1 As System.Web.UI.WebControls.Button
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' przy pierwszym zapytaniu źródło danych jest definiowane i powiązane z pierwszą siatką danych
If Not IsPostBack Then
'zdefiniuj źródło danych
With DataGrid1
.DataSource = createDataSource()
.DataBind()
End With
End If
' przy każdym zapytaniu siatka danych 2 jest powiązana ze źródłem pierwszej siatki danych
With Datagrid2
.DataSource = DataGrid1.DataSource
.DataBind()
End With
End Sub
Private Function createDataSource() As DataTable
' inicjuje się źródło danych
Dim thèmes As New DataTable
' kolumny
With thèmes.Columns
.Add("id", GetType(System.Int32))
.Add("thème", GetType(System.String))
.Add("description", GetType(System.String))
End With
' kolumna „id” będzie kluczem głównym
thèmes.Constraints.Add("cléprimaire", thèmes.Columns("id"), True)
' wiersze
Dim ligne As DataRow
For i As Integer = 0 To 4
ligne = thèmes.NewRow
ligne.Item("id") = i.ToString
ligne.Item("thème") = "thème" + i.ToString
ligne.Item("description") = "description du thème " + i.ToString
thèmes.Rows.Add(ligne)
Next
Return thèmes
End Function
Private Sub InitializeComponent()
End Sub
End Class
Metoda [createDataSource] tworzy źródło S typu [DataTable]. Nie będziemy się tutaj zagłębiać w jej kod, ponieważ nie jest on przedmiotem tego przykładu. Interesuje nas sposób, w jaki zbudowano dwa komponenty [DataGrid]:
- komponent [DataGrid1] jest powiązany z tabelą S tylko raz, podczas pierwszego zapytania. Później nie jest już z nią powiązany.
- komponent [DataGrid2] jest powiązany ze źródłem [DataGrid1.DataSource] przy każdym nowym zapytaniu.
Podczas pierwszego zapytania otrzymujemy następujący widok:

Zgodnie z logiką oba komponenty wyświetlają źródło danych, z którym zostały powiązane. Używamy przycisku [Envoyer], aby wywołać żądanie [PostBack] do serwera. Uzyskany widok wygląda wówczas następująco:

Zauważamy, że komponent [DataGrid1] zachował swoją wartość, ale nie komponent [DataGrid2]. Wyjaśnienie:
- jeszcze przed uruchomieniem procedury [Page_Load] obiekty [DataGrid1] i [DataGrid2] przywróciły wartości, które miały podczas poprzedniego zapytania, dzięki mechanizmowi [viewstate]. W rzeczywistości oba mają właściwość [EnableViewState] ustawioną na [vrai].
- uruchamia się procedura [Page_Load]. Ponieważ mamy do czynienia z operacją [PostBack], komponent [DataGrid1] nie jest modyfikowany przez [Page_Load] (patrz kod). W związku z tym zachowuje on wartość pobraną za pomocą operacji [viewstate]. Pokazuje to powyższy ekran.
- Komponent [DataGrid2] jest natomiast powiązany poprzez [DataBind] ze źródłem danych [DataGrid1.DataSource]. Zostaje więc odbudowany, a wartość, którą właśnie pobrał za pomocą [viewstate], zostaje utracona. Warto byłoby zatem, aby posiadał on właściwość [EnableViewState] powiązaną z [faux], co pozwoliłoby uniknąć niepotrzebnego zarządzania jego stanem. Powyższy ekran pokazuje, że [DataGrid2] został powiązany z pustym źródłem. Ponieważ źródłem tym jest [DataGrid1.DataSource], można wywnioskować, że chociaż mechanizm [viewstate] prawidłowo przywraca wyświetlanie komponentu [DataGrid1], to nie przywraca jednak jego właściwości, takich jak [DataSource].
Jakie wnioski można wyciągnąć z tego przykładu? Należy unikać ustawiania właściwości [EnableViewState] kontenera danych na wartość [vrai], jeśli kontener ten ma być powiązany (DataBind) ze źródłem danych przy każdym zapytaniu. Istnieją jednak pewne przypadki, w których nawet w takiej sytuacji właściwość [EnableViewState] kontenera musi pozostać ustawiona na [vrai], w przeciwnym razie zdarzenia, które chcielibyśmy obsłużyć, nie zostaną wywołane. Będziemy mieli okazję zapoznać się z takim przykładem.
Często źródło danych kontenera danych zmienia się w trakcie kolejnych zapytań. Dlatego przy każdym zapytaniu należy powiązać kontener ze źródłem danych. Często źródło to jest przechowywane w sesji, aby zapytania miały do niego dostęp. Nasz drugi przykład ilustruje ten mechanizm. Aplikacja udostępnia tylko jeden widok:
![]() |
nr | nazwa | typ | właściwości | rola |
1 | DataGrid | EnableViewState=true | wyświetla źródło danych S | |
2 | DataGrid | EnableViewState=false | wyświetla to samo źródło S co [DataGrid1] | |
3 | Przycisk | EnableViewState=false | przycisk [submit] | |
4 | Etykieta | EnableViewState=false | teksty informacyjne |
Komponent [DataGrid1] jest powiązany z danymi wyłącznie podczas pierwszego zapytania. Dzięki mechanizmowi [viewstate] zachowa swoją wartość w kolejnych zapytaniach. Komponent [DataGrid2] jest powiązany przy każdym żądaniu ze źródłem danych, do którego przy każdym nowym żądaniu dodawany jest element. Należy zatem powiązać (DataBind) komponent [DataGrid2] przy każdym żądaniu. Zgodnie z wcześniejszymi zaleceniami ustawiono więc jego atrybut [EnableViewState] na [faux]. W ten sposób przy drugim zapytaniu (po naciśnięciu przycisku [Envoyer]) otrzymujemy następującą odpowiedź:

Komponent [DataGrid1] zachował swoją pierwotną wartość. Komponent [DataGrid2] zawiera o jeden element więcej. Trzy pozycje [1,2,2] odpowiadają numerowi zapytania. Widać, że jedna z tych pozycji jest błędna. Spróbujemy zrozumieć, dlaczego tak się stało.
Kod prezentacji aplikacji [main.aspx] wygląda następująco:
<%@ Page src="main.aspx.vb" inherits="main" autoeventwireup="false" Language="vb" %>
<HTML>
<HEAD>
<title></title>
</HEAD>
<body>
<form runat="server">
<table>
<tr>
<td align="center" bgColor="#ccffcc">DataGrid 1</td>
<td align="center" bgColor="#ffff99">DataGrid 2</td>
</tr>
<tr>
<td vAlign="top">
<asp:DataGrid id="DataGrid1" runat="server" ...>
...
</asp:DataGrid>
</td>
<td vAlign="top">
<asp:DataGrid id="Datagrid2" runat="server" ... EnableViewState="False">
....
</asp:DataGrid>
</td>
</tr>
</table>
<P>Numéro de requête :
<asp:Label id="lblInfo1" runat="server" EnableViewState="False"></asp:Label>,
<asp:Label id="lblInfo2" runat="server" EnableViewState="False"></asp:Label>,
<asp:Label id="lblInfo3" runat="server" EnableViewState="False"></asp:Label></P>
<P>
<asp:Button id="Button1" runat="server" Text="Envoyer" EnableViewState="False"></asp:Button></P>
</form>
</body>
</HTML>
Kod kontrolera [main.aspx.vb] brzmi następująco:
Imports System.Data
Imports System
Public Class main
Inherits System.Web.UI.Page
Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
Protected WithEvents Datagrid2 As System.Web.UI.WebControls.DataGrid
Protected WithEvents Button1 As System.Web.UI.WebControls.Button
Protected WithEvents lblInfo1 As System.Web.UI.WebControls.Label
Protected WithEvents lblInfo2 As System.Web.UI.WebControls.Label
Protected WithEvents lblInfo3 As System.Web.UI.WebControls.Label
Dim dtThèmes As DataTable
Dim numRequête1 As Integer
Dim numRequête2 As Integer
Dim numRequête3 As New entier
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' przy pierwszym zapytaniu źródło danych jest zdefiniowane i powiązane z pierwszą siatką danych
If Not IsPostBack Then
'zdefiniuj źródło danych
dtThèmes = createDataSource()
With DataGrid1
.DataSource = dtThèmes
.DataBind()
End With
' zapisywanie informacji w sesji
Session("source") = dtThèmes
numRequête1 = 0 : Session("numRequête1") = numRequête1
numRequête2 = 0 : Session("numRequête2") = numRequête2
numRequête3.valeur = 0 : Session("numRequête3") = numRequête3
End If
' przy każdym zapytaniu dodawany jest nowy motyw
dtThèmes = CType(Session("source"), DataTable)
Dim nbThèmes = dtThèmes.Rows.Count
Dim ligne As DataRow = dtThèmes.NewRow
With ligne
.Item("id") = nbThèmes.ToString
.Item("thème") = "thème" + nbThèmes.ToString
.Item("description") = "description du thème " + nbThèmes.ToString
End With
dtThèmes.Rows.Add(ligne)
'– powiązanie siatki danych 2 ze źródłem danych
With Datagrid2
.DataSource = dtThèmes
.DataBind()
End With
' informacje o liczbie zapytań
numRequête1 = CType(Session("numRequête1"), Integer)
numRequête1 += 1
lblInfo1.Text = numRequête1.ToString
numRequête2 = CType(Session("numRequête2"), Integer)
numRequête2 += 1
lblInfo2.Text = numRequête2.ToString
numRequête3 = CType(Session("numRequête3"), entier)
numRequête3.valeur += 1
lblInfo3.Text = numRequête3.valeur.ToString
' zapisujemy pewne informacje w sesji
Session("numRequête2") = numRequête2
End Sub
Private Function createDataSource() As DataTable
....
End Function
End Class
Public Class entier
Private _valeur As Integer
Public Property valeur() As Integer
Get
Return _valeur
End Get
Set(ByVal Value As Integer)
_valeur = Value
End Set
End Property
End Class
Nie zamieściliśmy kodu metody [createDataSource]. Jest on taki sam jak w poprzedniej aplikacji, z tą różnicą, że w kodzie źródłowym umieszczono tylko trzy wiersze. Najpierw przyjrzyjmy się obsłudze źródła danych oraz dwóch kontenerów:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' przy pierwszym zapytaniu źródło danych jest definiowane i powiązane z pierwszą siatką danych
If Not IsPostBack Then
'zdefiniuj źródło danych
dtThèmes = createDataSource()
With DataGrid1
.DataSource = dtThèmes
.DataBind()
End With
' zapisywanie informacji w sesji
Session("source") = dtThèmes
...
End If
' przy każdym zapytaniu dodawany jest nowy motyw
dtThèmes = CType(Session("source"), DataTable)
Dim nbThèmes = dtThèmes.Rows.Count
Dim ligne As DataRow = dtThèmes.NewRow
With ligne
.Item("id") = nbThèmes.ToString
.Item("thème") = "thème" + nbThèmes.ToString
.Item("description") = "description du thème " + nbThèmes.ToString
End With
dtThèmes.Rows.Add(ligne)
'— powiązanie siatki danych 2 ze źródłem danych
With Datagrid2
.DataSource = dtThèmes
.DataBind()
End With
...
End Sub
Komponent [DataGrid1] jest powiązany ze źródłem danych S tylko podczas pierwszego zapytania (nie dotyczy IsPostBack). Jest on wówczas umieszczany w sesji. Później już nie będzie. Źródło danych S umieszczone w sesji jest pobierane przy każdym zapytaniu i uzupełniane o nowy wiersz. Komponent [DataGrid2] jest przy każdym zapytaniu jawnie powiązany ze źródłem danych S. Dlatego jego zawartość powiększa się o jeden wiersz przy każdym zapytaniu. Można zauważyć, że po zmodyfikowaniu zawartości źródła danych S nie umieszcza się go ponownie jawnie w sesji za pomocą operacji:
Dlaczego? Gdy rozpoczyna się żądanie, sesja zawiera obiekt Session("source") typu [DataTable], który jest źródłem danych w stanie z ostatniego żądania. Nazwijmy ten obiekt Session("source") literą S. Gdy piszemy:
dtThèmes = CType(Session("source"), DataTable)
[dtThèmes] i [S] to dwa odwołania do tego samego obiektu [DataTable]. W związku z tym, gdy kod obiektu [Page_Load] dodaje element do tabeli, do której odwołuje się obiekt [dtThèmes], dodaje go jednocześnie do tabeli, do której odwołuje się obiekt [S]. Po zakończeniu wykonywania strony wszystkie obiekty obecne w sesji zostaną zapisane, a zatem obiekt Session("source"), c.a.d. S, c.a.d oraz [dtThèmes]. Zapisywana jest zatem nowa zawartość źródła danych. Nie było potrzeby wpisywania:
aby wykonać tę kopię zapasową, ponieważ Session("source") ma już wartość [dtThèmes]. Nie ma to już zastosowania, gdy dane zapisane w sesji nie są obiektami, takimi jak struktury [Integer, Float, ...]. Pokazuje to zarządzanie licznikami żądań:
Dim numRequête1 As Integer
Dim numRequête2 As Integer
Dim numRequête3 As New entier
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' przy pierwszym zapytaniu źródło danych jest definiowane i powiązane z pierwszą siatką danych
....
' zapisywanie informacji w sesji
Session("source") = dtThèmes
numRequête1 = 0 : Session("numRequête1") = numRequête1
numRequête2 = 0 : Session("numRequête2") = numRequête2
numRequête3.valeur = 0 : Session("numRequête3") = numRequête3
End If
' przy każdym zapytaniu dodaje się nowy motyw
....
' informacje o liczbie zapytań
numRequête1 = CType(Session("numRequête1"), Integer)
numRequête1 += 1
lblInfo1.Text = numRequête1.ToString
numRequête2 = CType(Session("numRequête2"), Integer)
numRequête2 += 1
lblInfo2.Text = numRequête2.ToString
numRequête3 = CType(Session("numRequête3"), entier)
numRequête3.valeur += 1
lblInfo3.Text = numRequête3.valeur.ToString
' zapisywanie niektórych informacji w sesji
Session("numRequête2") = numRequête2
End Sub
Private Function createDataSource() As DataTable
....
End Function
Private Sub InitializeComponent()
End Sub
End Class
Public Class entier
Private _valeur As Integer
Public Property valeur() As Integer
Get
Return _valeur
End Get
Set(ByVal Value As Integer)
_valeur = Value
End Set
End Property
Liczniki żądań są zapisywane w trzech elementach:
- numRequête1 i numRequête2 typu [Integer] – [Integer] nie jest klasą, lecz strukturą
- numRequête3 typu [entier] – [entier] jest klasą zdefiniowaną specjalnie na tę okazję
Gdy piszemy:
numRequête1 = CType(Session("numRequête1"), Integer)
..
numRequête2 = CType(Session("numRequête2"), Integer)
..
numRequête3 = CType(Session("numRequête3"), entier)
..
- Struktura [Session("numRequête1")] jest kopiowana do [numRequête1]. W związku z tym, gdy element [numRequête1] ulega zmianie, element [Session("numRequête1")] pozostaje niezmieniony
- to samo dotyczy elementów [Session("numRequête2")] i [numRequête2]
- Elementy [Session("numRequête3")] i [numRequête3] są obydwa odwołaniami do tego samego obiektu typu [entier]. Obiekt, do którego się odnoszą, może być modyfikowany niezależnie przez jedno lub drugie odwołanie.
Na tej podstawie można wywnioskować, że nie ma sensu pisać:
w celu zapisania nowej wartości obiektu [numRequête3] w sesji. Natomiast należy zapisać:
aby zapisać nowe wartości struktur [numRequête1] i [numRequête2]. Robimy to tylko dla [numRequête2], co wyjaśnia, dlaczego na zrzucie ekranu uzyskanym po wykonaniu drugiego zapytania licznik [numRequête1] jest błędny.
Należy zatem pamiętać, że po wprowadzeniu do sesji dane nie muszą być do niej wielokrotnie ponownie wprowadzane, jeśli są reprezentowane przez obiekt. W pozostałych przypadkach należy to zrobić, jeśli dane uległy zmianie.
8.6. Wyświetlanie listy danych za pomocą paginowanego i posortowanego obiektu DataGrid
Komponent [DataGrid] umożliwia wyświetlenie zawartości pliku [DataSet]. Do tej pory zawsze tworzyliśmy nasze pliki [DataSet] „ręcznie”. Tym razem wykorzystujemy plik [DataSet] pochodzący z bazy danych. Tworzymy następującą aplikację MVC:
![]() |
Trzy widoki zostaną wbudowane w kod prezentacyjny kontrolera [main.aspx] w postaci kontenerów. Dlatego ta aplikacja ma tylko jedną stronę [main.aspx].
8.6.1. Klasy biznesowe
Klasa [produits] zapewnia dostęp do następującej bazy danych ACCESS:

Kod klasy [produits] jest następujący:
Imports System
Imports System.Data
Imports System.Data.OleDb
Imports System.Xml
Namespace st.istia.univangers.fr
Public Class produits
Private chaineConnexionOLEDB As String
Public Sub New(ByVal chaineConnexionOLEDB As String)
' zapisuje się ciąg połączenia
Me.chaineConnexionOLEDB = chaineConnexionOLEDB
End Sub
Public Function getDataSet(ByVal commande As String) As DataSet
' tworzy się obiekt DataAdapter w celu odczytania danych ze źródła OLEDB
Dim adaptateur As New OleDbDataAdapter(commande, chaineConnexionOLEDB)
' tworzy się obraz w pamięci wyniku zapytania SELECT
Dim contenu As New DataSet
Try
adaptateur.Fill(contenu)
Catch e As Exception
Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
End Try
' zwracamy wynik
Return contenu
End Function
End Class
End Namespace
Baza danych ACCESS będzie zarządzana za pomocą sterownika OLEDB. Konstruktorowi klasy [produits] podaje się ciąg identyfikujący połączenie, sterownik OLEDB oraz bazę, którą należy zarządzać. Klasa [produits] posiada metodę [getDataSet], która zwraca obiekt [DataSet] uzyskany w wyniku wykonaniazapytania SQL [select], którego tekst jest przekazywany jako parametr. W trakcie działania tej metody odbywa się kilka operacji: utworzenie połączenia z bazą danych, wykonanie [select] oraz zamknięcie połączenia. Wszystko to może spowodować wyjątek, który jest tutaj obsługiwany. Program testowy mógłby wyglądać następująco:
Option Explicit On
Option Strict On
' przestrzenie nazw
Imports System
Imports System.Data
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
' strona testowa
Module testproduits
Sub Main(ByVal arguments() As String)
' wyświetla zawartość tabeli produktów
' tabela znajduje się w bazie ACCESS, której nazwa jest nadawana plikowi
Const syntaxe1 As String = "pg bdACCESS"
' sprawdzanie parametrów programu
If arguments.Length <> 1 Then
' komunikat o błędzie
Console.Error.WriteLine(syntaxe1)
' koniec
Environment.Exit(1)
End If
' przygotowywanie ciągu połączenia
Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + arguments(0)
' tworzenie obiektu produktów
Dim objProduits As produits = New produits(chaineConnexion)
' pobieranie tabeli produktów do zestawu danych
Dim contenu As DataSet
Try
contenu = objProduits.getDataSet("select id,nom,prix from liste")
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' wyświetlanie jego zawartości
Dim lignes As DataRowCollection = contenu.Tables(0).Rows
For i As Integer = 0 To lignes.Count - 1
' wiersz i tabeli
Console.Out.WriteLine(lignes(i).Item("id").ToString + "," + lignes(i).Item("nom").ToString + _
"," + lignes(i).Item("prix").ToString)
Next
End Sub
End Module
End Namespace
Kompilacja poszczególnych elementów tej aplikacji przebiega w następujący sposób:
dos>vbc /t:library /r:system.dll /r:system.data.dll /r:system.xml.dll produits.vb
dos>vbc /r:produits.dll /r:system.data.dll /r:system.xml.dll /r:system.dll testproduits.vb
dos>dir
06/05/2004 16:52 118 784 produits.mdb
07/05/2004 11:07 902 produits.vb
07/04/2004 07:01 1 532 testproduits.vb
07/05/2004 14:21 3 584 produits.dll
07/05/2004 14:22 4 608 testproduits.exe
dos>testproduits produits.mdb
1,produit1,10
2,produit2,20
3,produit3,30
8.6.2. Widoki
Skoro mamy już klasę zapewniającą dostęp do danych, zajmiemy się teraz pisaniem kontrolerów i widoków tej aplikacji internetowej. Najpierw przyjrzyjmy się, jak ona działa. Pierwszy widok wygląda następująco:
![]() |
nr | nazwa | typ | właściwości | rola |
1 | TextBox | EnableViewState=true | pole wprowadzania zapytania select | |
2 | RequiredFieldValidator | EnableViewState=false | sprawdza, czy występuje 1 | |
3 | TextBox | EnableViewState=true | pole wprowadzania danych – określa liczbę wierszy danych, które mają być wyświetlane na jednej stronie wyników | |
4 | RequiredFieldValidator | EnableViewState=false | sprawdza, czy występuje cyfra 3 | |
5 | RangeValidator | EnableViewState=false | sprawdza, czy (3) mieści się w przedziale [1,30] | |
6 | Przycisk | EnableViewState=false | przycisk [submit] |
Nazwiemy ten widok widokiem [formulaire]. Pozwala on użytkownikowi na wykonanie zapytania SELECT SQL w bazie danych [produits.mdb]. Wykonanie zapytania powoduje utworzenie nowego widoku:
![]() |
nr | nazwa | typ | właściwości | rola |
1 | Etykieta | EnableViewState=false | pole informacyjne | |
2 | RadioButton | EnableViewState=false GroupName=rdTri | umożliwia wybór kierunku sortowania | |
3 | DataGrid | EnableViewState=true AllowPaging=true AllowSorting=true | tabela wyświetlająca wyniki zapytania SELECT | |
4 | LinkButton | EnableViewState=false | przycisk-link [submit] |
Nazwiemy ten widok widokiem [résultats]. To właśnie on zawiera element [DataGrid], który wyświetli wynik polecenia select SQL. Użytkownik może popełnić błąd w swoim zapytaniu. Niektóre błędy zostaną mu zgłoszone w widoku [erreurs] dzięki kontrolom poprawności.

Inne błędy zostaną mu zgłoszone w widoku [erreurs]:

Wyświetlany jest widok [erreurs]:
![]() |
nr | nazwa | typ | właściwości | rola |
1 | zmienna | kod HTML niezbędny do wyświetlania błędów | ||
3 | LinkButton | EnableViewState=false | przycisk-link [submit] |
Trzy widoki aplikacji to trzy różne kontenery (panele) na tej samej stronie [main.aspx]. Kod prezentacji wygląda następująco:
<%@ Page src="main.aspx.vb" inherits="main" autoeventwireup="false" Language="vb" %>
<HTML>
<HEAD>
</HEAD>
<body>
<P>Liaison de données avec un DataGrid</P>
<HR width="100%" SIZE="1">
<form runat="server">
<asp:panel id="vueFormulaire" runat="server">
<P>Commande [select] à exécuter sur la table LISTE</P>
<P> Exemple : select id, nom, prix from LISTE
</P>
<P>
<asp:TextBox id="txtSelect" runat="server" Columns="60"></asp:TextBox></P>
<P>
<asp:RequiredFieldValidator id="RequiredFieldValidator1" runat="server" EnableViewState="False" EnableClientScript="False"
ErrorMessage="Indiquez la requête [select] à exécuter" ControlToValidate="txtSelect" Display="Dynamic"></asp:RequiredFieldValidator></P>
<P>Nombre de lignes par page :
<asp:TextBox id="txtPages" runat="server" Columns="3"></asp:TextBox></P>
<P>
<asp:RequiredFieldValidator id="RequiredFieldValidator2" runat="server" EnableViewState="False" EnableClientScript="False"
ErrorMessage="Indiquez le nombre de lignes par page désirées" ControlToValidate="txtPages" Display="Dynamic"></asp:RequiredFieldValidator>
<asp:RangeValidator id="RangeValidator1" runat="server" EnableViewState="False" EnableClientScript="False"
ErrorMessage="Vous devez indiquer un nombre entre 1 et 30" ControlToValidate="txtPages" Display="Dynamic"
Type="Integer" MinimumValue="1" MaximumValue="30"></asp:RangeValidator></P>
<P>
<asp:Button id="btnExécuter" runat="server" EnableViewState="False" Text="Exécuter"></asp:Button></P>
</asp:panel>
<asp:Panel id="vueRésultats" runat="server">
<P>Résultats de la requête
<asp:Label id="lblSelect" runat="server"></asp:Label></P>
<P>Tri
<asp:RadioButton id="rdCroissant" runat="server" Text="croissant" Checked="True" GroupName="rdTri"></asp:RadioButton>
<asp:RadioButton id="rdDécroissant" runat="server" Text="décroissant" GroupName="rdTri"></asp:RadioButton></P>
<P>
<asp:DataGrid id="DataGrid1" runat="server" BorderColor="#CC9966" BorderStyle="None" BorderWidth="1px"
BackColor="White" CellPadding="4" AllowPaging="True" PageSize="4" AllowSorting="True">
<SelectedItemStyle Font-Bold="True" ForeColor="#663399" BackColor="#FFCC66"></SelectedItemStyle>
<ItemStyle ForeColor="#330099" BackColor="White"></ItemStyle>
<HeaderStyle Font-Bold="True" ForeColor="#FFFFCC" BackColor="#990000"></HeaderStyle>
<FooterStyle ForeColor="#330099" BackColor="#FFFFCC"></FooterStyle>
<PagerStyle NextPageText="Suivant" PrevPageText="Précédent" HorizontalAlign="Center"
ForeColor="#330099" BackColor="#FFFFCC"></PagerStyle>
</asp:DataGrid></P>
<P>
<asp:LinkButton id="lnkRésultats" runat="server" EnableViewState="False">Retour au formulaire</asp:LinkButton></P>
</asp:Panel>
<asp:Panel id="vueErreurs" runat="server">
<P>Les erreurs suivantes se sont produites :</P>
<% =erreursHTML %>
<P>
<asp:LinkButton id="lnkErreurs" runat="server" EnableViewState="False">Retour vers le formulaire</asp:LinkButton></P>
</asp:Panel>
</form>
</body>
</HTML>
8.6.3. Konfiguracja komponentu DataGrid
Przyjrzyjmy się paginacji komponentu [DataGrid] – opcji, z którą mamy do czynienia po raz pierwszy. W powyższym kodzie paginacja ta jest kontrolowana przez następujące atrybuty:
<asp:DataGrid id="DataGrid1" runat="server" PageSize="4" AllowPaging="True" ...>
...
<PagerStyle NextPageText="Suivant" PrevPageText="Précédent" ...></PagerStyle>
</asp:DataGrid></P>
zezwala na paginację | |
cztery wiersze danych na stronę | |
Tekst linku prowadzącego do następnej strony źródła danych | |
Tekst linku prowadzącego do poprzedniej strony źródła danych |
Informacje te można wprowadzić bezpośrednio w atrybutach tagu <asp:datagrid>. Można również skorzystać z elementu [WebMatrix]. W oknie właściwości elementu [DataGrid] należy kliknąć link [Générateur de propriétés]:

Pojawi się następujący kreator:

Wybieramy opcję [Pagination]:

Powyżej widoczne są wartości atrybutów paginacji z [DataGrid] w kodzie prezentacji.
Ponadto zezwalamy na sortowanie danych według jednej z kolumn elementu [DataGrid]. Można to zrobić na różne sposoby. Jednym z nich jest zdefiniowanie właściwości [AllowSorting=true] w oknie właściwości elementu [DataGrid]. Można również skorzystać z generatora właściwości. Niezależnie od zastosowanej metody skutkuje to obecnością atrybutu [AllowSorting=true] w tagu <asp:DataGrid> komponentu:
<asp:DataGrid id="DataGrid1" runat="server" ... AllowPaging="True" PageSize="4" AllowSorting="True">
8.6.4. Kontrolery
Kontroler [global.asax, global.asax.vb] ma następujący kod:
[global.asax]
[global.asax.vb]
Imports st.istia.univangers.fr
Imports System.Configuration
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' tworzenie obiektu produktów
Dim objProduits As produits
Try
objProduits = New produits(ConfigurationSettings.AppSettings("OLEDBStringConnection"))
' umieszczamy obiekt w aplikacji
Application("objProduits") = objProduits
' 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
End Class
Po uruchomieniu aplikacji (Application_Start) tworzymy obiekt [produits] i umieszczamy go w aplikacji, aby był dostępny dla wszystkich żądań wszystkich klientów. Jeśli podczas tworzenia tego obiektu wystąpi wyjątek, zostanie on odnotowany w aplikacji. Procedura [Application_Start] zostanie wykonana tylko raz. Następnie kontroler [global.asax] nie będzie już interweniował. Zadanie to przejmie wówczas kontroler [main.aspx.vb]:
Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Data
Imports st.istia.univangers.fr
Imports System
Imports System.Xml
Public Class main
Inherits System.Web.UI.Page
' komponenty strony
Protected WithEvents txtSelect As System.Web.UI.WebControls.TextBox
Protected WithEvents RequiredFieldValidator1 As System.Web.UI.WebControls.RequiredFieldValidator
Protected WithEvents txtPages As System.Web.UI.WebControls.TextBox
Protected WithEvents RequiredFieldValidator2 As System.Web.UI.WebControls.RequiredFieldValidator
Protected WithEvents RangeValidator1 As System.Web.UI.WebControls.RangeValidator
Protected WithEvents btnExécuter As System.Web.UI.WebControls.Button
Protected WithEvents vueFormulaire As System.Web.UI.WebControls.Panel
Protected WithEvents lblSelect As System.Web.UI.WebControls.Label
Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
Protected WithEvents lnkRésultats As System.Web.UI.WebControls.LinkButton
Protected WithEvents vueRésultats As System.Web.UI.WebControls.Panel
Protected WithEvents lnkErreurs As System.Web.UI.WebControls.LinkButton
Protected WithEvents vueErreurs As System.Web.UI.WebControls.Panel
Protected WithEvents rdCroissant As System.Web.UI.WebControls.RadioButton
Protected WithEvents rdDécroissant As System.Web.UI.WebControls.RadioButton
' dane strony
Protected erreursHTML As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' sprawdzamy, czy w aplikacji wystąpił błąd
If CType(Application("erreur"), Boolean) Then
' aplikacja nie zainicjowała się poprawnie
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
afficheErreurs(erreurs, False)
Exit Sub
End If
'pierwsze żądanie
If Not IsPostBack Then
' wyświetla się pusty formularz
afficheFormulaire()
End If
End Sub
Private Sub afficheErreurs(ByVal erreurs As ArrayList, ByVal afficheLien As Boolean)
' wyświetla widok błędów
erreursHTML = ""
For i As Integer = 0 To erreurs.Count - 1
erreursHTML += "<li>" + erreurs(i).ToString + "</li>" + ControlChars.CrLf
Next
lnkErreurs.Visible = afficheLien
' wyświetlany jest widok [erreurs]
vueErreurs.Visible = True
vueFormulaire.Visible = False
vueRésultats.Visible = False
End Sub
Private Sub afficheFormulaire()
' wyświetla widok [formulaire]
vueFormulaire.Visible = True
vueErreurs.Visible = False
vueRésultats.Visible = False
End Sub
Private Sub afficheRésultats(ByVal sqlTexte As String, ByVal données As DataView)
' inicjuje się kontrolki
lblSelect.Text = sqlTexte
With DataGrid1
.DataSource = données
.PageSize = CType(txtPages.Text, Integer)
.CurrentPageIndex = 0
.DataBind()
End With
' wyświetla się widok [résultats]
vueRésultats.Visible = True
vueFormulaire.Visible = False
vueErreurs.Visible = False
End Sub
Private Sub btnExécuter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExécuter.Click
' czy strona jest poprawna?
If Not Page.IsValid Then
afficheFormulaire()
Exit Sub
End If
' wykonywanie zapytania SELECT klienta
Dim données As DataView
Try
données = CType(Application("objProduits"), produits).getDataSet(txtSelect.Text.Trim).Tables(0).DefaultView
Catch ex As Exception
Dim erreurs As New ArrayList
erreurs.Add("erreur d'accès à la base de données (" + ex.Message + ")")
afficheErreurs(erreurs, True)
Exit Sub
End Try
' wszystko w porządku – wyświetlane są wyniki
afficheRésultats(txtSelect.Text.Trim, données)
' dane są zapisywane w sesji
Session("données") = données
End Sub
Private Sub retourFormulaire(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkErreurs.Click, lnkRésultats.Click
' zestaw widoków
vueErreurs.Visible = False
vueFormulaire.Visible = True
vueRésultats.Visible = False
End Sub
Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
' zmiana strony
With DataGrid1
.CurrentPageIndex = e.NewPageIndex
.DataSource = CType(Session("données"), DataView)
.DataBind()
End With
End Sub
Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
' sortowanie widoku danych
Dim données As DataView = CType(Session("données"), DataView)
données.Sort = e.SortExpression + " " + CType(IIf(rdCroissant.Checked, "asc", "desc"), String)
' wyświetlanie
With DataGrid1
.CurrentPageIndex = 0
.DataSource = données
.DataBind()
End With
End Sub
End Class
Podczas ładowania strony [Page_Load] sprawdzamy najpierw, czy aplikacja zainicjowała się poprawnie. Jeśli tak nie jest, wyświetlany jest widok [erreurs] bez linku powrotnego do formularza, ponieważ link ten jest wówczas zbędny. W rzeczywistości tylko widok [erreurs] może zostać wyświetlony, jeśli aplikacja nie zdołała poprawnie się zainicjować. W przeciwnym razie wyświetlany jest widok [formulaire], jeśli mamy do czynienia z pierwszym żądaniem klienta. Co do pozostałych kwestii, pozostawiamy czytelnikowi zadanie zrozumienia kodu. Skupimy się jedynie na trzech procedurach: procedurze [btnExécuter_Click], która jest wykonywana, gdy użytkownik zażądał wykonania zapytania SQL wprowadzonego w widoku [formulaire], procedurę [DataGrid1_PageIndexChanged], która jest wykonywana, gdyużytkownik korzysta z linków [Suivant] i [Précédent] w widoku [DataGrid] oraz procedura [DataGrid1_SortCommand], która jest uruchamiana, gdy użytkownik kliknie nagłówek kolumny w celu posortowania danych zgodnie zkolejności kolumny. Kierunek sortowania – rosnący lub malejący – jest ustalany za pomocą dwóch przycisków opcji sortowania.
W procedurze [btnExécuter_Click] zaczyna się zatem od sprawdzenia, czy strona jest poprawna, czy nie. Gdy uruchamia się procedura [btnExécuter_Click], przeprowadzono już weryfikacje związane z różnymi kontrolami poprawności strony. Dla każdej kontroli poprawności ustawiono dwa atrybuty:
na „prawda”, jeśli sprawdzone dane są poprawne, na „fałsz” w przeciwnym razie | |
komunikat o błędzie, jeśli sprawdzone dane okazały się nieprawidłowe |
Dla samej strony ustawiono atrybut [IsValid]. Ma on wartość „prawda” tylko wtedy, gdy wszystkie elementy sprawdzające poprawność mają atrybut [IsValid] ustawiony na „prawda”. Jeśli tak nie jest, należy wyświetlić widok [formulaire]. Zawiera on elementy sprawdzające poprawność, które wyświetlą swój atrybut [errorMessage]. Jeśli strona jest poprawna, wykorzystuje się obiekt typu [produits] utworzony przez [Application_Start] w celu uzyskania obiektu [DataSet] odpowiadającego wykonaniu zapytania select SQL. Następnie przekształca się go w obiekt [DataView]:
Dim données As DataView
Try
données = CType(Application("objProduits"), produits).getDataSet(txtSelect.Text.Trim).Tables(0).DefaultView
...
Można było po prostu pracować z obiektem [DataSet] i zapisać:
Dim données As DataSet
Try
données = CType(Application("objProduits"), produits).getDataSet(txtSelect.Text.Trim)
...
Obiekt [DataSet] stanowi w zasadzie zbiór tabel połączonych relacjami. W naszej konkretnej aplikacji obiekt [DataSet], uzyskany z klasy [produits], zawiera tylko jedną tabelę – tę wynikającą z instrukcji [select]. Tabelę można sortować, podczas gdy obiektu typu [DataSet] nie da się sortować, a nas interesuje właśnie sortowanie uzyskanych danych. Aby pracować z tabelą wynikową instrukcji [select], można by napisać:
Dim données As DataTable
Try
données = CType(Application("objProduits"), produits).getDataSet(txtSelect.Text.Trim).Tables(0)
...
Obiekt [DataTable], choć reprezentuje tabelę bazy danych, nie posiada metody sortowania. W tym celu konieczne jest utworzenie widoku tej tabeli. Widok jest obiektem typu [DataView]. Za pomocą filtrów można uzyskać różne widoki tej samej tabeli. Tabela posiada widok domyślny, w którym nie zdefiniowano żadnego filtra. Reprezentuje on zatem całość tabeli. Ten widok domyślny uzyskuje się za pomocą obiektu [DataTable.DefaultView]. Widok można sortować za pomocą jego właściwości [sort], do której jeszcze wrócimy.
Jeśli uzyskanie [DataSet] z klasy [produits] przebiega pomyślnie, wyświetlany jest widok [résultats], w przeciwnym razie wyświetlany jest widok [erreurs]. Wyświetlenie widoku [résultats] odbywa się za pomocą procedury [afficheRésultats], do której przekazywane są dwa parametry:
- tekst, który ma zostać umieszczony w etykiecie [lblSelect]
- [DataView], który ma zostać powiązany z [DataGrid1]
Ten przykład pokazuje nam dużą elastyczność komponentu [DataGrid]. Potrafi on rozpoznać strukturę komponentu [DataView], z którym jest powiązany, i dostosować się do niej. Wreszcie procedura [btnExécuter_Click] zapisuje właśnie uzyskany obiekt [DataView] w sesji użytkownika, aby móc z niego skorzystać, gdy użytkownik zażąda kolejnych stron z tego samego obiektu [DataView].
Procedura [DataGrid1_PageIndexChanged] jest uruchamiana, gdy użytkownik korzysta z linków [Suivant] i [Précédent] zawartych w [DataGrid]. Otrzymuje ona dwa parametry:
Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.) Handles DataGrid1.PageIndexChanged
obiekt będący źródłem zdarzenia – w tym przypadku jeden z linków [Suivant] lub [Précédent] | |
informacje o zdarzeniu. Atrybut e.NewPageIndex to numer strony, która ma zostać wyświetlona w odpowiedzi na żądanie klienta |
Pełny kod menedżera zdarzeń wygląda następująco:
Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
' zmiana strony
With DataGrid1
.CurrentPageIndex = e.NewPageIndex
.DataSource = CType(Session("données"), DataView)
.DataBind()
End With
End Sub
Komponent [DataGrid] posiada atrybut [CurrentPageIndex], który wskazuje numer strony, którą wyświetla lub ma wyświetlić. Atrybutowi temu nadaje się wartość [NewPageIndex] z parametru [e]. Następnie [DataGrid] jest powiązany z [DataView], który został zapisany w sesji przez procedurę [btnExécuter_Click].
Można się zastanawiać, czy parametr [DataGrid] potrzebuje atrybutu [EnableViewState=true], skoro jego zawartość jest obliczana przez kod przy każdym nowym wywołaniu strony. Można by pomyśleć, że nie. Jednakże, jeśli [DataGrid] posiada atrybut [EnableViewState=false], okazuje się, że zdarzenie [DataGrid1.PageIndexChanged] nigdy nie jest wyzwalane. Dlatego pozostawiono [EnableViewState=true]. Wiadomo, że powoduje to umieszczenie zawartości [DataGrid] w ukrytym polu [__VIEWSTATE] na stronie. Może to spowodować znaczne spowolnienie działania strony, jeśli [DataGrid] jest duży. Jeśli jest to uciążliwe, można samodzielnie zarządzać paginacją, nie korzystając z automatycznej paginacji [DataGrid].
Procedura [DataGrid1_SortCommand] jest uruchamiana, gdy użytkownik kliknie nagłówek jednej z kolumn wyświetlanych przez [DataGrid], aby posortować dane według tej kolumny. Otrzymuje ona dwa parametry:
Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
obiekt będący źródłem zdarzenia – w tym przypadku jeden z linków [Suivant] lub [Précédent] | |
informacje o zdarzeniu. Atrybut [e.SortExpression] to nazwa kolumny, na którą kliknięto w celu sortowania |
Pełny kod obsługi zdarzenia wygląda następująco:
Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
' sortowanie widoku danych
Dim données As DataView = CType(Session("données"), DataView)
données.Sort = e.SortExpression + " " + CType(IIf(rdCroissant.Checked, "asc", "desc"), String)
' wyświetlanie
With DataGrid1
.CurrentPageIndex = 0
.DataSource = données
.DataBind()
End With
End Sub
Pobieramy komponent [DataView] wyświetlany przez komponent [DataGrid] w bieżącej sesji. Umieściła go tam procedura [btnExécuter_Click]. Komponent [DataView] posiada właściwość [Sort], której przypisuje się wyrażenie sortujące. Wyrażenie to ma składnię [select ... order by expr1, expr2, ...], gdzie po każdym [expri] może następować słowo kluczowe [asc] dla sortowania rosnącego lub [desc] dla sortowania malejącego. Wyrażenie [order by] użyte w tym przypadku to [order by colonne asc/desc]. Właściwość [e.SortExpression] podaje nazwę kolumny [DataGrid], która została kliknięta w celu sortowania. Ciąg znaków [asc/desc] jest ustalany na podstawie wartości przycisków opcji z grupy [rdTri]. Po ustaleniu wyrażenia sortowania dla [DataView], [DataGrid] jest z nim powiązany. Umieszczamy [DataGrid] na jego pierwszej stronie.
8.7. Komponent DataList i powiązanie danych
Skupimy się teraz na komponencie [DataList]. Oferuje on więcej możliwości formatowania niż [DataGrid], ale jest mniej elastyczny. Nie potrafi bowiem automatycznie dostosować się do źródła danych, z którym jest powiązany. Jeśli takie dostosowanie jest pożądane, należy je wprowadzić za pomocą kodu. Jeśli struktura źródła danych jest znana z góry, komponent ten oferuje możliwości formatowania, które mogą sprawić, że będzie on preferowany w stosunku do komponentu [DataGrid].
8.7.1. Zastosowanie
Aby zilustrować wykorzystanie komponentu [DataList], tworzymy aplikację MVC analogiczną do poprzedniej:
![]() |
Trzy widoki zostaną wbudowane w kod prezentacyjny kontrolera [main.aspx] w postaci kontenerów. Dlatego też ta aplikacja posiada jedną stronę [main.aspx].
8.7.2. Klasy biznesowe
Klasa [produits] jest taka sama jak poprzednio.
8.7.3. Widoki
Gdy użytkownik wysyła pierwsze żądanie do aplikacji, otrzymuje następujący widok [résultats1]:
![]() |
nr | nazwa | typ | właściwości | rola |
1 | RadioButton | EnableViewState=false | umożliwia wybór jednego z dwóch stylów [DataList] | |
2 | Przycisk | EnableViewState=false | przycisk [submit] | |
3 | DataList | EnableViewState=true | pole wyświetlania listy danych |
Jeśli użytkownik wybierze styl nr 2, otrzyma następujący widok [résultats2]:
![]() |
nr | nazwa | typ | właściwości | rola |
1 | DataList | EnableViewState=true | pole wyświetlania listy danych |
Widok [erreurs] zgłasza problem z dostępem do źródła danych:
![]() |
nr | nazwa | typ | właściwości | rola |
1 | zmienna | kod HTML niezbędny do wyświetlania błędów |
Trzy widoki aplikacji to trzy różne kontenery (panele) w ramach tej samej strony [main.aspx]. Jej kod prezentacji wygląda następująco:
<%@ Page src="main.aspx.vb" inherits="main" autoeventwireup="false" Language="vb" %>
<HTML>
<HEAD>
</HEAD>
<body>
<P>Liaison de données avec un DataList</P>
<HR width="100%" SIZE="1">
<form runat="server" ID="Form1">
<asp:Panel Runat="server" ID="bandeau">
<P>Choisissez votre style :
<asp:RadioButton id="RadioButton1" runat="server" EnableViewState="False" Text="1" GroupName="rdstyle"
Checked="True"></asp:RadioButton>
<asp:RadioButton id="RadioButton2" runat="server" EnableViewState="False" Text="2" GroupName="rdstyle"></asp:RadioButton>
<asp:Button id="btnChanger" runat="server" EnableViewState="False" Text="Changer"></asp:Button></P>
<HR width="100%" SIZE="1">
</asp:Panel>
<asp:Panel id="vueRésultats1" runat="server">
<P>
<asp:DataList id="DataList1" runat="server" BorderColor="Tan" BorderWidth="1px" BackColor="LightGoldenrodYellow"
CellPadding="2" RepeatDirection="Horizontal" RepeatColumns="4" ForeColor="Black">
<SelectedItemStyle ForeColor="GhostWhite" BackColor="DarkSlateBlue"></SelectedItemStyle>
<HeaderTemplate>
Contenu de la table [liste] de la base [produits]
</HeaderTemplate>
<AlternatingItemStyle BackColor="PaleGoldenrod"></AlternatingItemStyle>
<ItemTemplate>
nom :
<%# Container.DataItem("nazwa") %>
<br>
prix :
<%# databinder.eval(Container.DataItem,"cena","{0:C}") %>
<br>
</ItemTemplate>
<FooterStyle BackColor="Tan"></FooterStyle>
<HeaderStyle Font-Bold="True" BackColor="Tan"></HeaderStyle>
</asp:DataList></P>
</asp:Panel>
<asp:Panel id="vueRésultats2" runat="server">
<P>
<asp:DataList id="DataList2" runat="server">
<HeaderTemplate>
Contenu de la table [liste] de la base [produits]
<HR width="100%" SIZE="1">
</HeaderTemplate>
<AlternatingItemStyle BackColor="Teal"></AlternatingItemStyle>
<SeparatorStyle BackColor="LightSkyBlue"></SeparatorStyle>
<ItemStyle BackColor="#C0C000"></ItemStyle>
<ItemTemplate>
nom :
<%# Container.DataItem("nazwa") %>
, prix :
<%# databinder.eval(Container.DataItem,"cena","{0:C}") %>
<BR>
</ItemTemplate>
<SeparatorTemplate>
<HR width="100%" SIZE="1">
</SeparatorTemplate>
<HeaderStyle BackColor="#C0C0FF"></HeaderStyle>
</asp:DataList></P>
</asp:Panel>
<asp:Panel id="vueErreurs" runat="server">
<P>Les erreurs suivantes se sont produites :</P>
<%= erreursHTML %>
</asp:Panel>
</form>
</body>
</HTML>
8.7.4. Konfiguracja komponentów [DataList]
Przyjrzyjmy się różnym atrybutom komponentu [DataList]. Jest ich bardzo wiele, a my przedstawiamy tylko niewielką część. W ramach komponentu [DataList] można zdefiniować do siedmiu szablonów wyświetlania:
szablon nagłówka [DataList] | |
szablon wierszy wyświetlających elementy powiązanej listy danych. Tylko ten szablon jest obowiązkowy. | |
Aby wizualnie odróżnić kolejne wyświetlane elementy, można użyć dwóch szablonów: ItemTemplate dla elementu n, AlternatingItemTemplate dla elementu n+1 | |
szablon elementu zaznaczonego w [DataList] | |
szablon separatora między dwoma elementami w [DataList] | |
[DataList] umożliwia modyfikację wyświetlanych wartości. [EditItemTemplate] to szablon elementu z [DataList], dla którego aktywny jest tryb „edycji” | |
szablon stopki elementu [DataList] |
Komponent [DataList1] został utworzony na podstawie [WebMatrix]. W jego oknie właściwości wybrano odnośnik [Mise en forme automatique]:
![]() | 1234567 ![]() |
W powyższym przykładzie schemat [Couleur 5] wygeneruje plik [DataList] zawierający style dla następujących szablonów: HeaderTemplate (1), ItemTemplate (2, 6), AlternatingTemplate (3, 5), SelectedItemTemplate (4), FooterTemplate (7). Wygenerowany kod wygląda następująco:
<asp:DataList id="DataList1" runat="server" BorderColor="Tan" BorderWidth="1px" BackColor="LightGoldenrodYellow"
CellPadding="2" ForeColor="Black">
<SelectedItemStyle ForeColor="GhostWhite" BackColor="DarkSlateBlue"></SelectedItemStyle>
<AlternatingItemStyle BackColor="PaleGoldenrod"></AlternatingItemStyle>
<FooterStyle BackColor="Tan"></FooterStyle>
<HeaderStyle Font-Bold="True" BackColor="Tan"></HeaderStyle>
</asp:DataList></P>
Nie zdefiniowano żadnego szablonu. Musimy to zrobić sami. Definiujemy następujące szablony:
| |
|
Należy pamiętać, że model [ItemTemplate] służy do wyświetlania elementów źródła danych powiązanego z modelem [DataList]. Źródło danych to zbiór wierszy danych, z których każdy zawiera jedną lub więcej wartości. Bieżący wiersz źródła danych jest reprezentowany przez obiekt [Container.DataItem]. Taki wiersz składa się z kolumn. [Container.DataItem("col1")] to wartość kolumny „col1” bieżącego wiersza. Aby uwzględnić tę wartość w kodzie prezentacji, należy wpisać <%# Container.DataItem("col") %>. Czasami chcemy zaprezentować element bieżącego wiersza w specjalnym formacie. W tym przypadku chcemy wyświetlić kolumnę „prix” bieżącego wiersza w euro. Wykorzystujemy wówczas funkcję [DataBinder.Eval], która przyjmuje trzy parametry:
- bieżący wiersz [Container.DataItem]
- nazwę kolumny, którą należy sformatować
- ciąg formatujący w postaci {0:format}, gdzie [format] jest jednym z formatów akceptowanych przez metodę [string.format].
Zatem kod <%# DataBinder.Eval(Container.DataItem,"cena",{0:C}) %> spowoduje wyświetlenie kolumny [prix] z bieżącego wiersza w formacie walutowym (format C = Currency).
W rezultacie otrzymamy [DataList], który będzie wyglądał następująco:

W powyższym przykładzie dane zostały rozmieszczone po cztery w każdym wierszu. Uzyskano to dzięki następującym atrybutom [DataList]:
Poziomo | |
pożądana liczba kolumn |
Ostatecznie kod komponentu [DataList1] jest tym samym, który został przedstawiony w kodzie prezentacyjnym nieco wyżej. Analizę kodu prezentacyjnego komponentu [DataList2] pozostawiamy czytelnikowi. Podobnie jak w przypadku komponentu [DataGrid], większość właściwości komponentu [DataList] można ustawić za pomocą kreatora komponentu [WebMatrix]. W tym celu należy skorzystać z linku [Générateur de propriétés] w oknie właściwości komponentu [DataList]:
![]() | ![]() |
8.7.5. Kontrolki
Kontroler [global.asax, global.asax.vb] wygląda następująco:
[global.asax]
[global.asax.vb]
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Data
Imports System.Collections
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' tworzymy obiekt „produkty”
Try
Dim données As DataSet = New produits(ConfigurationSettings.AppSettings("OLEDBStringConnection")).getDataSet("select * from LISTE")
' umieszczamy obiekt w aplikacji
Application("données") = données
' 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
End Class
Po uruchomieniu aplikacji (Application_Start) tworzymy obiekt [dataset] na podstawie klasy biznesowej [produits] i umieszczamy go w aplikacji, aby był dostępny dla wszystkich żądań wszystkich klientów. Jeśli podczas tego tworzenia wystąpi wyjątek, zostanie on odnotowany w aplikacji. Procedura [Application_Start] zostanie wykonana tylko raz. Następnie kontroler [global.asax] nie będzie już interweniował. Zadanie to przejmie wówczas kontroler [main.aspx.vb]:
Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Data
Imports st.istia.univangers.fr
Imports System
Imports System.Xml
Public Class main
Inherits System.Web.UI.Page
' komponenty strony
Protected WithEvents vueErreurs As System.Web.UI.WebControls.Panel
Protected WithEvents DataList1 As System.Web.UI.WebControls.DataList
Protected WithEvents DataList2 As System.Web.UI.WebControls.DataList
Protected WithEvents vueRésultats1 As System.Web.UI.WebControls.Panel
Protected WithEvents vueRésultats2 As System.Web.UI.WebControls.Panel
Protected WithEvents RadioButton1 As System.Web.UI.WebControls.RadioButton
Protected WithEvents RadioButton2 As System.Web.UI.WebControls.RadioButton
Protected WithEvents btnChanger As System.Web.UI.WebControls.Button
Protected WithEvents bandeau As System.Web.UI.WebControls.Panel
' dane strony
Protected erreursHTML As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' sprawdzamy, czy w aplikacji wystąpił błąd
If CType(Application("erreur"), Boolean) Then
' aplikacja nie zainicjowała się poprawnie
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
afficheErreurs(erreurs, False)
Exit Sub
End If
'pierwsze żądanie
If Not IsPostBack Then
' inicjowanie elementów sterujących
With DataList1
.DataSource = CType(Application("données"), DataSet)
.DataBind()
End With
With DataList2
.DataSource = CType(Application("données"), DataSet)
.DataBind()
End With
' wyświetla się pusty formularz
afficheRésultats(True, False)
End If
End Sub
Private Sub afficheErreurs(ByVal erreurs As ArrayList, ByVal afficheLien As Boolean)
' wyświetla widok błędów
erreursHTML = ""
For i As Integer = 0 To erreurs.Count - 1
erreursHTML += "<li>" + erreurs(i).ToString + "</li>" + ControlChars.CrLf
Next
' wyświetla się widok [erreurs]
vueErreurs.Visible = True
vueRésultats1.Visible = False
vueRésultats2.Visible = False
bandeau.Visible = False
End Sub
Private Sub afficheRésultats(ByVal visible1 As Boolean, ByVal visible2 As Boolean)
' wyświetla widok [résultats]
vueRésultats1.Visible = visible1
vueRésultats2.Visible = visible2
vueErreurs.Visible = False
End Sub
Private Sub btnChanger_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnChanger.Click
' zmiana stylu
afficheRésultats(RadioButton1.Checked, RadioButton2.Checked)
End Sub
End Class
8.8. Komponent Repeater i połączenie danych
Komponent [Repeater] umożliwia powtórzenie kodu HTML dla każdego elementu listy danych. Załóżmy, że chcemy wyświetlić listę błędów w następującej postaci:

Napotkaliśmy już ten problem i rozwiązaliśmy go, umieszczając w kodzie prezentacji zmienną w postaci <ul><% =erreursHTML %></ul>, przy czym wartość erreursHTML jest obliczana przez kontroler. Wartość ta zawiera kod HTML, odpowiadający liście. Wadą tego rozwiązania jest to, że jeśli chcemy zmodyfikować wygląd tej listy HTML, musimy przejść do części kontrolera, co jest sprzeczne z zasadą separacji kontrolera od prezentacji. Rozwiązaniem jest komponent [Repeater]. Podobnie jak w przypadku [DataList], można zdefiniować szablony <HeaderTemplate> dla nagłówka, <ItemTemplate> dla bieżącego elementu listy danych oraz <FooterTemplate> dla końca danych. W tym przypadku moglibyśmy mieć następującą definicję dla komponentu [Repeater]:
<asp:Repeater id="Repeater1" runat="server" EnableViewState="False">
<HeaderTemplate>
Les erreurs suivantes se sont produites :
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<%# Container.DataItem %>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
Przypominamy, że [Container.DataItem] reprezentuje wiersz danych, jeśli źródło danych ma kilka kolumn. Reprezentuje pojedynczą wartość, jeśli źródło ma tylko jedną kolumnę. Tak właśnie będzie w tym przypadku. Na przykład stworzymy następującą aplikację:

Kod wyświetlania strony wygląda następująco:
<html>
<head>
</head>
<body>
<form runat="server">
<p>
Liaison de données avec un composant [Repeater]
</p>
<hr />
<p>
<asp:Repeater id="Repeater1" runat="server" EnableViewState="False">
<ItemTemplate>
<li>
<%# Container.DataItem %>
</li>
</ItemTemplate>
<HeaderTemplate>
Les erreurs suivantes se sont produites :
<ul>
</HeaderTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</p>
</form>
</body>
</html>
Kod kontrolny brzmi następująco:
<%@ Page Language="VB" %>
<script runat="server">
' procedura wykonywana podczas ładowania strony
Sub page_Load(sender As Object, e As EventArgs)
if not IsPostBack then
' tworzy się źródło danych
with Repeater1
.DataSource=createDataSource
.DataBind
end with
end if
End Sub
function createDataSource as ArrayList
' tworzy listę tablicową
dim erreurs as new ArrayList
dim i as integer
for i=0 to 5
erreurs.add("erreur-"+i.ToString)
next
return erreurs
end function
</script>
<html>
...
</html>
W odpowiedzi na pierwsze żądanie klienta przypisujemy obiekt [ArrayList] do komponentu [Repeater], który ma reprezentować listę błędów.
8.9. Application
W tym miejscu powracamy do aplikacji, która została już omówiona w kontekście komponentów serwerowych. Aplikacja ta umożliwia przeprowadzanie symulacji obliczeń podatkowych. Opiera się ona na klasie [impot], której nie będziemy tutaj ponownie omawiać. Klasa ta potrzebuje danych, które pobiera ze źródła danych OLEDB. W tym przykładzie będzie to źródło ACCESS. W tej nowej wersji wprowadzamy następujące zmiany:
- wykorzystanie komponentów walidacyjnych do sprawdzania poprawności danych
- wykorzystanie komponentów serwerowych powiązanych ze źródłami danych do wyświetlania wyników
8.9.1. Struktura aplikacji MVC
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].
8.9.2. Widoki aplikacji
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:

Błędy te są mu sygnalizowane w widoku [formulaire]:

Użytkownik poprawia wówczas swoje błędy. Może przeprowadzić nowe symulacje:

W ten sposób otrzymuje widok [simulations] z kolejną symulacją:

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

8.9.2.1. Kod prezentacji
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]
Poniżej przedstawiamy szczegółowy opis elementów składowych tych trzech kontenerów. Kontener [panelform] ma następujący wygląd:
![]() |
nr | nazwa | typ | właściwości | rola |
Panel | widok formularza | |||
RadioButton | GroupName=rdmarie | przyciski opcji | ||
CustomValidator | ErrorMessage=Nie podałeś swojego stanu cywilnego EnableClientScript=false | sprawdź, czy program kliencki rzeczywiście przesłał oczekiwany stan cywilny | ||
TextBox | liczba dzieci | |||
RequiredFieldValidator | ErrorMessage=Podaj liczbę dzieci ControlToValidate=txtEnfants | sprawdza, czy pole [txtEnfants] nie jest puste | ||
RangeValidator | ErrorMessage=Wpisz liczbę z przedziału od 1 do 30 ControlToValidate=txtEnfants | sprawdza, czy wartość pola [txtEnfants] mieści się w przedziale [1,30] | ||
TextBox | EnableViewState=true | roczne wynagrodzenie | ||
RequiredFieldValidator | ControlToValidate=txtSalaire ErrorMessage=Podaj wysokość swojego wynagrodzenia | sprawdź, czy pole [txtSalaire] nie jest puste | ||
RegularExpressionValidator | ControlToValidate=txtSalaire ErrorMessage=Nieprawidłowe wynagrodzenie RegularExpression=\s*\d+\s* | sprawdza, czy pole [txtSalaire] zawiera ciąg cyfr | ||
Przycisk | CausesValidation=true | przycisk [submit] w formularzu – uruchamia obliczenie podatku | ||
Przycisk | CausesValidation=false | przycisk [submit] w formularzu – czyści formularz |
Może budzić zdziwienie kontrola [cvMarié], której zadaniem jest sprawdzenie, czy użytkownik zaznaczył jeden z dwóch przycisków opcji. Nie może być bowiem inaczej, jeśli korzysta on z formularza przesłanego przez serwer. Ponieważ nie ma co do tego pewności, konieczne jest sprawdzenie wszystkich przesłanych parametrów. Warto również zwrócić uwagę na atrybut [CausesValidation=false] przycisku [btnEffacer]. Gdy użytkownik korzysta z tego przycisku, nie należy sprawdzać przesłanych danych, ponieważ zostaną one zignorowane.
Wszystkie komponenty kontenera mają właściwość [EnableViewState=false] z wyjątkiem [panelForm, txtEnfants, txtSalaire, rdOui, rdNon]. Kontener [panelerreurs] ma następujący wygląd:
![]() |
nr | nazwa | typ | właściwości | rola |
Panel | EnableViewState=false | widok błędów | ||
Repeater | EnableViewState=false | wyświetla listę błędów |
Komponent [rptErreurs] jest zdefiniowany w następujący sposób:
<asp:Repeater id="rptErreurs" runat="server" EnableViewState="False">
<ItemTemplate>
<li>
<%# Container.Dataitem%>
</li>
</ItemTemplate>
<HeaderTemplate>
Les erreurs suivantes se sont produites
<ul>
</HeaderTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
Lista danych powiązana z komponentem [rptErreurs] będzie obiektem [ArrayList] zawierającym listę komunikatów o błędach. Zatem powyżej <%# Container.Dataitem%> oznacza bieżący komunikat o błędzie. Kontener [panelsimulations] ma następujący wygląd:
![]() |
nr | nazwa | typ | właściwości | rola |
Panel | EnableViewState=false | widok symulacji | ||
LinkButton | EnableViewState=false | link do formularza | ||
DataGrid | EnableViewState=false | odpowiedzialny za wyświetlanie symulacji umieszczonych w obiekcie [DataTable] |
Komponent [dgSimulations] jest skonfigurowany w następujący sposób w ramach [Webmatrix]. W oknie właściwości [dgSimulations] wybieramy odnośnik [Mise en forme automatique] i wybieramy schemat [Couleur 3]:
![]() | ![]() |
Następnie, nadal w oknie właściwości [dlgSimulations], wybieramy link [Générateur de propriétés]. Wyświetlamy nagłówki kolumn:

Wybieramy opcję [Colonnes], aby zdefiniować cztery kolumny w [DataGrid]:

Odznaczamy pozycję [Créer des colonnes automatiquement ...]. Sami zdefiniujemy cztery kolumny obiektu [DataGrid]. Obiekt ten zostanie powiązany z obiektem [DataTable] zawierającym cztery kolumny o nazwach odpowiednio „żonaty”, „dzieci”, „wynagrodzenie” i „podatek”. Aby utworzyć kolumnę w obiekcie [DataGrid], wybieramy opcję [Colonnes connexe] i korzystamy z przycisku tworzenia, jak pokazano powyżej. Kolumna zostaje utworzona i możemy zdefiniować jej właściwości. Pierwsza kolumna tabeli symulacji będzie zawierała kolumnę „żonaty” z obiektu [DataTable], który zostanie powiązany z [DataGrid]. Ta pierwsza kolumna obiektu [DataGrid] jest zdefiniowana w kreatorze w następujący sposób:

nazwa kolumny, w tym przypadku „Żonaty” | |
nazwa kolumny źródła danych, która będzie wyświetlana przez tę kolumnę w pliku [DataGrid]. W tym przypadku jest to kolumna „żonaty” z pliku [DataTable]. |
Druga kolumna jest zdefiniowana w następujący sposób:
Dzieci | |
dzieci |
Trzecia kolumna jest zdefiniowana w następujący sposób:
Roczne wynagrodzenie | |
wynagrodzenie | |
{0:C} – format walutowy umożliwiający wyświetlenie znaku euro |
Czwarta kolumna jest zdefiniowana w następujący sposób:
Kwota podatku | |
podatek | |
{0:C} – format walutowy w celu wyświetlenia znaku euro |
Umieszczamy kod prezentacji i kod kontrolny w dwóch oddzielnych plikach. Pierwszy znajdzie się w pliku [main.aspx], a drugi w pliku [main.aspx.vb]. Kod z pliku [main.aspx] jest następujący:
<%@ page src="main.aspx.vb" inherits="main" AutoEventWireUp="false" %>
<HTML>
<HEAD>
<title>Calculer votre impôt</title>
</HEAD>
<body>
<P>Calculer 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
<asp:CustomValidator id="cvMarie" runat="server" ErrorMessage="Vous n'avez pas indiqué votre état marital"
Display="Dynamic" EnableClientScript="False" Visible="False" EnableViewState="False"></asp:CustomValidator></TD>
</TR>
<TR>
<TD>Nombre d'enfants</TD>
<TD>
<asp:TextBox id="txtEnfants" runat="server" Columns="3" MaxLength="3"></asp:TextBox>
<asp:RequiredFieldValidator id="rfvEnfants" runat="server" ErrorMessage="Indiquez le nombre d'enfants" Display="Dynamic"
ControlToValidate="txtEnfants" EnableViewState="False"></asp:RequiredFieldValidator>
<asp:RangeValidator id="rvEnfants" runat="server" ErrorMessage="Tapez un nombre entre 1 et 30" Display="Dynamic"
ControlToValidate="txtEnfants" Type="Integer" MaximumValue="30" MinimumValue="0" EnableViewState="False"></asp:RangeValidator></TD>
</TR>
<TR>
<TD>Salaire annuel (euro)</TD>
<TD>
<asp:TextBox id="txtSalaire" runat="server" Columns="10" MaxLength="10"></asp:TextBox>
<asp:RequiredFieldValidator id="rfvSalaire" runat="server" ErrorMessage="Indiquez le montant de votre salaire"
Display="Dynamic" ControlToValidate="txtSalaire" EnableViewState="False"></asp:RequiredFieldValidator>
<asp:RegularExpressionValidator id="revSalaire" runat="server" ErrorMessage="Salaire invalide" Display="Dynamic"
ControlToValidate="txtSalaire" ValidationExpression="\s*\d+\s*" EnableViewState="False"></asp:RegularExpressionValidator></TD>
</TR>
</TABLE>
<P>
<asp:Button id="btnCalculer" runat="server" Text="Calculer"></asp:Button>
<asp:Button id="btnEffacer" runat="server" Text="Effacer" CausesValidation="False"></asp:Button></P>
</asp:panel>
<asp:panel id="panelerreurs" runat="server" EnableViewState="False">
<asp:Repeater id="rptErreurs" runat="server" EnableViewState="False">
<ItemTemplate>
<li>
<%# Container.Dataitem%>
</li>
</ItemTemplate>
<HeaderTemplate>
Les erreurs suivantes se sont produites
<ul>
</HeaderTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</asp:panel>
<asp:panel id="panelsimulations" runat="server" EnableViewState="False">
<P>
<asp:DataGrid id="dgSimulations" runat="server" BorderColor="#DEBA84" BorderStyle="None" CellSpacing="2"
BorderWidth="1px" BackColor="#DEBA84" CellPadding="3" AutoGenerateColumns="False" EnableViewState="False">
<SelectedItemStyle Font-Bold="True" ForeColor="White" BackColor="#738A9C"></SelectedItemStyle>
<ItemStyle ForeColor="#8C4510" BackColor="#FFF7E7"></ItemStyle>
<HeaderStyle Font-Bold="True" ForeColor="White" BackColor="#A55129"></HeaderStyle>
<FooterStyle ForeColor="#8C4510" BackColor="#F7DFB5"></FooterStyle>
<Columns>
<asp:BoundColumn DataField="marié" HeaderText="Marié"></asp:BoundColumn>
<asp:BoundColumn DataField="enfants" HeaderText="Enfants"></asp:BoundColumn>
<asp:BoundColumn DataField="salaire" HeaderText="Salaire annuel" DataFormatString="{0:C}"></asp:BoundColumn>
<asp:BoundColumn DataField="impôt" HeaderText="Montant de l'impôt" DataFormatString="{0:C}"></asp:BoundColumn>
</Columns>
<PagerStyle HorizontalAlign="Center" ForeColor="#8C4510" Mode="NumericPages"></PagerStyle>
</asp:DataGrid></P>
<P>
<asp:LinkButton id="lnkForm2" runat="server" EnableViewState="False">Retour au formulaire</asp:LinkButton></P>
</asp:panel>
</FORM>
</body>
</HTML>
8.9.3. Kod kontrolny 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
Imports System.Data
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' tworzymy obiekt typu „impot”
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
Dim simulations As New DataTable("simulations")
simulations.Columns.Add("marié", Type.GetType("System.String"))
simulations.Columns.Add("enfants", Type.GetType("System.String"))
simulations.Columns.Add("salaire", Type.GetType("System.Int32"))
simulations.Columns.Add("impôt", Type.GetType("System.Int32"))
Session.Item("simulations") = simulations
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, w którym rozdziale 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 [DataTable] powiązanym z kluczem sesji „simulations”. W momencie rozpoczęcia sesji klucz ten jest powiązany z pustym obiektem [DataTable], którego struktura została jednak zdefiniowana. 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\devel\aspnet\poly\webforms2\vs\impots6\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
Imports System.Web.UI.Control
Imports System.Data
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 lnkForm2 As System.Web.UI.WebControls.LinkButton
Protected WithEvents panelerreurs As System.Web.UI.WebControls.Panel
Protected WithEvents rfvEnfants As System.Web.UI.WebControls.RequiredFieldValidator
Protected WithEvents rvEnfants As System.Web.UI.WebControls.RangeValidator
Protected WithEvents rfvSalaire As System.Web.UI.WebControls.RequiredFieldValidator
Protected WithEvents rptErreurs As System.Web.UI.WebControls.Repeater
Protected WithEvents dgSimulations As System.Web.UI.WebControls.DataGrid
Protected WithEvents revSalaire As System.Web.UI.WebControls.RegularExpressionValidator
Protected WithEvents cvMarie As System.Web.UI.WebControls.CustomValidator
Protected WithEvents panelsimulations As System.Web.UI.WebControls.Panel
' zmienne lokalne
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
End Sub
Private Sub afficheErreurs(ByRef erreurs As ArrayList)
...
End Sub
Private Sub afficheFormulaire()
...
End Sub
Private Sub afficheSimulations(ByRef simulations As DataTable, 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 Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
...
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
Private Sub cvMarie_ServerValidate(ByVal source As System.Object, ByVal args As System.Web.UI.WebControls.ServerValidateEventArgs)
...
End Sub
End Class
Pierwszym zdarzeniem przetwarzanym przez kod jest [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
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] wygląda następująco:
Private Sub afficheErreurs(ByRef erreurs As ArrayList)
' tworzy listę błędów
With rptErreurs
.DataSource = erreurs
.DataBind()
End With
' wyświetlanie kontenerów
panelerreurs.Visible = True
panelform.Visible = False
panelsimulations.Visible = False
Exit Sub
End Sub
Procedura otrzymuje jako parametr listę komunikatów o błędach w [erreurs] typu [ArrayList]. Wystarczy powiązać to źródło danych z komponentem [rptErreurs] odpowiedzialnym za jego wyświetlanie.
Procedura wyświetlająca widok [formulaire] wygląda następująco:
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; w przypadku błędów wyświetlany jest komunikat
If Not Page.IsValid Then
afficheFormulaire()
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))
' dodaje się wynik do istniejących symulacji
Dim simulations As DataTable = CType(Session.Item("simulations"), DataTable)
Dim simulation As DataRow = simulations.NewRow
simulation("marié") = CType(IIf(rdOui.Checked, "oui", "non"), String)
simulation("enfants") = txtEnfants.Text.Trim
simulation("salaire") = CType(txtSalaire.Text.Trim, Long)
simulation("impôt") = impot
simulations.Rows.Add(simulation)
' symulacje są umieszczane w sesji
Session.Item("simulations") = simulations
' wyświetla się strona symulacji
afficheSimulations(simulations, "Retour au formulaire")
End Sub
Procedura rozpoczyna się od sprawdzenia poprawności strony. Należy tu przypomnieć, że w momencie uruchomienia procedury [btnCalculer_Click] kontrole poprawności zostały już przeprowadzone, a atrybut [IsValid] strony został ustawiony. Jeśli strona jest nieprawidłowa, wyświetlany jest ponownie widok [formulaire], a procedura zostaje zakończona. Komponenty walidacyjne widoku [formulaire], których atrybuty [IsValid]–[false] są nieprawidłowe, wyświetlą swój atrybut [ErrorMessage]. Jeśli strona jest poprawna, kwota podatku jest obliczana na podstawie 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.
Na koniec widok [simulations] jest wyświetlany przez następującą procedurę [afficheSimulations]:
Private Sub afficheSimulations(ByRef simulations As DataTable, ByRef lien As String)
' powiązanie tabeli danych ze źródłem symulacji
With dgSimulations
.DataSource = simulations
.DataBind()
End With
' powiązanie
lnkForm2.Text = lien
' widoki
panelsimulations.Visible = True
panelerreurs.Visible = False
panelform.Visible = False
End Sub
Procedura ma dwa parametry:
- listę symulacji w [simulations] typu [DataTable]
- tekst linku w [lien]
Źródło danych [simulations] jest powiązane z komponentem odpowiedzialnym za jego wyświetlanie. 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 tego widoku otrzymają wartość, która będzie albo wartością zadaną dla nich, albo ich poprzednią wartością. Ponieważ w tym przypadku widok klienta POST 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.
8.9.4. 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 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>,/impots6). W przeglądarce wpisuje się adres URL [http://impots6/main.aspx]:

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

8.9.5. Wniosek
Mamy aplikację MVC, która wykorzystuje wyłącznie komponenty serwerowe. Wykorzystanie tych komponentów pozwoliło na całkowite oddzielenie części prezentacyjnej aplikacji od jej części sterującej. Dotychczas nie było to możliwe, ponieważ kod prezentacyjny zawierał zmienne obliczane przez kod sterujący, a ich wartości zawierały kod HTML. Teraz tak już nie jest.






















