Skip to content

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:

Image

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:

Image

ASP.NET oferuje komponenty zwane komponentami walidacyjnymi, które umożliwiają sprawdzenie następujących przypadków:

Komponent
Rola
RequiredFieldValidator
sprawdza, czy pole nie jest puste
CompareValidator
porównuje dwie wartości ze sobą
RangeValidator
sprawdza, czy wartość mieści się w przedziale
RegularExpressionValidator
sprawdza, czy pole spełnia wyrażenie regularne
CustomValidator
umożliwia programiście zdefiniowanie własnych reguł walidacji – ten komponent mógłby zastąpić wszystkie pozostałe
ValidationSummary
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
txtNom
TextBox
EnableViewState=true
pole wprowadzania danych
2
RequiredFieldValidator1
RequiredFieldValidator
EnableViewState=false
EnableClientScript=true
ErrorMessage=Pole [nazwa] jest obowiązkowe
komponent walidacji
3
btnEnvoyer
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:

ControlToValidate
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.
EnableClientScript
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.
ErrorMessage
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]:

Image

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" />
                            &nbsp;
                        </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:

Image

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:

Image

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

Image

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

Image

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

Image

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

Image

nr
nazwa
typ
właściwości
rola
1
cmbChoix1
DropDownList
EnableViewState=true
lista rozwijana
2
cmbChoix2
DropDownList
EnableViewState=true
lista rozwijana
3
CompareValidator1
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
CompareValidator2
CompareValidator
EnableViewState=false
EnableClientScript=true
ErrorMessage=Nieprawidłowy wybór 2
ControlToCompare=?
Operator=Nie równe
Typ=ciąg znaków
komponent walidacji
5
btnEnvoyer
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:

ControlToValidate
pole, którego zawartość ma być sprawdzana przez komponent
EnableClientScript
wartość logiczna – wartość „prawda” oznacza, że zawartość poprzedniego pola musi być sprawdzana również po stronie klienta
ErrorMessage
komunikat o błędzie, który komponent ma wyświetlić w przypadku wykrycia błędu
ValeurToCompare
wartość, z którą należy porównać wartość pola [ControlToValidate]
ControlToCompare
komponent, z którego wartością należy porównać wartość pola [ControlToValidate]
Operator
operator porównania między tymi dwiema wartościami
Type
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:

Image

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

Image

nr
nazwa
typ
właściwości
rola
1
cmbDiplomes
DropDownList
EnableViewState=true
lista rozwijana
2
CompareValidator2
CompareValidator
EnableViewState=false
EnableClientScript=true
ErrorMessage=Nieprawidłowy dyplom
Operator=Nierówne
ValueToCompare=?
Type=String
komponent walidacyjny kontrolki [1]
3
txtAutreDiplome
TextBox
EnableViewState=false
pole wprowadzania danych
4
CustomValidator1
CustomValidator
EnableViewState=false
EnableClientScript=true
ErrorMessage=Nieprawidłowa precyzja dyplomu
ClientValidationFunction=chkAutreDiplome
komponent walidacji pola [3]
5
txtAnDiplome
TextBox
EnableViewState=false
pole wprowadzania danych
6
RangeValidator1
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]
7
RequiredFieldValidator2
RequiredFieldValidator
EnableViewState=false
EnableClientScript=true
ErrorMessage=Wymagany rok uzyskania dyplomu
ControlToValidate=txtAnDiplome
komponent sprawdzający poprawność pola [5]
8
btnEnvoyer
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:

ControlToValidate
pole, którego wartość musi być sprawdzana przez komponent
EnableClientScript
wartość logiczna – wartość „prawda” oznacza, że zawartość poprzedniego pola musi być sprawdzana również po stronie klienta
ErrorMessage
komunikat o błędzie, który komponent ma wyświetlić w przypadku wykrycia błędu
MinValue
minimalna wartość pola, które ma zostać sprawdzone
MaxValue
maksymalna wartość pola, które ma zostać sprawdzone
Type
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:

ControlToValidate
pole, którego wartość musi zostać sprawdzona przez komponent
EnableClientScript
wartość logiczna – wartość „true” oznacza, że zawartość poprzedniego pola musi być sprawdzana również po stronie klienta
ErrorMessage
komunikat o błędzie, który komponent ma wyświetlić w przypadku wykrycia błędu
ClientValidationFunction
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&#238;trise">Ma&#238;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]:

Image

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

Image

nr
nazwa
typ
właściwości
rola
1
txtMel
TextBox
EnableViewState=true
pole wprowadzania danych
2
RequiredFieldValidator3
RequiredFieldValidator
ControlToValidate=txtMel
Display=Dynamic
komponent walidacji kontrolki [1]
3
RegularExpressionValidator1
RegularExpressionValidator
ControlToValidate=txtMel
Display=Dynamic
komponent walidacji kontrolki [1]
4
btnEnvoyer
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:

ControlToValidate
pole, którego wartość musi być sprawdzana przez komponent
EnableClientScript
wartość logiczna – wartość „prawda” oznacza, że zawartość poprzedniego pola musi być sprawdzana również po stronie klienta
ErrorMessage
komunikat o błędzie, który komponent ma wyświetlić w przypadku wykrycia błędu
RegularExpression
wyrażenie regularne, z którym zostanie porównana zawartość pola [ControlToValidate]
Display
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:

Image

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

Image

nr
nazwa
typ
właściwości
rola
1
ValidationSummary1
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
lnkErreursToFormulaire
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]:

Image

1

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

Image

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

Image

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

Image

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

Image

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

DropDownList, ListBox
<option value="V">T</option>
CheckButtonList
<input type="checkbox" value="V">T
RadioButtonList
<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:

DataSource
źródło danych [Array], [ArrayList], [DataTable], [DataSet], [HashTable], ...
DataMember
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.
DataTextField
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]
DataValueField
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:

Array A
[ListControl].DataSource=A
pola [Text] i [Value] elementów [ListControl] będą miały jako wartości elementy z A
ArrayList AL
[ListControl].DataSource=AL
pola [Text] i [Value] elementów z [ListControl] będą miały wartości elementów z AL
DataTable DT
[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
DataSet DS
[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”
HashTable HT
[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
1
Tablica
2
ArrayList
3
DataTable
4
DataSet
5
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:

            .DataSource= myDataTable
            .DataValueField="id"
            .DataTextField="texte"

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:

            .DataSource= myDataSet
            .DataMember="table1"
            .DataValueField="id"
            .DataTextField="texte"

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:

            .DataSource=myHashTable
            .DataValueField="key"
            .DataTextField="value"

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

DataSource
źródło danych [Array], [ArrayList], [DataTable], [DataSet], ...
DataMember
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:

Image

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

Image

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:

Image

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

Image

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:

Image

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:

          with [DataGrid]
            .DataSource=[source de données]
            .DataBind
          end with

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
DataGrid1
DataGrid
EnableViewState=true
wyświetla źródło danych S
2
DataGrid2
DataGrid
EnableViewState=true
wyświetla to samo źródło S co [DataGrid1]
3
Button1
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:

Image

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:

Image

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
DataGrid1
DataGrid
EnableViewState=true
wyświetla źródło danych S
2
DataGrid2
DataGrid
EnableViewState=false
wyświetla to samo źródło S co [DataGrid1]
3
Button1
Przycisk
EnableViewState=false
przycisk [submit]
4
lblInfo1
lblInfo2
lblInfo3
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ź:

Image

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&nbsp;:
                <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:

            Session("source") = S

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:

            Session("source") = dtThèmes

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

        Session("numRequête3") = numRequête3

w celu zapisania nowej wartości obiektu [numRequête3] w sesji. Natomiast należy zapisać:

        Session("numRequête1") = numRequête1
        Session("numRequête2") = numRequête2

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:

Image

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
txtSelect
TextBox
EnableViewState=true
pole wprowadzania zapytania select
2
RequiredFieldValidator1
RequiredFieldValidator
EnableViewState=false
sprawdza, czy występuje 1
3
txtPages
TextBox
EnableViewState=true
pole wprowadzania danych – określa liczbę wierszy danych, które mają być wyświetlane na jednej stronie wyników
4
RequiredFieldValidator2
RequiredFieldValidator
EnableViewState=false
sprawdza, czy występuje cyfra 3
5
RangeValidator1
RangeValidator
EnableViewState=false
sprawdza, czy (3) mieści się w przedziale [1,30]
6
btnExécuter
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
lblSelect
Etykieta
EnableViewState=false
pole informacyjne
2
rdCroissant
rdDécroissant
RadioButton
EnableViewState=false
GroupName=rdTri
umożliwia wybór kierunku sortowania
3
DataGrid1
DataGrid
EnableViewState=true
AllowPaging=true
AllowSorting=true
tabela wyświetlająca wyniki zapytania SELECT
4
lnkRésultats
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.

Image

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

Image

Wyświetlany jest widok [erreurs]:

nr
nazwa
typ
właściwości
rola
1
erreursHTML
zmienna
 
kod HTML niezbędny do wyświetlania błędów
3
lnkForm2
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>&nbsp;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&#233;c&#233;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&#233;c&#233;dent" ...></PagerStyle>
                    </asp:DataGrid></P>
AllowPaging="true"
zezwala na paginację
PageSize="4"
cztery wiersze danych na stronę
NextPageText="Suivant"
Tekst linku prowadzącego do następnej strony źródła danych
PrevPageText="Précédent"
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]:

Image

Pojawi się następujący kreator:

Image

Wybieramy opcję [Pagination]:

Image

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]

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

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

IsValid
na „prawda”, jeśli sprawdzone dane są poprawne, na „fałsz” w przeciwnym razie
ErrorMessage
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
source
obiekt będący źródłem zdarzenia – w tym przypadku jeden z linków [Suivant] lub [Précédent]
e
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
source
obiekt będący źródłem zdarzenia – w tym przypadku jeden z linków [Suivant] lub [Précédent]
e
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
RadioButton1
RadioButton2
RadioButton
EnableViewState=false
umożliwia wybór jednego z dwóch stylów [DataList]
2
btnChanger
Przycisk
EnableViewState=false
przycisk [submit]
3
DataList1
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
DataList2
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
erreursHTML
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:

HeaderTemplate
szablon nagłówka [DataList]
ItemTemplate
szablon wierszy wyświetlających elementy powiązanej listy danych. Tylko ten szablon jest obowiązkowy.
AlternatingItemTemplate
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
SelectedItemTemplate
szablon elementu zaznaczonego w [DataList]
SeparatorTemplate
szablon separatora między dwoma elementami w [DataList]
EditItemTemplate
[DataList] umożliwia modyfikację wyświetlanych wartości. [EditItemTemplate] to szablon elementu z [DataList], dla którego aktywny jest tryb „edycji”
FooterTemplate
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:

HeaderTemplate

                        <HeaderTemplate>
                            Contenu de la table [liste] de la base [produits]
                        </HeaderTemplate>
ItemTemplate

                        <ItemTemplate>
                            nom :
                            <%# Container.DataItem("nazwa") %>
                            <br>
                            prix :
                            <%# DataBinder.Eval(Container.DataItem,"cena","{0:C}") %>
                            <br>
                        </ItemTemplate>

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:

Image

W powyższym przykładzie dane zostały rozmieszczone po cztery w każdym wierszu. Uzyskano to dzięki następującym atrybutom [DataList]:

RepeatDirection
Poziomo
RepeatColumns
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]

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

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

Image

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

Image

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:

Image

Użytkownik wypełnia formularz:

Image

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

Image

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

Image

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

Image

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

Image

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

Image

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

Image

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
0
panelform
Panel
 
widok formularza
1
rdOui
rdNon
RadioButton
GroupName=rdmarie
przyciski opcji
2
cvMarie
CustomValidator
ErrorMessage=Nie podałeś swojego stanu cywilnego
EnableClientScript=false
sprawdź, czy program kliencki rzeczywiście przesłał oczekiwany stan cywilny
3
txtEnfants
TextBox
 
liczba dzieci
4
rfvEnfants
RequiredFieldValidator
ErrorMessage=Podaj liczbę dzieci
ControlToValidate=txtEnfants
sprawdza, czy pole [txtEnfants] nie jest puste
5
rvEnfants
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]
6
txtSalaire
TextBox
EnableViewState=true
roczne wynagrodzenie
7
rfvSalaire
RequiredFieldValidator
ControlToValidate=txtSalaire
ErrorMessage=Podaj wysokość swojego wynagrodzenia
sprawdź, czy pole [txtSalaire] nie jest puste
8
revSalaire
RegularExpressionValidator
ControlToValidate=txtSalaire
ErrorMessage=Nieprawidłowe wynagrodzenie
RegularExpression=\s*\d+\s*
sprawdza, czy pole [txtSalaire] zawiera ciąg cyfr
9
btnCalculer
Przycisk
CausesValidation=true
przycisk [submit] w formularzu – uruchamia obliczenie podatku
10
btnEffacer
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
0
panelerreurs
Panel
EnableViewState=false
widok błędów
1
rptErreurs
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
0
panelsimulations
Panel
EnableViewState=false
widok symulacji
2
lnkForm2
LinkButton
EnableViewState=false
link do formularza
1
dgSimulations
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:

Image

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

Image

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:

Image

Texte de l'en-tête
nazwa kolumny, w tym przypadku „Żonaty”
Champ de données
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:

Texte de l'en-tête
Dzieci
Champ de données
dzieci

Trzecia kolumna jest zdefiniowana w następujący sposób:

Texte de l'en-tête
Roczne wynagrodzenie
Champ de données
wynagrodzenie
Expression de mise en forme
{0:C} – format walutowy umożliwiający wyświetlenie znaku euro

Czwarta kolumna jest zdefiniowana w następujący sposób:

Texte de l'en-tête
Kwota podatku
Champ de données
podatek
Expression de mise en forme
{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&#233;" HeaderText="Mari&#233;"></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&#244;t" HeaderText="Montant de l'imp&#244;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:

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

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


Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Collections
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]:

Image

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

Image

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.