Skip to content

7. Composants serveur ASP - 1

7.1. Introduction

Nous décrivons dans ce chapitre, la technologie préconisée dans ASP.NET pour construire l'interface utilisateur. Nous savons qu'il y a deux phases bien distinctes dans le traitement d'une page .aspx par le serveur web :

  1. il y a tout d'abord exécution du contrôleur de la page. Celui-ci est constitué par du code situé soit dans la page .aspx elle-même (solution WebMatrix) soit dans un fichier à part (solution Visual Studio.NET).
  2. puis le code de présentation de la page .aspx est exécuté pour être transformé en code HTML envoyé au client.

Image

ASP.NET offre trois bibliothèques de balises pour écrire le code de présentation de la page :

  1. les balises HTML classiques. C'est ce que nous avons utilisé jusqu'à maintenant.
  2. les balises HTML serveur
  3. les balises webforms

Quelque soit la bibliothèque de balises utilisée, le rôle du contrôleur de page ne change pas. Il doit calculer la valeur des paramètres dynamiques apparaissant dans le code de présentation. Jusqu'à maintenant, ces paramètres dynamiques étaient simples : c'étaient des objets de type [String]. Ainsi si dans le code de présentation on a une balise <%=nom%> :

  • le contrôleur de page déclare une variable [nom] de type [String] et calcule sa valeur
  • lorsque le contrôleur de page a terminé son travail et que le code de présentation est exécuté pour générer la réponse HTML, la balise <%=nom%> est remplacée par la valeur calculée par le code de contrôle

On sait que le découpage contrôleur/présentation est arbitraire et qu'on peut mélanger code contrôleur et code de présenttaion dans la même page. Nous avons expliqué pourquoi cette méthode était déconseillée et nous continuerons à respecter le découpage contrôle/présentation.

Les balises [HTML serveur] et [WebForms] permettent d'introduire dans le code de présentation des objets plus complexes que le simple objet [String]. Cela présente parfois un réel intérêt. Prenons l'exemple d'un formulaire avec liste. Celle-ci doit être présentée au client avec un code html qui ressemble à ceci :

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

Le contenu de la liste et l'option à sélectionner sont des éléments dynamiques et donc doivent être générés par le contrôleur de page. Nous avons déjà rencontré ce problème et l'avons résolu en mettant dans le code de présentation la balise

<%=uneListeHTML%>

Cette balise va être remplacée par la valeur [String] de la variable [uneListeHTML]. Cette valeur calculée par le contrôleur devra être le code HTML de la liste, c.a.d. "<select name=..>...</select>". Ce n'est pas particulièrement difficile à faire et cela paraît une solution élégante qui évite de mettre du code de génération directement dans la partie présentation de la page. Ici il y aurait une boucle avec des tests à insérer dans celle-ci qui serait ainsi fortement "polluée". Néanmoins, cette méthode présente un inconvénient. Le découpage contrôle/présentation d'une page sert aussi à délimiter deux domaines de compétence :

  • celui du développeur .NET qui s'occupe du contrôleur de page
  • celui de l'infographiste qui s'occupe de la partie présentation de celle-ci

Ici, on voit qu'on a transféré la génération du code HTML de la liste dans le contrôleur. L'infographiste peut vouloir agir sur ce code HTML pour modifier l'aspect "visuel" de la liste. Il sera obligé de travailler dans la partie [contrôleur] et donc de sortir de son domaine de compétence avec les risques d'erreurs introduites malencontreusement dans le code que cela comporte.

Les bibliothèques de balises serveur résolvent ce problème. Elles proposent un objet représentant une liste HTML. Ainsi la bibliothèque [WebForms] offre la balise suivante :

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

Cette balise représente un objet de type [ListBox] qui peut être manipulé par le contrôleur de page. Cet objet a des propriétés pour représenter les différentes options de la liste HTML et désigner l'option sélectionnée. Le contrôleur de page va donc donner les valeurs adéquates à ces propriétés. Lorsque la partie présentation va être exécutée, la balise

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

va être remplacée par le code HTML représentant l'objet [uneListe], c.a.d. le code "<select ..>...</select>". Pour l'instant, pas de différence fondamentale avec la méthode précédente sinon une façon de coder orientée objet, ce qui est intéressant. Revenons à notre infographiste qui a besoin de modifier le "look" de la liste. Les balises serveur ont des attributs de style (BackColor, Bordercolor, BorderWidth, ...) qui permettent de fixer l'aspect visuel de l'objet HTML correspondant. Ainsi on pourra écrire :


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

L'intérêt est que l'infographiste reste dans le code de présentation pour faire ces modifications. C'est un avantage certain vis à vis de la méthode précédente. Les bibliothèques de balises serveur amènent donc des facilités dans la construction de la partie présentation des pages, ce qu'on a appelé dans les chapitres précédents, l'interface utilisateur. Ce chapitre a pour but de les présenter. On verra qu'elle propose parfois des objets complexes tels que des calendriers ou des tables liées à des sources de données. Elles sont extensibles, c.a.d. que l'utilisateur peut créer sa propre bibliothèque de balises. Il peut ainsi réaliser une balise générant un bandeau dans une page. Toutes les pages utilisant cette balise auront alors le même bandeau.

La génération HTML faite pour une balise s'adapte au type du navigateur client. Lorsque celui-ci fait une requête au serveur web, il envoie parmi ses entêtes HTTP, un entête [User-Agent: xx] où [xx] identifie le client. Voici un exemple :

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

Avec cette information, le serveur web peut connaître les capacités du client, notamment le type de code HTML qu'il sait gérer. Il y a eu en effet, au fil du temps, plusieurs versions du langage HTML. Les navigateurs récents comprennent les versions les plus récentes du langage, ce que ne savent pas faire des navigateurs plus anciens. En fonction de l'entête HTTP [User-Agent:] que le client lui a envoyé, le serveur lui enverra une version HTML qu'il saura comprendre. C'est une idée intéressante et utile car le développeur n'a alors pas à se préoccuper du type de navigateur client de son application.

Enfin, des IDE évolués comme Visual Studio.NET, WebMatrix, ... permettent une conception "à la windows" de l'interface Web. Ces outils non indispensables, apportent cependant une aide décisive au développeur. Celui-ci dessine l'interface web à l'aide de composants graphiques qu'ils déposent sur cette interface. Il a un accès direct aux propriétés de chacun des composants de l'interface qu'il peut ainsi paramétrer à sa guise. Ces propriétés seront traduites dans le code HTML de présentation de l'interface en attributs de la balise <asp:> du composant. L'intérêt pour le développeur est qu'il n'a pas à se rappeler ni la liste ni la syntaxe des attributs de chaque balise. C'est un avantage appréciable lorsqu'on ne connaît pas parfaitement les bibliothèques de balises serveur offertes par ASP.NET. Lorsque cette syntaxe est acquise, certains développeurs pourront préférer coder directement les balises dans le code de présentation de la page sans passer par la phase conception graphique. Un IDE n'est plus alors utile. Un simple éditeur de texte suffit. Selon la façon dont on travaille, l'accent est alors mis sur les composants (utilisation d'un IDE) ou les balises (utilisation d'un éditeur de texte). Il y a équivalence entre ces deux termes. Le composant est l'objet qui va être manipulé par le code de contrôle de la page. L'IDE nous donne accès à ses propriétés en phase de conception. Les valeurs données à celles-ci sont traduites immédiatement dans les attributs de la balise du composant dans le code de présentation. En phase d'exécution, le code de contrôle de la page va manipuler le composant et affecter des valeurs à certaines de ses propriétés. Le code de présentation va lui générer le code HTML du composant en utilisant d'une part les attributs fixés à la conception pour la balise serveur correspondante, d'autre part les valeurs des propriétés du composant calculées par le code de contrôle.

7.2. Le contexte d'exécution des exemples

Nous allons illustrer la conception des interfaces web à base de composants serveur avec des programmes dont le contexte d'exécution sera la plupart du temps le suivant :

  1. l'application web sera composée d'une unique page P contenant un formulaire F,
  2. le client fera sa première requête directement à cette page P. Cela consistera à demander l'url de la page P avec un navigateur. C'est donc une requête GET qui sera faite sur cette url P. Le serveur délivrera la page P et donc le formulaire F qu'elle contient,
  3. l'utilisateur remplira celui-ci et le postera, c.a.d. qu'il fera une action qui forcera le navigateur à poster le formulaire F au serveur. L'opération POST du navigateur sera toujours à destination de la page P. Le serveur délivrera de nouveau la page P avec le formulaire F, le contenu de ce dernier ayant pu être modifié par l'action de l'utilisateur.
  4. puis les étapes 2 et 3 reprendront.

C'est un processus d'exécution bien particulier en-dehors duquel certains concepts exposés ci-après ne fonctionnent plus. Nous ne sommes plus dans un contexte d'architecture MVC dans lequel une application multi-pages est contrôlée par une page particulière que nous avons appelée "contrôleur d'application". Dans ce type d'architecture, le POST des formulaires ont pour cible le contrôleur et non les formulaires eux-mêmes. Or nous allons voir que construire un formulaire avec des composants serveur implique que ce formulaire soit posté à lui-même.

7.3. Le composant Label

7.3.1. Utilisation

La balise <asp:label> permet d'insérer un texte dynamique dans le code de présentation d'une page. Ca ne fait donc pas davantage que la balise <%=variable%> utilisée jusqu'à maintenant. L'étude de cette première balise va nous permettre de découvrir le mécanisme des balises serveur. Nous créons une page avec une partie contrôle [form1.aspx.vb] et une partie présentation [form1.aspx] . Il s'agit d'afficher l'heure :

Image

Ce problème a déjà été traité dans le chapitre 2 et le lecteur est invité à s'y reporter s'il souhaite savoir comment il avait été traité. Le code de présentation [form1.aspx] est le suivant :


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

Nous introduisons la balise <asp:label>. Dans les bibliothèque de balises, l'attribut [runat="server"] est obligatoire. L'attribut ID identifie le composant. Le contrôleur devra le référencer avec cet identifiant. Le code du contrôleur [form1.aspx.vb] est le suivant :


Imports System.Web.UI.WebControls

Public Class form1
   Inherits System.Web.UI.Page

   Protected lblHeure As Label

   Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Init
       ' sauve la requête courante dans request.txt du dossier de la page
       Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
       Me.Request.SaveAs(requestFileName, True)
   End Sub

   Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
       ' met l'heure dans lblHeure
       lblHeure.Text = "Il est " + Date.Now.ToString("T")
   End Sub

End Class

Le contrôleur doit donner une valeur à l'objet [lblHeure] de type [System.Web.UI.WebControls.Label]. Tous les objets affichés par les balises <asp:> appartiennent à l'espace de noms [System.Web.UI.WebControls]. Aussi pourra-t-on importer systématiquement cet espace de noms :


Imports System.Web.UI.WebControls

L'objet [Label] a différentes propriétés dont la propriété [Text] qui représente le texte qui sera affiché par la balise <asp:label> correspondante. Ici, nous mettons dans cette propriété l'heure courante. Nous le faisons dans la procédure [Form_Load] du contrôleur qui est systématiquement exécuté. Dans la procédure [Form_Init] qui elle aussi est toujours exécutée mais avant la procédure [Form_Load], nous mémorisons la requête du client dans un fichier [request.txt] du dossier de l'application. Nous aurons l'occasion d'examiner ce fichier pour comprendre certains aspects du fonctionnement des pages utilisant des balises serveur.

L'objet [Label] possède de nombreuses propriétés, méthodes et événements. Le lecteur est invité à lire la documentation sur la classe [Label] pour les découvrir. Dans la suite, il en sera toujours ainsi. Pour chaque balise, nous ne présentons que les quelques propriétés dont nous avons besoin.

7.3.2. Les tests

Nous plaçons les fichiers (form1.aspx, form1.aspx.vb) dans un dossier <application-path> et lançons Cassini avec les paramètres (<application-path>,/form1). Puis nous demandons l'url [http://localhost/form1/form1.aspx]. Nous obtenons le résultat suivant :

Image

Le code HTML reçu par le navigateur est le suivant :

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

On voit que la balise serveur


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

a été transformée en le code HTML suivant :

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

C'est la propriété [Text] de l'objet [lblHeure] qui a été placé entre les balises <span> et </span>. La requête faite par le client et mémorisée dans [request.txt] est la suivante :

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

Rien que de très normal.

7.3.3. Construire l'application avec WebMatrix

Nous avons construit le code de présentation de la page [form1.aspx] à la main. Cette méthode est utilisable si on connaît les balises. Un simple éditeur de texte suffit alors pour construire l'interface utilisateur. Pour débuter, un outil de conception graphique allié à une génération de code automatique est souvent nécessaire parce qu'on ne connaît pas la syntaxe des balises dont on a besoin. Nous construisons maintenant la même application avec l'outil WebMatrix. Une fois WebMatrix lancé, nous choisissons l'option [File/New File] :

Image

Nous créons une page ASP.NET appelée [form2.aspx]. Une fois validé l'assistant précédent, nous obtenons la fenêtre de conception de la page [form2.aspx] :

Image

Image

On rappelle que WebMatrix met le code de contrôle de la page et celui de présentation dans un même fichier, ici [form2.aspx]. L'onglet [All] présente le contenu de ce fichier texte. On peut constater dès maintenant qu'il n'est pas vide :

Image

L'inconvénient de ce type d'outils est que souvent ils génèrent du code inutile. C'est le cas ici, où WebMatrix a généré une balise HTML <form> alors qu'on ne va pas construire de formulaire... Par ailleurs, on peut constater que le document n'a pas de balise <title>. Nous remédions à ces deux problèmes sur le champ pour obtenir la nouvelle version suivante :

Image

Ce que nous appelons le code contrôleur va venir s'insérer entre les balises <script> et </script>, ce qui assure une séparation au moins visuelle entre les deux types de code : contrôle et présentation. Nous revenons sur l'onglet [Design] pour dessiner notre interface. Une liste de composants est disponible dans une fenêtre d'outils à gauche de la fenêtre de conception :

Image

La fenêtre d'outils offre l'accès à deux types de composants :

  • les composants [WebControls] qui se traduisent par des balises <asp:>
  • les composants [HTML Elements] qui se traduisent par des balises HTML classiques. Cependant, on peut ajouter aux attributs d'une balise HTML, l'attribut [runat="server"]. Dans ce cas, la balise HTML et ses attributs sont accessibles au contrôleur via un objet ayant pour propriétés celles de la balise HTML qu'il représente. On a appelé précédemment ces balises, les balises HTML serveur.

Double-cliquons sur le composant [Label] de la liste de contrôles [WebControls]. Dans l'onglet [Design] on obtient le résultat suivant :

Image

Dans l'onglet [All] le code est devenu le suivant :

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

On peut remarquer tout d'abord qu'on a perdu la balise <script>. Une balise <asp:label> a été générée. Elle a un nom [Label1] et une valeur [Label]. Revenons sur l'onglet [Design] pour modifier ces deux valeurs. Nous y cliquons une fois sur le composant [Label] pour faire apparaître la fenêtre de propriétés de ce composant, en bas à droite :

Image

Le lecteur est invité à consulter les propriétés de l'objet [Label]. Deux nous intéressent ici :

  • Text : c'est le texte que doit afficher le label - nous mettons la chaîne vide (c.a.d. rien)
  • ID : c'est son identifiant - nous mettons lblHeure

L'onglet [Design] devient ceci :

Image

et le code de [All] devient :

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

La partie présentation de la page est finie. Il nous reste à écrire le code de contrôle chargé de mettre l'heure dans la propriété [Text] de [lblHeure]. Nous ajoutons dans l'onglet [All] le code suivant :


<%@ Page Language="VB" %>

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

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

On remarquera que dans le code contrôleur, on ne déclare pas l'objet [lblHeure] comme il avait été fait précédemment :


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

En effet, tous les composants serveur <asp:> de la partie présentation font l'objet d'une déclaration implicite dans la partie code de contrôle. Aussi les déclarer provoque une erreur de compilation, celle-ci indiquant que l'objet est déjà déclaré. Nous sommes prêts pour l'exécution. Nous prenons l'option [View/Start] ou le raccourci [F5]. Cassini est automatiquement lancé avec les paramètres suivants :

Image

Nous acceptons ces valeurs. Le navigateur par défaut du système est automatiquement lancé pour demander l'url [http://localhost/form2.aspx]. Nous obtenons le résultat suivant :

Image

Par la suite, nous utiliserons principalement l'outil WebMatrix afin de faciliter la construction et les tests des courts programmes que nous écrirons.

7.4. Le composant Literal

7.4.1. Utilisation

La balise <asp:literal> permet d'insérer un texte dynamique dans le code de présentation d'une page comme la balise <asp:label>. Son principal attribut est [Text] qui représente le texte qui sera inséré tel quel dans le flux HTML de la page. Cette balise est suffisante si on n'a pas l'intention de mettre en forme le texte que l'on veut insérer dans le flux HTML. En effet si la classe [Label] permet de mettre en forme grâce à des attributs tels que [BorderColor, BorderWidth, Font, ...], la classe [Literal] n'a aucun de ces attributs. Le lecteur pourra reprendre intégralement l'exemple précédent en remplaçant le composant [Label] par un composant [Literal].

7.5. Le composant Button

7.5.1. Utilisation

La balise <asp:Button> permet d'insérer un bouton de type [Submit] dans un formulaire qui amène avec lui une gestion d'événements semblable à celle que l'on trouve dans les applications windows. C'est ce point qu'on veut approfondir ici. Nous créons la page [form3.aspx] suivante :

Image

Cette page construite avec WebMatrix a trois composants :

nom

type

propriétés

rôle

1


Button1

Button

text=Bouton1

bouton submit

2


Button2

Button

text=Bouton2

bouton submit

3


lblInfo

Label

text=

message d'information

Le code généré par WebMatrix pour cette partie est le suivant :

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

Nous retrouvons les composants [Button] et [Label] utilisés lors de la conception graphique de la page dans des balises <asp:>. Notons la balise <form runat="server"> qui a été générée automatiquement. C'est une balise HTML serveur, c.a.d. une balise HTML classique représentée néanmoins par un objet manipulable par le contrôleur. Le code HTML de la balise <form> sera généré à partir de la valeur que le contrôleur donnera à cet objet.

Ajoutons dans la partie contrôleur du code, la procédure [Page_Init] qui gère l'événement [Init] de la page. Nous y plaçons le code qui sauvegarde la requête du client dans le fichier [request.txt]. Nous en aurons besoin pour comprendre le mécanisme des boutons.

<script runat="server">
    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) 
        ' sauve la requte courante dans request.txt du dossier de la page
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub
</script>

On notera ci-dessus que nous n'avons pas mis la clause [Handles MyBase.Init] derrière la déclaration de la procédure [Page_Init]. En effet, l'événement [Init] de l'objet [Page] a un gestionnaire par défaut qui s'appelle [Page_Init]. Si on utilise ce nom de gestionnaire, la clause [Handles Page.Init] devient inutile. La mettre ne provoque cependant pas d'erreur.

7.5.2. Tests

Nous lançons l'application sous WebMatrix par [F5]. Nous obtenons la page suivante :

Image

Le code HTML reçu par le navigateur est le suivant :

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

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

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

Notons les points suivants :

  • la balise <form runat="server"> s'est transformée en balise HTML
        <form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
    

Deux attributs ont été fixés [method="post"] et [action="form3.aspx"]. Peut-on donner des valeurs différentes à ces attributs ? Nous essaierons d'éclaircir ce point un peu plus loin. On retiendra ici que le formulaire sera posté à l'url [form3.aspx]. Deux autres attributs [name, id] ont été également fixés. La plupart du temps, ils sont ignorés. Cependant si la page contient du code Javascript exécuté côté navigateur, l'attribut [name] de la balise <form> est utile.

  • les balises <asp:button> sont devenues des balises HTML de boutons [submit]. Un clic sur l'un quelconque de ces boutons provoquera donc un "post" du formulaire [_ctl10] à l'url [form3.aspx].
  • la balise <asp:label> est devenue une balise HTML <span>
  • un champ caché [__VIEWSTATE] a été généré avec une valeur bizarre :
    <input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />
    

Ce champ représente sous une forme codée l'état du formulaire envoyé au client. Cet état représente la valeur de tous les composants de celui-ci. Comme [__VIEWSTATE] fait partie du formulaire, sa valeur sera postée avec le reste au serveur. Cela permettra à celui-ci de savoir quel composant du formulaire a changé de valeur et éventuellement de prendre des décisions. Celles-ci prendront la forme d'événements du type "le TextBox untel a changé de valeur".

7.5.3. Les requêtes du client

Une fois obtenue la page [form3.aspx] dans le navigateur, redemandons-la puis regardons la requête qui a été envoyée par le navigateur pour l'obtenir. On se rappelle que notre application la mémorise dans le fichier [request.txt] dans le dossier de l'application :

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

On a là un GET classique. Maintenons cliquons sur le bouton [Bouton1] de la page dans le navigateur. Il ne se passe apparemment rien. Pourtant nous savons que le formulaire a été posté. C'est le code HTML de la page qui le dit. Cela est confirmé par le nouveau contenu de [request.txt] :

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

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

Le premier entête HTTP indique bien que le client a fait un POST vers l'url [/form3.aspx]. La dernière ligne montre les valeurs postées :

  • la valeur du champ caché __VIEWSTATE
  • la valeur du bouton qui a été cliqué

Si nous cliquons sur [Bouton2], les valeurs postées par le navigateur sont les suivantes :

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

La valeur du champ caché est toujours la même mais c'est la valeur de [Button2] qui a été postée. Le serveur peut donc savoir quel bouton a été utilisé. Il va s'en servir pour déclencher un événement qui pourra être traité par la page, une fois que celle-ci aura été chargée.

7.5.4. Gérer l'événement Click d'un objet Button

Rappelons le fonctionnement d'une page .aspx. Celle-ci est un objet dérivé de la classe [Page]. Appelons la classe dérivée [unePage]. Lorsque le serveur reçoit une requête pour une telle page, un objet de type [unePage] est instancié par une opération new unePage(...). Puis ensuite, le serveur génère deux événements appelés [Init] et [Load] dans cet ordre. L'objet [unePage] peut les gérer en fournissant les gestionnaires d'événements [Page_Init] et [Page_Load]. Ensuite d'autres événements seront générés. Nous aurons l'occasion d'y revenir. Si la requête du client est un POST, le serveur générera l'événement [Click] du bouton qui a provoqué ce POST. Si la classe [unePage] a prévu un gestionnaire pour cet événement, celui-ci sera appelé. Voyons ce mécanisme avec WebMatrix. Dans l'onglet [Design] de [form3.aspx], double-cliquons sur le bouton [Bouton1]. Nous sommes alors amenés automatiquement dans l'onglet [Code], dans le corps d'une procédure appelée [Button1_Click]. Pour mieux comprendre, allons dans l'onglet [All] et regardons la totalité du code. Les modifications suivantes ont été apportées :

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

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

    End Sub

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

Un nouvel attribut [onclick="Button1_Click"] a été ajouté à la balise <asp:Button> de [Button1]. Cet attribut indique la procédure chargée de traiter l'événement [Click] sur l'objet [Button1], ici la procédure [Button1_Click]. Il ne nous reste plus qu'à écrire celle-ci :

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

La procédure met dans le label [lblInfo] un message d'information. Nous procédons de la même façon pour le bouton [Bouton2] pour obtenir la nouvelle page [form3.aspx] suivante :

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

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
        ' sauve la requte courante dans request.txt du dossier de la page
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub

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

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

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

Nous lançons l'exécution par [F5] pour obtenir la page suivante :

Image

Si nous regardons le code HTML reçu, nous constaterons qu'il n'a pas changé vis à vis de celui de la version précédente de la page :

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

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

Si nous cliquons sur [Bouton1], nous obtenons la réponse suivante :

Image

Le code HTML reçu pour cette réponse est le suivant :

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

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

Nous pouvons constater que le champ caché [__VIEWSTATE] a changé de valeur. Cela reflète le changement de valeur du composant [lblInfo].

7.5.5. Les événements de la vie d'une application ASP.NET

La documentation ASP.NET donne la liste des événements générés par le serveur au cours de la vie d'une application :

  • lors de la toute première requête que reçoit l'application, l'événement [Start] sur l'objet [HttpApplication] de l'application va être généré. Cet événement peut être géré par la procédure [Application_Start] du fichier [global.asax] de l'application.

Ensuite nous allons avoir une série d'événements qui vont se répéter pour chaque requête reçue :

  • si la requête n'a pas envoyé de jeton de session, une nouvelle session est démarrée et un événement [Start] sur l'objet [Session] associé à la requête est généré. Cet événement peut être géré par la procédure [Session_Start] du fichier [global.asax] de l'application.
  • le serveur génère l'événement [BeginRequest] sur l'objet [HttpApplication]. Il peut être géré par la procédure [Application_BeginRequest] du fichier [global.asax] de l'application.
  • le serveur charge la page demandée par la requête. Il va instancier un objet [Page] puis générer deux événements sur cet objet : [Init] puis [Load]. Ces deux événements peuvent être gérés par les procédure [Page_Init] et [Page_Load] de la page.
  • à partir des valeurs postées reçues, le serveur va générer d'autres événements : [TextChanged] pour un composant [TextBox] qui a changé de valeur, [CheckedChanged] pour un bouton radio qui a changé de valeur, [SelectedIndexChanged] pour une liste qui a changé d'élément sélectionné, ... Nous aurons l'occasion de mentionner les principaux événements pour chacun des composants serveur que nous allons présenter. Chaque événement E sur un objet nommé O peut être géré par une procédure portant le nom O_E.
  • l'ordre de gestion des événements précédents n'est pas garanti. Aussi les gestionnaires ne doivent-ils faire aucune hypothèse sur cet ordre. On est cependant assuré que l'événement [Click] du bouton qui a provoqué le POST est traité en dernier.
  • une fois la page prête, le serveur va l'envoyer au client. Avant, il génère l'événement [PreRender] qui peut être géré par la procédure [Page_PreRender] de la page.
  • une fois la réponse HTML envoyée au client, la page va être déchargée de la mémoire. Deux événements seront générés à cette occasion [Unload] et [Disposed]. La page peut utiliser ces événements pour libérer des ressources.

L'application peut également recevoir des événements en-dehors d'une requête client :

  • l'événement [End] sur un objet [Session] de l'application se produit lorsqu'une session se termine. Cela peut se produire sur une demande explicite du code d'une page soit parce que la durée de vie accordée à la session est dépassé. La procédure [Session_End] du fichier [global.asax] gère cet événement. On y libère en général des ressources obtenues dans [Session_Start].
  • l'événement [End] sur l'objet [HttpApplication] de l'application se produit lorsque l'application se termine. Cela se produit notamment lorsqu'on arrête le serveur Web. La procédure [Application_End] du fichier [global.asax] gère cet événement. On y libère en général des ressources obtenues dans [Application_Start].

On retiendra les points suivants :

  • le modèle événementiel précédent s'appuie sur des échanges HTTP client-serveur classiques. On le voit parfaitement lorsqu'on examine les entêtes HTTP échangés.
  • le traitement des événements précédents se fait toujours côté serveur. Le clic sur un bouton peut bien sûr être traité par un script Javascript côté serveur. Mais il ne s'agit pas alors d'un événement serveur et on est là dans une technologie indépendante de ASP.NET.

A quel moment sont traités les événements, qu'ils soient traités du côté serveur (événements liés aux composants serveur) ou du côté navigateur par des scripts Javascript ?

Prenons l'exemple d'une liste déroulante. Lorsque l'utilisateur change l'élément sélectionné dans celle-ci, l'événement (changement de l'élément sélectionné) peut être traité ou non et s'il est traité il peut l'être à différents moments.

  • si on souhaite le traiter immédiatement, on a deux solutions :
    • il peut être traité par le navigateur à l'aide d'un script Javascript. Le serveur n'intervient alors pas. Pour que cela soit possible, il faut que la page puisse être reconstruite avec des valeurs présentes dans la page.
    • il peut être traité par le serveur. Pour cela, il n'y a qu'une solution : le formulaire doit être envoyé au serveur pour traitement. On a donc une opération [submit]. On verra que dans ce cas, on utilise un composant serveur appelé [DropDownList] et on fixe son attribut [AutoPostBack] à [true]. Cela veut dire qu'en cas de changement d'élément sélectionné dans la liste déroulante, le formulaire doit être immédiatement posté au serveur. Le serveur génère dans ce cas, pour l'objet [DropDownList] un code HTML associé à une fonction Javascript chargée de faire un [submit] dès que l'événement "changement de l'élément sélectionné" se produit. Ce [submit] postera le formulaire au serveur et dans celui-ci des champs cachés seront positionnés pour indiquer que le [post] provient d'un changement de sélection dans la liste déroulante. Le serveur génèrera alors l'événement [SelectedIndexChanged] que la page pourra gérer.
    • si on souhaite le traiter mais pas immédiatement, on fixe l'attribut [AutoPostBack] du composant serveur [DropDownList] à [false]. Le serveur génère dans ce cas, pour l'objet [DropDownList] le code HTML classique d'une liste <select> sans fonction Javascript associée. Rien ne se passe alors lorsque l'utilisateur change la sélection de la liste déroulante. Cependant, lorsqu'il va valider le formulaire par un bouton [submit] par exemple, le serveur va pouvoir reconnaître qu'il y a eu un changement de sélection. Nous avons en effet vu que le formulaire envoyé au serveur avait un champ caché [__VIEWSTATE] représentant sous une forme codée l'état de tous les éléments du formulaire envoyé. Lorsque le serveur reçoit le nouveau formulaire posté par le client, il va pouvoir vérifier si la liste déroulante a changé ou non d'élément sélectionné. Si oui, il va générer l'événement [SelectedIndexChanged] que la page pourra alors gérer. Pour distinguer ce mécanisme du précédent, certains auteurs disent que l'événement "changement de sélection" a été mis "en cache" lorsqu'il s'est produit sur le navigateur . Il ne sera traité par le serveur que lorsque le navigateur lui postera le formulaire, à la suite souvent d'un clic sur un bouton [submit].
    • enfin si on ne souhaite pas traiter l'événement, on fixe l'attribut [AutoPostBack] du composant serveur [DropDownList] à [false] et on n'écrit pas le gestionnaire de son événement [SelectedIndexChanged].

Une fois compris le mécanisme de traitement des événements, le développeur ne concevra pas une application web comme une application windows. En effet, si un changement de sélection dans un combobox d'une application windows peut être utilisé pour changer immédiatement l'aspect du formulaire dans lequel se trouve celui-ci, on hésitera davantage à traiter cet événement immédiatement dans une application web s'il implique un "post" du formulaire au serveur donc un aller-retour réseau entre le client et le serveur. C'est pourquoi, la propriété [AutoPostBack] des composants serveur est mise à [false] par défaut. Par ailleurs, le mécanisme [AutoPostBack] qui s'appuie sur des scripts javascript générés automatiquement par le serveur web dans le formulaire envoyé au client ne peut être utilisé que si on est sûr que le navigateur client a autorisé l'exécution des scripts javascript sur son navigateur. Les formulaires sont donc souvent construits de la façon suivante :

  • les composants serveur du formulaire ont leur propriété [AutoPostBack] à [false]
  • le formulaire a un ou des boutons chargés de faire le [POST] du formulaire
  • on écrit dans le code contrôleur de la page, les gestionnaires des seuls événements qu'on veut gérer, le plus souvent l'événement [Click] sur un des boutons.

7.6. Le composant TextBox

7.6.1. Utilisation

La balise <asp:TextBox> permet d'insérer un champ de saisie dans le code de présentation d'une page. Nous créons une page [form4.aspx] pour obtenir la présentation suivante :

Image

Cette page construite avec WebMatrix a les composants suivants :

nom

type

propriétés

rôle

1


TextBox1

TextBox

AutoPostback=true

Text=

champ de saisie

2


TextBox2

TextBox

AutoPostback=false

Text=

champ de saisie

3


lblInfo1

Label

text=

message d'information sur le contenu de [TextBox1]

3


lblInfo2

Label

text=

message d'information sur le contenu de [TextBox2]

Le code généré par WebMatrix pour cette partie est le suivant :

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

Das l'onglet [Design], double-cliquons sur le composant [TextBox1]. Le squelette du gestionnaire de l'événement [TextChanged] de cet objet est alors généré (onglet [All]) :

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

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

L'attribut [OnTextChanged="TextBox1_TextChanged"] a été ajouté à la balise <asp:TextBox id="TextBox1"> pour désigner le gestionnaire de l'événement [TextChanged] sur [TextBox1]. C'est la procédure [TextBox1_Changed] que nous écrivons maintenant.

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

Dans la procédure, nous écrivons dans le label [lblInfo1] un message signalant l'événement et indiquant le contenu de [TextBox1]. On fait de même pour [TextBox2]. Nous indiquons également l'heure pour mieux suivre le traitement des événements. Le code final de [form4.aspx] est le suivant :

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

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
        ' sauve la requte courante dans request.txt du dossier de la page
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub

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

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

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

Nous avons ajouté la procédure [Page_Init] pour mémoriser la requête du client comme dans l'exemple précédent.

7.6.2. Tests

Nous lançons l'application sous WebMatrix par [F5]. Nous obtenons la page suivante :

Image

Le code HTML reçu par le navigateur est le suivant :

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

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

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

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

Il y a beaucoup de choses dans ce code qui ont générées automatiquement par le serveur. Notons les points suivants :

  • il y a trois champs cachés : [__VIEWSTATE] que nous avons déjà rencontré, [__EventTarget] et [__EventArgument]. Ces deux derniers champs sont utilisés pour gérer l'événement navigateur "change" sur le champ de saisie [TextBox1]
  • les balises serveur <asp:textbox> ont donné naissance aux balises HTML <input type="text" ...> qui correspondent aux champs de saisie
  • la balise serveur <asp:textbox id="TextBox1" AutoPostBack="true" ...> a donné naissance à une balise <input type="text" ...> ayant un attribut [onchange="__doPostBack('TextBox1','')"]. Cet attribut dit qu'en cas de changement du contenu de [TextBox1], la fonction Javascript [_doPostBack(...)] doit être exécutée. Celle-ci est la suivante :
    <script language="javascript">
    <!--
        function __doPostBack(eventTarget, eventArgument) {
            var theform = document._ctl0;
            theform.__EVENTTARGET.value = eventTarget;
            theform.__EVENTARGUMENT.value = eventArgument;
            theform.submit();
        }
    // -->
    </script>
    

Que fait la fonction ci-dessus ? Elle affecte une valeur à chacun des deux champs cachés [__EventTarget] et [__EventArgument] puis poste le formulaire. Celui-ci est donc envoyé au serveur. C'est l'effet [AutoPostBack]. L'événement navigateur "change" provoque l'exécution du code "__doPostBack('TextBox1','')". On en déduit que dans le formulaire posté, le champ caché [__EventTarget] aura la valeur 'TextBox1' et le champ caché [__EventArgument] la valeur ''. Cela permettra au serveur de connaître le composant qui a provoqué le POST.

  • la balise serveur <asp:textbox id="TextBox2"...> a donné naissance à une balise <input type="text" ...> classique parce que son attribut [AutoPostBack] n'était pas positionné à [true].
  • la balise <form> indique que le formulaire sera posté à [form4.aspx] :
        <form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">
    

Faisons notre permier test. Tapons un texte dans le premier champ de saisie :

Image

puis mettons le curseur dans le second champ de saisie. Aussitôt, nous obtenons une nouvelle page :

Image

Que s'est-il passé ? Lorsque le curseur a quitté le premier champ de saisie, le navigateur a vérifié si le contenu de celui-ci avait changé. C'était le cas. Il a donc généré côté navigateur, l'événement [change] sur le champ HTML [TextBox1]. Nous avons vu alors qu'une fonction Javascript s'exécutait et postait le formulaire à [form4.aspx]. Cette page a donc été rechargée par le serveur. Les valeurs postées par le formulaire ont permis au serveur de savoir à son tour que le contenu de la balise serveur [TextBox1] avait changé. La procédure [TextBox1_Changed] a donc été exécutée côté serveur. Elle a placé un message dans le label [lblInfo1]. Une fois cette procédure terminée, [form4.aspx] a été envoyée au navigateur. C'est pourquoi nous avons maintenant un texte dans [lblInfo1]. Ceci dit, on peut s'étonner d'avoir quelque chose dans le champ de saisie [TextBox1]. En effet, aucune procédure exécutée côté serveur n'affecte de valeur à ce champ. On a là un mécanisme général des formulaires web ASP.NET : le serveur renvoie le formulaire dans l'état où il l'a reçu. Pour cela, il réaffecte aux composants la valeur qui a été postée pour eux par le client. Pour certains composants, le client ne poste aucune valeur. C'est le cas par exemples des composants <asp:label> qui sont traduits en balises HTML <span>. On se souvient que le formulaire a un champ caché [__VIEWSTATE] qui représente l'état du formulaire lorsqu'il est envoyé au client. Cet état est la somme des états de tous les composants du formulaire y compris les composants <asp:label> s'il y en a. Comme le champ caché [__VIEWSTATE] est posté par le navigateur client, le serveur est capable de restituer l'état précédent de tous les composants du formulaire. Il ne lui reste plus qu'à modifier ceux qui ont vu leur valeur changée par le POST.

Regardons maintenant dans [request.txt] la requête qui a été faite par le navigateur :

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

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

On voit parfaitement le POST ainsi que les paramètres postés. Revenons à notre navigateur et saisissons un texte dans le second champ de saisie :

Image

Revenons sur le champ de saisie n° 1 pour saisir un nouveau texte : rien ne se passe cette fois-ci. Pourquoi ? parce que le champ de saisie [TextBox2] n'ayant pas sa propriété [AutoPostBack] à [true], la balise <input type="text"...> générée pour lui ne gère pas l'événement [Change] comme le montre son code HTML :

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

Aucun événement ne se produit donc lorsqu'on quitte le champ de saisie n° 2. Maintenant entrons un nouveau texte dans le champ n° 1 :

Image

Quittons le champ de saisie n° 1. Aussitôt l'événement [Change] sur ce champ est détecté et le formulaire posté au serveur qui renvoie en retour la page suivante :

Image

Que s'est-il passé ? Tout d'abord, le navigateur a posté le formulaire. On retrouve ce fait dans la requête du client mémorisée dans [request.txt] :

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

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

Le serveur commence par restituer aux composants leurs valeurs précédentes grâce au champ caché [__VIEWSTATE] que le client lui a envoyé. Grâce aux champs postés [TextBox1] et [TextBox2] il va affecter aux composants [TextBox1] et [TextBox2] les valeurs qui lui ont été postées. C'est par ce mécanisme que le client va retrouver le formulaire tel qu'il a été posté. Puis, toujours grâce aux champs postés [__VIEWSTATE], [TextBox1] et [TextBox2], le serveur va découvrir que les valeurs des champs de saisie [TextBox1] et [TextBox2] ont changé. Il va donc générer les événements [TextChanged] pour ces deux objets. Le procédures [TextBox1_TextChanged] et [TextBox2_TextChanged] vont être exécutées et les labels [labelInfo1] et [labelInfo2] recevoir une nouvelle valeur. Puis la page [form4.aspx] ainsi modifiée est renvoyée au client.

Maintenant nous modifions de nouveau le champ de saisie n° 1 :

Image

Lorsque nous sortons le curseur de saisie du champ 1, l'événement [Change] se produit sur le navigateur. Ensuite la séquence d'événements déjà expliquée (post du navigateur vers le serveur, ..., envoi de la réponse du serveur) se déroule. On obtient la réponse suivante :

Image

A cause de l'heure affichée pour chacun des messages, on voit que seule la procédure [TextBox1_Changed] a été exécutée sur le serveur. La procédure [TextBox2_TextChanged] n'a pas été exécutée parce que justement la valeur de [TextBox2] n'a pas changé. Enfin, mettons un nouveau texte dans le champ n° 2 :

Image

Puis plaçons le curseur sur le champ n° 1 puis remettons-le sur le champ n° 2. La page ne change pas. Pourquoi ? Parce que nous ne changeons pas la valeur du champ n° 1, l'événement navigateur [Change] ne se produit pas lorsque nous quittons ce champ. Du coup, le formulaire n'est pas posté au serveur. Donc rien ne change sur la page. C'est le fait que le contenu de [lblInfo2] ne change pas qui nous montre qu'il n'y a pas de POST. S'il y en avait un, le serveur détecterait que le contenu de [TextBox2] a changé et devrait refléter ce fait dans [lblInfo2].

On retiendra de cet exemple qu'il n'y a pas d'intérêt à mettre la propriété [AutoPostBack] d'un [TextBox] à [true]. Cela provoque un aller-retour client-serveur inutile la majeure partie du temps.

7.6.3. Le rôle du champ __VIEWSTATE

Nous avons vu que le serveur mettait systématiquement un champ caché appelé __VIEWSTATE dans le formulaire qu'il générait. Nous avons dit que ce champ représentait l'état du formulaire et que si on lui retournait ce champ caché, le serveur était capable de reconstituer la valeur précédente du formulaire. L'état d'un formulaire est la somme des états de ses composants. Chacun d'eux a une propriété [EnableViewState] à valeur booléenne indiquant si l'état du composant doit être placé ou non dans le champ caché [__VIEWSATE]. Par défaut cette propriété a la valeur [true] faisant que l'état de tous les composants d'un formulaire est placé dans [__VIEWSTATE]. Quelquefois, ce n'est pas souhaitable.

Faisons quelques expériences pour mieux comprendre le rôle de la propriété [EnableViewState]. Mettons cette propriété à [false] pour les deux champs de saisie :

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

Exécutons maintenant l'application et tapons un premier texte dans le champ n° 1 avant de passer dans le champ n° 2. Un POST est alors fait vers le serveur et on obtient la réponse suivante :

Image

Tapons un texte dans le champ n° 2, modifions celui du champ n° 1 puis revenons dans le champ n° 2 (dans cet ordre). Un nouveau POST est fait vers le serveur et nous obtenons la réponse suivante :

Image

Pour l'instant, tout est comme avant. Maintenant modifions le contenu du champ n° 1 puis passons dans le champ n° 2. Un nouveau POST est fait. La réponse du serveur est la suivante :

Image

Cette fois-ci, il y a un changement. Le serveur a détecté un événement [TextChanged] sur le champ n° 2 puisque l'heure de [lblInfo2] a été modifiée. Or il n'y avait pas de changement. Ceci s'explique par la propriété [EnableViewState=false] de [TextBox2]. Elle fait que le serveur n'a pas mis dans le champ [__VIEWSTATE] du formulaire, l'état précédent de [TextBox2]. Cela revient à lui affecter la chaîne vide comme état précédent. Lorsque le POST du au changement du contenu de [TextBox1] s'est produit, le serveur a comparé la valeur actuelle de [TextBox2] qui était [deux] à sa valeur précédente (la chaîne vide). Il a en conclu que [TextBox2] avait changé de valeur et a généré l'événement [TextChanged] pour [TextBox2]. On peut valider ce fonctionnement en mettant la chaîne vide dans [TextBox2]. D'après ce qui vient d'être expliqué, le serveur ne devrait pas générer l'événement [TextChanged] pour [TextBox2]. Essayons :

Image

C'est bien ce qui s'est passé. L'heure et le contenu de [lblInfo2] montrent que la procédure [TextBox2_TextChanged] n'a pas été exécutée. Ceci compris, examinons la propriété [EnableViewState] des quatre composants du formulaire :


TextBox1

on souhaite maintenir l'état de ce composant afin que le serveur sache s'il a changé ou non


TextBox2

idem


lblInfo1

on ne souhaite pas maintenir l'état de ce composant. On veut que le texte soit recalculé à chaque nouveau POST. S'il n'est pas recalculé, il doit être vide. Tout cela est bien obtenu avec [EnableViewState=false]


lblInfo2 

idem

Notre page de présentation devient la suivante :

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

Refaisons la même série de tests que précédemment. Là où nous avons obtenu l'écran

Image

nous obtenons maintenant :

Image

Dans cette étape, on changeait le contenu du champ 1 sans changer celui du champ 2 faisant en sorte que la procédure [TextBox2_TextChanged] n'était pas exécutée côté serveur ce qui impliquait que le champ [lblInfo2] ne recevait pas de nouvelle valeur. Il est donc affiché avec sa valeur précédente. Dans la version 1 [EnableViewState=true] cette valeur précédente était la valeur sasie. Dans la version 2 [EnableViewState=false], cette valeur précédente est la chaîne vide.

Il est parfois inutile de conserver l'état précédent des composants. Plutôt que de mettre [EnableViewState=false] pour chacun d'eux on peut déclarer que la page ne doit pas maintenir son état. Cela se fait dans la directive [Page] du code de présentation :

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

Dans ce cas, quelque soit la valeur de sa propriété [EnableViewState], l'état d'un composant n'est pas mis dans le champ caché [__VIEWSTATE]. Tout se passe alors comme si son état précédent était la chaîne vide.

Utilisons maintenant le client [curl] pour mettre en lumière d'autres mécanismes. Nous demandons tout d'abord l'url [http://localhost/form4.aspx] :

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

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


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

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

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

Nous recevons du serveur le code HTML de [form4.aspx]. Il n'est pas différent de celui qu'avait reçu le navigateur. Rappelons ici, la requête faite par le navigateur lorsqu'il postait le formulaire :

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

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

Faisons ce même POST mais sans envoyer le champ [__VIEWSTATE] :

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

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

On notera les points suivants :

  • le serveur a détecté un événement [TextChanged] sur [TextBox1] puisqu'il a généré le texte [lblInfo1]. L'absence de [__VIEWSTATE] ne l'a pas gêné. En son absence, il suppose que la valeur précédente d'un champ de saisie est la chaîne vide.
  • il a été capable de remettre le texte posté pour [TextBox1] dans l'attribut [value] de la balise [TextBox1] afin que le champ [TextBox1] réapparaisse avec la valeur saisie. Pour cela, il n'a pas besoin de [__VIEWSTATE] mais seulement de la valeur postée pour [TextBox1]

Maintenant, refaisons la même requête sans rien changer. Nous obtenons la nouvelle réponse suivante :

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


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

En l'absence de [__VIEWSTATE], le serveur n'a pas pu voir que le champ [TextBox1] n'avait pas changé de valeur. Il fait alors comme si la valeur précédente était la chaîne vide. Il a donc généré ici l'événement [TextChanged] sur [TextBox1]. Rejouons toujours la même requête avec cette fois-ci le champ [TextBox1] vide et [TextBox2] non vide :

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

En l'absence de [__VIEWSTATE] la valeur précédente de [TextBox1] a été considérée comme la chaîne vide. La valeur postée de [TextBox1] étant également la chaîne vide, l'événement [TextChanged] sur [TextBox1] n'a pas été généré. La procédure [TextBox1_TextChanged] n'a pas été exécutée et donc le champ [lblInfo1] n'a pas reçu de nouvelle valeur. On sait que dans ce cas, le composant garde son ancienne valeur. Or ici, ce n'est pas le cas, [lblInfo1] a perdu sa valeur précédente. Ceci, parce que cette valeur est cherchée dans [__VIEWSTATE]. Comme ce champ est absent, la chaîne vide a été affectée à [lblInfo1]. Pour [TextBox2], le serveur a comparé sa valeur postée [second texte] à sa valeur précédente. En l'absence de [__VIEWSTATE], cette valeur précédente est égale à la chaine vide. La valeur postée de [TextBox2] étant différente de la chaîne vide, l'événement [TextChanged] sur [TextBox2] a été généré. La procédure [TextBox2_TextChanged] a été exécutée et le champ [lblInfo2] a reçu une nouvelle valeur.

On peut se demander si les paramètres [__EVENTTARGET] et [__EVENTARGUMENT] sont bien utiles. En n'envoyant pas ces paramètres, le serveur ne saura pas par quel événement a été provoqué le [submit]. Essayons :

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

On voit qu'aucun événement [TextChanged] n'a été traité. Par ailleurs, les champs postés [TextBox1] et [TextBox2] ne retrouvent pas leurs valeurs postées. Tout se passe en fait comme si on avait fait un GET. Tout redevient normal si on a le champ [__EVENTTARGET] dans les champs postés, même s'il n'a pas de valeur :

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

7.6.4. Autres propriétés du composant TextBox

Le composant serveur [TextBox] permet également de générer les balises HTML <input type="password"..> et <texarea>..</textarea>, c.a.d. les balises correspondant respectivement à un champ de saisie protégée et un champ de saisie multilignes. Cette génération est contrôlée par la propriété [TextMode] du composant [TextBox]. Elle a trois valeurs possibles :

valeur

balise HTML générée


SingleLine

<input type="text" ...>


MultiLine

<textarea>...</textarea>


Password

<input type="password ...>

Nous examinons l'usage de ces propriétés avec l'exemple [form5.aspx] suivant :

Image

nom

type

propriétés

rôle

1


btnAjouter

Button

 

bouton [submit] - sert à ajouter le contenu de [TextBox1] à celui de [TextBox2]

2


TextBox1

TextBox

TextMode=Password

Text=

champ de saisie protégée

3


TextBox2

TextBox

TextMode=Multilignes

Text=

cumule les saisies faites dans [TextBox1]

La propriété [EnableViewState] de la page est mise à [false]. Côté serveur, nous gérons l'événement clic sur le boutton [btnAjouter] :

Sub btnAjouter_Click(sender As Object, e As EventArgs)
  ' on ajoute le contenu de [textBox1] à celui de [TextBox2]
  textbox2.text=textbox2.text + textbox1.text+controlchars.crlf
End Sub

Pour comprendre ce code, il faut se rappeler le mode de traitement du POST d'un formulaire. Les procédures [Page_Init] et [Page_Load] sont exécutées en premier. Puis viennent toutes les procédures d'événements mis en cache. Enfin la procédure gérant l'événement qui a provoqué le [POST] est exécutée, ici la procédure [btnAjouter_Click]. Lorsque les gestionnaires d'événements s'exécutent, tous les composants de la page ayant une valeur dans le POST ont pris cette valeur. Les autres ont retrouvé leur valeur précédente si leur propriété [EnableViewState] était à [true] ou leur valeur de conception si leur propriété [EnableViewState] était à [false]. Ici, les valeurs des champs [TextBox1] et [TextBox2] vont faire partie du POST fait par le client. Aussi dans le code précédent, [textbox1.text] aura pour valeur la valeur postée par le client, de même pour [textbox2.text]. La procédure [btnAjouter_Click] met dans le champ [TextBox2] la valeur postée pour [TextBox2] ajoutée à celle postée pour [TextBox1] ajoutée à la marque de fin de ligne [ControlChars.CrLf] définie dans l'espace de noms [Microsoft.VisualBasic]. Il n'est pas nécessaire d'importer cet espace, le serveur web l'important par défaut.

Le code final de [form5.aspx] est le suivant :

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

    Sub btnAjouter_Click(sender As Object, e As EventArgs)
      ' on ajoute le contenu de [textBox1] à celui de [TextBox2]
      textbox2.text+=textbox1.text+controlchars.crlf
    End Sub

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

Un peu plus haut, on a donné la copie d'écran d'une exécution.

7.7. Le composant DropDownList

La balise <asp:DropDownList> permet d'insérer une liste déroulante dans le code de présentation d'une page. Nous créons une page [form6.aspx] pour obtenir la présentation suivante :

Image

nom

type

propriétés

rôle

1


DropDownList1

DropDownList

AutoPostback=true

EnableViewState=true

liste déroulante

2


lblInfo

Label

EnableViewState=false

message d'information

Le code de présentation généré est le suivant :

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

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

Pour l'instant, la liste déroulante ne contient aucun élément. Nous allons la remplir dans la procédure [Page_Load]. Pour cela nous devons connaître certaines des propriétés et méthodes de la classe [DropDownList] :


Items

collection de type [ListItemCollection] des éléments de la liste déroulante. Les membres de cette collection dont de type [ListItem].


Items.Count

nombre d'éléments de la collection [Items]


Items(i)

élement n° i de la liste - de type [ListItem]


Items.Add

pour ajouter un nouvel élément [ListItem] à la collection [Items]


Items.Clear

pour supprimer tous les éléments de la collection [Items]


Items.RemoveAt(i)

pour supprimer l'élément n° i de la collection [Items]


SelectedItem

1er élément [ListItem] de la collection [Items] ayant sa propriété [Selected] à vrai


SelectedIndex

n° de l'élément [SelectedItem] dans la collection [Items]

Les éléments de la collection [Items] de la classe [DropDownList] sont de type [ListItem]. Chaque élément [ListItem] donne naissance à une balise HTML <option> :

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

Nous décrivons certaines propriétés et méthodes de la classe [ListItem] :


ListItem(String texte, String value)

constructeur - crée un élément [ListItem] avec les propriétés [texte] et [value]. Un élément ListItem(T,V) donnera naissance à la balise HTML <option value="V">T</option>. La classe [ListITem] permet donc de décrire les éléments d'une liste HTML


Selected

booléen. Si vrai, l'option correspondante de la liste HTML aura l'attribut [selected="selected"]. Cet attribut indique au navigateur que l'élément correspondant doit appararaître sélectionné dans la liste HTML


Text

le texte T de l'option HTML <option value="V" [selected="selected"]>T</option>


Value

la valeur V de l'attribut [Value] de l'option HTML <option value="V" [selected="selected"]>T</option>

Nous avons assez d'informations pour écrire dans la procédure [Page_Load] de la page, le code de remplissage de la liste déroulante [DropDownList1] :

    Sub Page_Load(sender As Object, e As EventArgs)
        ' on remplit le combo si c'est la 1ère fois qu'on est appelé
        if not IsPostBack then
          dim valeurs() as String = {"1","2","3","4"}
            dim textes() as  String = {"un","deux","trois","quatre"}

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

Le composant [DropDownList1] ainsi initialisé, sa traduction HTML sera la suivante :

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

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

</select>

Nous savons que la procédure [Page_Load] est exécutée à chaque exécution de la page [form6.aspx]. Celle-ci est appelée la 1ère fois par un GET, puis par un POST à chaque fois que l'utilisateur sélectionne un nouvel élément dans la liste déroulante. Faut-il exécuter à chaque fois le code de remplissage de cette liste dans [Page_Load] ? La réponse dépend de l'attribut [EnableViewState] du composant [DropDownList1]. Si cet attribut est à vrai, on sait que l'état du composant [DropDownList1] va être conservé au fil des requêtes dans le champ caché [__VIEWSTATE]. Cet état comprend deux choses :

  • la liste de toutes les valeurs de la liste déroulante
  • la valeur de l'élément sélectionné dans cette liste

Il paraît alors tentant de mettre la propriété [EnableViewState] du composant [DropDownList1] à [true] afin de ne pas avoir à recalculer les valeurs à mettre dans la liste. Le problème alors, est que la procédure [Page_Load] étant exécutée à chaque fois que la page [form6.aspx] est demandée, ces valeurs vont être quand même calculées. L'objet [Page] dont [form6.aspx] est une instance possède un attribut [IsPostBack] à valeur booléenne. Si cet attribut est à vrai, cela signifie que la page est appelée par un POST. A faux, il signifie que la page a été appelée par un GET. Dans notre système d'aller-retour client-serveur, le client demande toujours la même page [form6.aspx] au serveur. La première fois, il la demande avec un GET, les fois suivantes avec un POST. On en conclut que la propriété [IsPostBack] peut nous servir à détecter le premier appel GET du client. On ne génère les valeurs de la liste déroulante que lors de ce premier appel. Aux requêtes suivantes, ces valeurs seront générées par le mécanisme du [VIEWSTATE]. Dans d'autres situations, le contenu d'une liste peut varier d'une requête à l'autre et doit alors être recalculé à chacune d'elles. Dans ce cas, on mettra l'attribut [EnableViewState] de celle-ci à [false] pour éviter un double calcul inutile du contenu de la liste sauf si on a besoin de connaître les éléments sélectionnés précédemment dans la liste car cette information est conservée dans le [VIEWSTATE].

L'attribut [AutoPostBack] de la liste [DropDownList1] a été mis à vrai. Cela signifie que le navigateur postera le formulaire dès qu'il détectera l'événement "changement de l'élément sélectionné" dans la liste déroulante. Le serveur à son tour détectera grâce au [VIEWSTATE] et aux valeurs postées, que l'élément sélectionné dans le composant [DropDownList1] a changé. Il déclenchera alors l'événement [SelectedIndexChanged] sur ce composant. Nous le traiterons avec la procédure suivante :

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

Lorsque cette procédure s'exécute, l'objet [DropDownList1] a retrouvé ses éléments de type [ListItem] grâce au [VIEWSTATE]. Par ailleurs, l'un d'eux de type [ListItem] a son attribut [Selected] à vrai, celui dont la valeur a été postée par le navigateur. On a accès à cet élément de plusieurs façons :


DropDownList1.SelectedItem

est le 1er élément [ListItem] de la liste ayant son attribut [Selected] à vrai


DropDownList1.SelectedItem.Text

correspond à la partie [texte] de la balise HTML de l'élément <option value="...">texte</option> sélectionné par l'utilisateur


DropDownList1.SelectedItem.Value

correspond à la partie [value] de la balise HTML de l'élément <option value="...">texte</option> sélectionné par l'utilisateur


DropdDownList1.SelectedIndex

n° dans la collection [DropDownList1.Items] du 1er élément [ListItem] ayant son attribut [Selected] à vrai

Le code final de [form6.aspx] est le suivant :

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

    Sub Page_Load(sender As Object, e As EventArgs)
        ' on remplit le combo si c'est la 1ère fois qu'on est appelé
        if not IsPostBack then
          dim valeurs() as String = {"1","2","3","4"}
            dim textes() as  String = {"un","deux","trois","quatre"}

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

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

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

7.8. Le composant ListBox

La balise <asp:ListBox> permet d'insérer une liste dans le code de présentation d'une page. Nous créons [form7.aspx] pour obtenir la présentation suivante :

Image

nom

type

propriétés

rôle

1


txtSaisie

TextBox

EnableViewState=false

champ de saisie

2


btnAjouter

Button

 

bouton [submit] qui transfère dans Liste 1, le contenu de txtSaisie s'il est non vide.

3


ListBox1

ListBox

EnableViewState=true

SelectionMode=Single

liste de valeurs à sélection simple

4


ListBox2

ListBox

EnableViewState=true

SelectionMode=Multiple

liste de valeurs à sélection multiple

5


btn1vers2

Button

 

bouton [submit] qui transfère dans [liste 2] l'élément sélectionné de [liste 1]

6


btn2vers1

Button

 

bouton [submit] qui transfère dans [liste 1] les éléments sélectionnés de [liste 2]

Le code de présentation généré est le suivant :

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

La classe [ListBox] est dérivée de la même classe [ListControl] que la classe [DropDownList] étudiée précédemment. On y retrouve toutes les propriétés et méthodes vues pour [DropDownList] car elles appartenaient en fait à [ListControl]. Une propriété nouvelle apparaît :


SelectionMode

fixe le mode de sélection de la liste HTML <select> qui sera générée à partir du composant. Si SelectionMode=Single, alors un seul élément pourra être sélectionné. Si SelectionMode=Multiple, plusieurs éléments pourront être sélectionnés. Pour cela, l'attribut [multiple="multiple"] sera généré dans la balise <select> de la liste HTML.

Traitons les événements. Le clic sur le bouton [Ajouter] sera traité par la procédure [btnAjouter_Click] suivante :

    Sub btnAjouter_Click(sender As Object, e As EventArgs)
      ' ajout dans la liste 1
      dim texte as string=txtSaisie.text.trim
      if texte<> "" then ListBox1.Items.Add(New ListItem(texte))
      ' raz txtSaisie
      txtSaisie.text=""
    End Sub

Si le texte saisi dans [txtSaisie] n'est pas la chaîne vide ou blanche, un nouvel élément est ajouté à la liste [ListBox1]. Nous savons que nous devons ajouter un élément de type [ListItem]. Précédemment, nous avions utilisé le constructeur [ListItem(T as String, V as String)] pour faire un travail analogue. Un tel élément donne naissance à la balise HTML [<option value="V">T</option>]. Ici, nous utilisons le constructeur [ListItem(T as String)] qui donne naissance à la balise HTML [<option value="T">T</option>], c.a.d. que le texte [T] de l'option est pris pour former la valeur de l'option. Une fois le contenu de [txtSaisie] ajouté à la liste [ListBox1], le champ [txTSaisie] est vidé.

Les clics sur les boutons [Effacer] seront traités par les procédures suivantes :

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

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

Les clics sur les boutons de transfert entre listes sont eux traités par les procédures suivantes :

    Sub btn1vers2_Click(sender As Object, e As EventArgs)
      ' transfert de l'élment sélectionné dans liste 1 vers liste 2
      transfert(ListBox1,ListBox2)
    End Sub

    Sub btn2vers1_Click(sender As Object, e As EventArgs)
      ' transfert de l'élment sélectionné dans liste 2 vers liste 1
      transfert(ListBox2,ListBox1)
    End Sub

    sub transfert(l1 as listbox, l2 as listbox)
      ' transferts des éléments sélectionnés dans l1 vers l2
      ' qq chose à faire ?
      if l1.selectedindex=-1 then return
      dim i as integer
      ' on commence par la fin
      for i=l1.items.count-1 to 0 step -1
        ' sélectionné ?
        if l1.items(i).selected then
            ' plus sélectionné
            l1.items(i).selected=false
          ' transfert dans l2
          l2.items.add(l1.items(i))
          ' suppression dans l1
          l1.items.removeAt(i)
        end if
      next
    end sub

Parce que les deux boutons font le même travail, transférer des éléments d'une liste à une autre, on peut se ramener à une unique procédure de transfert ayant deux paramètres :

  • l1 de type [ListBox] qui est la liste source
  • l2 de type [ListBox] qui est la liste destination

Tout d'abord, on vérifie s'il y a bien au moins un élément sélectionné dans la liste l1, sinon il n'y a rien à faire. Pour cela, on examine la propriété [l1.selectedindex] qui représente le n° du premier élément sélectionné dans la liste. S'il n'y en a aucun, sa valeur est -1. S'il y a au moins un élément sélectionné dans l1, on opère le transfert vers l2. Pour cela, on parcourt toute la liste des éléments de l1 et on regarde pour chacun d'eux si son attribut [selected] est à vrai. Si oui, son attribut [selected] est mis à [false], puis il est recopié dans la liste l2 enfin il est supprimé de la liste l1. Cette suppression provoque une renumérotation des éléments de la liste l1. C'est pourquoi la liste des éléments de l1 est-elle parcourue à l'envers. Si on la parcourt à l'endroit et qu'on supprime l'élément n° 10, l'élément n° 11 devient le n° 10 et le n° 12 l'élément n° 11. Après avoir traité l'élément n° 10, notre boucle à l'endroit va traiter l'élément n° 11 qui d'après ce qu'on vient d'expliquer est l'ancien n° 12. Celui qui avait le n° 11 et qui porte maintenant le n° 10 nous échappe. En parcourant les éléments de la liste l1 dans l'autre sens, on évite ce problème.

7.9. Les composants CheckBox, RadioButton

Les balises <asp:RadioButton> et <asp:CheckBox> permettent d'insérer respectivement un bouton radio et une case à cocher dans le code de présentation d'une page. Nous créons une page [form8.aspx] pour obtenir la présentation suivante :

Image

nom

type

propriétés

rôle

1


RadioButton1
RadioButton2
RadioButton3

RadioButton

RadioButton1.Checked=true

RadioButton1.Text=1

RadioButton2.Checked=false

RadioButton2.Text=2

RadioButton3.Checked=false

RadioButton3.Text=3

pour les 3 boutons : GroupName=radio

boutons radio

2


CheckBoxA
CheckBoxB
CheckBoxC

CheckBox

Checked=false pour tous

CheckBoxA.Text=A

CheckBoxB.Text=B CheckBoxC.Text=C

cases à cocher

3


btnEnvoyer

Button

 

bouton [submit]

4


lstInfos

ListBox

 

liste d'informations

Pour que le navigateur traite les trois boutons radio comme des boutons exclusifs les uns des autres, il faut les rassembler dans un groupe de boutons radio. Cela se fait au moyen de l'attribut [GroupName] de la classe [RadioButton]. L'état de la page n'a pas à être maintenu dans cette application. Aussi mettons-nous l'attribut [EnableViewState="false"] à la page. Le code de présentation est le suivant :

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

Il nous faut écrire la procédure [btnEnvoyer_Click] pour traiter l'événement [Click] sur ce bouton. L'état d'un bouton radio ou d'une case à cocher est donné par son attribut [Checked], booléen à vrai si la case est cochée à faux sinon. Il nous suffit donc d'écrire dans la liste [lstInfos] la valeur de l'attribut [Checked] des six boutons radio et cases à cocher. Comme il n'y a pas de difficulté particulière à cela, nous innovons un peu :

<script runat="server">

    Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
      ' on place des informations dans le listbox
      for each c as control in FindControl("frmControls").controls
          ' le contrôle est-il dérivé de CheckBox
          if TypeOf(c) is CheckBox then
              lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
          end if
      next
    End Sub

</script>

La page peut être vue comme une structure arborescente de contrôles. Dans notre exemple notre page comporte des textes et des contrôles serveur. Les textes sont vus comme un contrôle particulier appelé [LiteralControl]. Le moindre texte donne naissance à ce contrôle même un suite d'espaces entre deux contrôles. Chaque contrôle a un attribut ID qui l'identifie. C'est l'attribut ID qui appararaît dans les balises :

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

Si nous ignorons les contrôles [LiteralControl], la page étudiée a les contrôles suivants :

- [HtmlForm] qui est le formulaire [ID=frmControls]. Celui-ci est à son tour un conteneur de contrôles. Il contient les contrôles suivants :

-- [ID=RadioButton1] de type [RadioButton]

-- [ID=RadioButton2] de type [RadioButton]

-- [ID=RadioButton3] de type [RadioButton]

-- [ID=CheckBoxA] de type [CheckBox]

-- [ID= CheckBoxA] de type [CheckBox]

-- [ID= CheckBoxA] de type [CheckBox]

-- [ID=btnEnvoyer] de type [Button]

Un contrôle possède les propriété suivantes :


[Control].Controls

rend la collection des contrôles enfants de [Control] s'il en a


[Control].FindControl(ID)

rend le contrôle identifié par ID se trouvant à la racine de l'arborescence des contrôles enfants de [Control]. Dans l'exemple ci-dessus : Page.FindControl("frmControls") désigne le conteneur [HtmlForm]. Pour atteindre le bouton radio [RadioButton1], il faudra écrire

Page.FindControl("frmControls").FindControl("RadioButton1")


[Control].ID

identifiant de [Control]

Revenons au code de la procédure [btnEnvoyer_Click] :

    Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
      ' on place des informations dans le listbox
      for each c as control in FindControl("frmControls").controls
          ' le contrôle est-il dérivé de CheckBox ?
          if TypeOf(c) is CheckBox then
              lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
          end if
      next
    End Sub

On veut afficher l'état des boutons radio et des cases à cocher qui se trouvent dans le formulaire. Nous parcourons tous les contrôles de celui-ci. Si le contrôle courant est d'un type dérivé de [CheckBox] nous affichons sa propriété [Checked]. La classe [RadioButton] étant dérivée de la classe [CheckBox], le test est valable pour les deux types de contrôles. La copie d'écran présentée plus haut montre un exemple d'exécution.

7.10. Les composants CheckBoxList, RadioButtonList

Parfois, on souhaite amener l'utilisateur à faire des choix entre des valeurs qu'on ne connaît pas au moment de la conception de la page. Ces choix proviennent d'un fichier de configuration, d'une base de données, ... et ne sont connus qu'au moment de l'exécution. Il existe des solutions à ce problème et nous les avons rencontrées. La liste à sélection simple convient bien lorsque l'utilisateur ne peut faire qu'un choix et la liste à sélection multiple lorsqu'il peut en faire plusieurs. D'un point de vue esthétique et si le nombre de choix n'est pas important, on peut vouloir utiliser des boutons radio à la place de la liste à sélection simple ou des cases à cocher à la place de la liste à sélection multiple. C'est possible avec les composants [CheckBoxList] et [RadioButtonList].

Les classes [CheckBoxList] et [RadioButtonList] sont dérivées de la même classe [ListControl] que les classes [DropDownList] et [ListBox] étudiées précédemment. Aussi va-t-on retrouver certaines des propriétés et méthodes vues pour ces classes, celles qui appartenaient en fait à [ListControl].


Items

collection de type [ListItemCollection] des éléments de la liste déroulante. Les membres de cette collection dont de type [ListItem].


Items.Count

nombre d'éléments de la collection [Items]


Items(i)

élement n° i de la liste - de type [ListItem]


Items.Add

pour ajouter un nouvel élément [ListItem] à la collection [Items]


Items.Clear

pour supprimer tous les éléments de la collection [Items]


Items.RemoveAt(i)

pour supprimer l'élément n° i de la collection [Items]


SelectedItem

1er élément [ListItem] de la collection [Items] ayant sa propriété [Selected] à vrai


SelectedIndex

n° de l'élément précédent dans la collection [Items]

Certaines propriétés sont spécifiques aux classes [CheckBoxList] et [RadioButtonList] :


RepeatDirection

[horizontal] ou [vertical] pour des listes horizontales ou verticales.

Les éléments de la collection [Items] sont de type [ListItem]. Chaque élément [ListItem] donnera naissance à une balise différente selon qu'on affaire à un objet [CheckBoxList] ou [RadioButtonList] :

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

Ou

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

Nous décrivons certaines propriétés et méthodes de la classe [ListItem] :


ListItem(String texte, String value)

constructeur - crée un élément [ListItem] avec les propriétés texte et value. Un élément ListItem(T,V) donnera naissance à la balise HTML <input type="checkbox" value="V">T ou <input type="radio" value="V">T selon les cas.


Selected

booléen. Si vrai, l'option correspondante de la liste HTML aura l'attribut [selected="selected"]. Cet attribut indique au navigateur que l'élément correspondant doit appararaître sélectionné dans la liste HTML


Text

le texte T de l'option HTML <input type=".." value="V" [selected="selected"]>T


Value

la valeur de l'attribut Value de l'option HTML <input type=".." value="V" [selected="selected"]>T

Nous nous proposons de construire la page [form8b.aspx] suivante :

Image

nom

type

propriétés

rôle

1


RadioButtonList1

RadioButtonList

EnableViewState=true

RepeatDirection=horizontal

liste de boutons radio

2


CheckBoxList1

CheckBoxList

EnableViewState=true

RepeatDirection=horizontal

liste de cases à cocher

3


btnEnvoyer

Button

 

bouton [submit] qui affiche dans [4] la liste des éléments sélectionnés dans les deux listes

4


lstInfos

ListBox

EnableViewState=false

liste de valeurs

Le code de présentation de la page est le suivant :

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

Le code de contrôle est le suivant :

<script runat="server">

    Sub Page_Load(sender As Object, e As EventArgs) handles MyBase.Load
        ' on remplit les listes si c'est la 1ère fois qu'on est appelé
        if not IsPostBack then
            ' textes pour la liste RadioButton
          dim textesRadio() as String = {"1","2","3","4"}
          ' textes pour la liste CheckBox
          dim textesCheckBox() as  String = {"un","deux","trois","quatre"}
                ' remplissage liste radio
            dim i as integer
            for i=0 to textesRadio.length-1
                RadioButtonList1.Items.Add(new ListItem(textesRadio(i)))
            next
            ' sélection élément n° 1
            RadioButtonList1.SelectedIndex=1
            ' remplissage liste checkbox
            for i=0 to textesCheckBox.length-1
                CheckBoxList1.Items.Add(new ListItem(textesCheckBox(i)))
            next
        end if
    end sub


    Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
      ' on place des informations dans le listbox lstinfos
      affiche(RadioButtonList1)
      affiche(CheckBoxList1)
    End Sub

    sub affiche(l1 as ListControl)
      ' affiche les valeurs des éléments sélectionnés de l1
      ' qq chose à faire ?
      if l1.selectedindex=-1 then return
      dim i as integer
      ' on commence par la fin
      for i= 0 to l1.items.count-1
        ' sélectionné ?
        if l1.items(i).selected then
          lstInfos.Items.Add("["+TypeName(l1)+"] ["+l1.items(i).text+"] sélectionné")
        end if
      next
    end sub

</script>

Dans la procédure [Page_Load] qui est exécutée à chaque exécution de la page, les deux listes sont initialisées. Pour éviter qu'elles le soient à chaque fois, on utilise la propriété [IsPostBack] pour ne le faire que la première fois. Les fois suivantes, les listes seront régénérées automatiquement par le mécanisme du [VIEWSTATE]. Une fois la page affichée, l'utilisateur coche certaines cases et utilise le bouton [Envoyer]. Les valeurs du formulaire sont alors postées au formulaire lui-même. Après exécution de [Page_Load], c'est la procédure [btnEnvoyer_Click] qui est exécutée. Celle-ci fait appel à la procédure [affiche] pour remplir la liste [lstInfos]. Celle-ci reçoit comme paramètre un objet de type [ListControl] ce qui permet de lui envoyer indifféremment un objet [RadioButtonList] ou un objet [CheckBoxList], classes dérivées de [ListControl]. La liste [lstInfos] peut avoir son attribut [EnableViewState] à [false] puisque son état n'a pas à être maintenu entre les différentes requêtes.

7.11. Les composants Panel,LinkButton

La balise <asp:panel> permet d'insérer un conteneur de contrôles dans une page. L'intérêt du conteneur est que certaines de ses propriétés s'appliquent à tous les contrôles qu'il contient. Il en est ainsi de sa propriété [Visible]. Cette propriété existe pour tout contrôle serveur. Si un conteneur a la propriété [Visible=false], chacun de ses contrôles sera contrôlé par sa propre propriété [Visible]. S'il a la propriété [Visible=false], alors le conteneur et tout ce qu'il contient n'est pas affiché. Cela peut être plus simple que de gérer la propriété [Visible] de chacun des contrôles du conteneur.

La balise <asp:LinkButton> permet d'insérer un lien dans le code de présentation d'une page. Elle a un rôle analogue au bouton [Button]. Elle provoque en effet un POST côté client grâce à une fonction Javascript qui lui est associée. Nous créons une page [form9.aspx] pour obtenir la présentation suivante :

Image

nom

type

propriétés

rôle

1


Panel1

Panel

EnableViewState=true

conteneur de contrôles

2


ListBox1

ListBox

EnableViewState=true

une liste de trois valeurs

3


lnkCacher

LinkButton

EnableViewState=false

lien pour cacher le conteneur

Lorsque le conteneur est caché, un nouveau lien apparaît :

Image

nom

type

propriétés

rôle

4


lnkVoir

LinkButton

EnableViewState=false

lien pour afficher le conteneur

Le code de présentation de la page est le suivant :

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

Remarquons que ce code initialise la liste [ListBox1] avec trois valeurs. Les gestionnaires des événements [Clic] sur les deux liens sont les suivants :

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

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

    Sub lnkVoir_Click(sender As Object, e As EventArgs)
        ' affiche le conteneur 1
        panel1.Visible=true
        ' changt des liens
        lnkVoir.visible=false
        lnkCacher.visible=true
    End Sub

    Sub lnkCacher_Click(sender As Object, e As EventArgs)
        ' cache le conteneur 1
        panel1.Visible=false
        ' changt des liens
        lnkVoir.visible=true
        lnkCacher.visible=false
    End Sub
</script>

Nous utiliserons la procédure [Page_Load] pour initialiser le formulaire. Nous le ferons lors de la première requête (IsPostBack=false) :

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

    Sub Page_Load(sender As Object, e As EventArgs)
        ' la 1ère fois
        if not IsPostBack then
            ' on montre le conteneur
            lnkVoir_Click(nothing,nothing)
        end if
    end sub
.....
</script>

7.12. Pour poursuivre...

Les paragraphes précédents ont présenté un certain nombre de composants serveur. Seules quelques propriétés de ceux-ci ont été présentées à chaque fois. Pour approfondir l'étude de ces composants, le lecteur pourra procéder de diverses façons :

  • découvrir les propriétés d'un composant avec un IDE tel que WebMatrix. Celui-ci expose en effet les principales propriétés des composants utilisés dans un formulaire
  • consulter la documentation de .NET pour découvrir l'intégralité des classes correspondant à chacun des composants serveur. C'est la méthode à préférer pour une maîtrise totale du composant. On y découvrira l'arborescence des classes qui mènent aux composants, les propriétés, méthodes, constructeurs, événements de chacune d'elles. De plus la documentation fournit parfois des exemples.

Dans ce chapitre, nous avons utilisé la technique [WebMatrix] du tout en un, c.a.d. qu'on a mis le code de présentation et le code de contrôle d'une page dans le même fichier. De façon générale, nous ne préconisons pas cette méthode mais celle dite du [codebehind] utilisée auparavant qui met ces deux codes dans deux fichiers séparés. On rappelle que l'intérêt de cette séparation réside dans le fait que le code de contrôle peut être compilé sans avoir à exécuter l'application web. Par ailleurs, nos exemples et nous l'avons expliqué dès le début du chapitre, avaient un profil bien particulier : ils étaient constitués d'une unique page qui était un formulaire que s'échangeaient le client et le serveur dans des cycles successifs de demande-réponse, la première requête client étant un GET, et les suivantes des POST.

7.13. Composants serveur et contrôleur d'application

Dans les chapitres précédents, nous avons construit plusieurs applications web. Elles ont toutes été construites selon l'architecture MVC (Modèle-Vue-Contrôleur) qui découpe l'aplication en blocs bien distincts et en facilite la maintenance. Nous construisions alors nos interfaces utilisateur avec des balises HTML standard. Avec ce qui vient d'être vu, il est naturel de vouloir maintenant utiliser des composants serveur. Reprenons un problème déjà étudié longuement qui était celui du calcul d'un impôt. Son architecture MVC était la suivante :

Image

L'application a deux vues : [formulaire.aspx] et [erreurs.aspx]. La vue [formulaire.aspx] est présentée lorsque l'url [main.aspx] est demandée la première fois :

Image

L'utilisateur renseigne le formulaire :

Image

et utilise le bouton [Calculer] pour obtenir la réponse suivante :

Image

Dans une application MVC, toute requête doit passer par le contrôleur, ici [main.aspx]. Cela signifie que lorsque le formulaire [formulaire.aspx] a été rempli a été rempli par l'utilisateur, il doit être posté à [main.aspx] et non pas à [formulaire.aspx]. Ceci n'est tout simplement pas possible si nous construisons l'interface utilisateur [formulaire.aspx] avec des composants serveurs ASP. Pour s'en rendre compte, construisons un formulaire [formtest.aspx] avec un composant <asp:button> :

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

On notera l'attribut [action="main.aspx"] de la balise <form..>. Exécutons cette application. La page de présentation ne présente qu'un bouton :

Image

Regardons le code HTML envoyé par le serveur :

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

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

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

On voit que le POST du formulaire a pour cible le formulaire lui-même [action="formtest.aspx"] alors que nous avions écrit dans [formtest.aspx] la balise HTML serveur :

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

L'attribut [runat="server"] de la balise <form> nous est imposé par l'utilisation des composants serveur. Une erreur de compilation se produit si nous ne mettons pas cet attribut. Lorsqu'on le met, alors l'attribut [action] de la balise <form> est ignorée. Le serveur génère toujours un attribut [action] qui pointe sur le formulaire lui-même. On en déduit que dans une application MVC, on ne peut utiliser de formulaires construits avec la balise <form ... runat="server">. Or cette balise est indispensable à tous les composants ASP serveur récupérant des saisies de l'utilisateur. Autant dire qu'on ne peut utiliser de formulaires ASP serveur dans une application MVC. C'est une grande découverte. En effet l'un des points forts du marketing d'ASP.NET est qu'on peut construire une application web comme une application windows. C'est vrai si notre application ne respecte pas l'architecture MVC mais plus vrai sinon. Or l'architecture MVC paraît une notion fondamentale du développement web actuel qu'il semble difficile d'ignorer.

Il est possible d'utiliser l'architecture MVC conjointement avec des formulaires à composants ASP pour des applications ayant peu de vues différentes grâce à l'artifice suivant :

  • l'application consiste en une page unique qui fait office de contrôleur
  • les vues se traduisent dans cette page par des conteneurs différents, un conteneur par vue. Pour afficher une vue, on rend visible son conteneur et on cache les autres

C'est une solution élégante que nous mettons en oeuvre maintenant dans quelques exemples

7.14. Exemples d'applications MVC avec composants serveur ASP

7.14.1. Exemple 1

Nous mettons en oeuvre dans ce premier exemple les composants serveur que nous avons présentés. La page [form10.aspx] sera la suivante :

Image Image

La copie d'écran gauche ci-dessus est le formulaire tel qu'il est présenté au client. Celui-ci le remplit et le valide par [Envoyer]. Le serveur lui renvoie une vue lui présentant une liste des valeurs saisies (copie d'écran droite). Un lien permet à l'utilisateur de retourner au formulaire. Il retrouve celui-ci tel qu'il l'a validé. Le code de présentation de [form10.aspx] est le suivant :

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

La page a deux conteneurs, un pour chaque vue : [panelform] pour la vue formulaire, [panelinfo] pour la vue informations. La liste des composants du conteneur [panelForm] est la suivante :

nom

type

propriétés

rôle


panelform

Panel

EnableViewState=true

vue formulaire


rdOui
rdNon

RadioButton

EnableViewState=true

GroupName=rdmarie

boutons radio


chk1
chk2
chk3

CheckBox

EnableViewState=true

cases à cocher


txtSaisie

TextBox

EnableViewState=true

champ de saisie


txtMdp

TextBox

EnableViewState=true

champ de saisie protégée


txtArea

TextBox

EnableViewState=true

boîte de saisie multilignes


cmbValeurs

DropDownList

EnableViewState=true

liste déroulante


lstSimple

ListBox

EnableViewState=true

SelectionMode=Single

liste à sélection simple


btnRazSimple

Button

EnableViewState=false

désélectionne tous les éléments de lstSimple


lstMultiple

ListBox

EnableViewState=true

SelectionMode=Multiple

liste à sélection multiple


btnRazMultiple

Button

EnableViewState=false

désélectionne tous les éléments de lstMultiple


lblSecret

Label

EnableViewState=true

Visible=false

champ caché


btnEffacer

HTML standard

 

affiche une alerte


btnEnvoyer

Button

EnableViewState=false

bouton [submit] du formulaire


btnReset

HTML standard

 

bouton [reset] du formulaire

Le rôle du [VIEWSTATE] pour les composants est ici important. Tous les composants sauf les boutons doivent avoir la propriété [EnableViewState=true]. Pour en comprendre la raison, il faut se rappeler le fonctionnement de l'application. Supposons que le champ [txtSaisie] ait la propriété [EnableViewState=false] :

  1. le client demande une première fois la page [form10.aspx]. Il obtient la vue formulaire
  2. il le remplit et le poste avec le bouton [Envoyer]. Les champs de saisie sont alors postés et le serveur affecte aux composants serveur la valeur postée ou leur état [VIEWSTATE] s'ils en avaient un. Ainsi le champ [txtSaisie] se voit affectée la valeur saisie par l'utilisateur. Aussi pour cette étape, son état [VIEWSTATE] ne sert à rien. Comme résultat de l'opération, la vue [informations] est envoyée, en fait toujours la page [form10.aspx] mais avec un conteneur affiché différent.
  3. l'utilisateur consulte cette nouvelle vue et utilise le lien [Retour au formulaire] pour revenir à celui-ci. Un POST est alors fait vers [form10.aspx]. Il n'y a alors au plus qu'une valeur postée : la valeur sélectionnée dans la liste d'informations par l'utilisateur, information non exploitée par la suite. Toujours est-il qu'il n'y a a pas de champ [txtSaisie] posté.
  4. le serveur reçoit le POST et affecte aux composants serveur la valeur postée ou leur état [VIEWSTATE] s'ils en avaient un. Ici, [txtNom] n'a pas de valeur postée. Si son attribut [EnableViewState] est à [false], la chaîne vide lui sera affectée. Comme on souhaite qu'il ait la valeur saisie par l'utilisateur, il faut qu'il ait la propriété [EnableViewState=true].

Le conteneur [panelinfo] a les contrôles suivants :

nom

type

propriétés

rôle


panelinfo

Panel

EnableViewState=false

vue informations


lstInfos

ListBox

EnableViewState=false

liste d'informations récapitulant les valeurs saisies par l'utilisateur


LinkButton1

LinkButton

EnableViewState=false

lien de retour vers le formulaire

Au moment des tests, si on regarde le code HTML généré par le code de présentation ci-dessus, on pourra être étonné du code généré pour le champ caché [lblSecret] :

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

Le composant [lblSecret] n'est pas traduit en code HTML car il a la propriété [Visible=false]. Cependant parce qu'il a la propriété [EnableViewState=true], sa valeur sera néanmoins conservée dans le champ caché [__VIEWSATE]. Aussi sera-t-on capable de la récupérer comme le montreront les tests.

Il nous reste à écrire les gestionnaires d'événements. Dans [Page_Load] nous initialiserons le formulaire :

Sub page_Load(sender As Object, e As EventArgs)
         ' la 1ère fois, on initialise les éléments
         ' les fois suivantes, ceux-ci retrouvent leurs valeurs par le VIEWSTATE
         if IsPostBack then return
         ' init formulaire
         ' panelinfo pas affiché
         panelinfo.visible=false
         ' paneform affiché
         panelform.visible=true
         ' boutons radio
         rdNon.Checked=true
         ' cases à cocher
         chk2.Checked=true
         ' champ de saisie
         txtSaisie.Text="qqs mots"
         ' champ mot de passe
         txtMdp.Text="ceciestsecret"
         ' boîte de saisie
         txtArea.Text="ligne"+ControlChars.CrLf+"ligne2"+ControlChars.CrLf
         ' combo
         dim i as integer
         for i=1 to 4
             cmbValeurs.Items.Add(new ListItem("choix"+i.ToString,i.ToString))
         next
         cmbValeurs.SelectedIndex=1
         ' liste à sélection simple
         for i=1 to 7
             lstSimple.Items.Add(new ListItem("simple"+i.ToString,i.ToString))
         next
         lstSimple.SelectedIndex=0
         ' liste à sélection multiple
         for i=1 to 10
             lstMultiple.Items.Add(new ListItem("multiple"+i.ToString,i.ToString))
         next
         lstMultiple.Items(0).Selected=true
         lstMultiple.Items(2).Selected=true
         ' champ caché
         lblSecret.Text="secret"
End Sub

Le clic sur les boutons [lstRazSimple] et [lstMultiple] :

Sub btnRazSimple_Click(sender As Object, e As EventArgs)
    ' raz liste simple
    lstSimple.SelectedIndex=-1
End Sub

Sub razMultiple_Click(sender As Object, e As EventArgs)
    ' raz liste multiple
    lstMultiple.SelectedIndex=-1
End Sub

Le clic sur le bouton [Envoyer] :

Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
    ' le panel info est rendu visible et le panel formulaire est caché
  panelinfo.Visible=true
  panelform.visible=false
  ' on récupère les valeurs postées et on les met dans lstInfos
  ' boutons radio
  dim info as string="état marital : "+iif(rdoui.checked,"marié"," non marié")
  affiche(info)
  ' cases à cocher
  info=" cases cochées : "+iif(chk1.checked,"1 oui","1 non")+","+ _
      iif(chk2.checked,"2 oui","2 non")+","+iif(chk3.checked,"3 oui","3 non")
  affiche(info)
  ' champ de saisie
  affiche("champ de saisie : " + txtSaisie.Text.Trim)
  ' mot de passe
  affiche("mot de passe : " + txtMdp.Text.Trim)
  ' boîte de saisie
  dim lignes() as String
  lignes=new Regex("\r\n").Split(txtArea.Text.Trim)
  dim i as integer
  for i=0 to lignes.length-1
      lignes(i)="["+lignes(i).Trim+"]"
  next
  affiche("Boîte de saisie : " + String.Join(",",lignes))
  ' combo
  affiche("éléments sélectionnés dans combo : "+selection(cmbValeurs))
  ' liste simple
  affiche("éléments sélectionnés dans liste simple : "+selection(lstSimple))
  ' liste multiple
  affiche("éléments sélectionnés dans liste multiple : "+selection(lstMultiple))
  ' champ caché
  affiche ("Champ caché : " + lblSecret.Text)
End Sub

sub affiche(msg as String)
    ' affiche msg dans lstInfos
    lstInfos.Items.Add(msg)
end sub

       function selection(liste as ListControl) as string
           ' on parcourt les éléments de liste
           ' pour trouver ceux qui sont sélectionnés
           dim i as integer
           dim info as string=""
           for i=0 to liste.Items.Count-1
               if liste.Items(i).Selected then info+="[" + liste.Items(i).Text + "]"
           next
           return info
       end function

Enfin le clic sur le lien [Retour vers le formulaire ] :

Sub LinkButton1_Click(sender As Object, e As EventArgs)
    ' on affiche le formulaire et on cache le panel info
    panelform.visible=true
    panelinfo.visible=false
End Sub

7.14.2. Exemple 2

Nous reprenons ici, une application déjà traitée avec des formulaires HTML standard. L'application permet de faire des simulations de calculs d'impôt. Elle s'appuie sur une classe [impot] qu'on ne rappellera pas ici. Cette classe a besoin de données qu'elle trouve dans une source de données OLEDB. Pour l'exemple, ce sera une source ACCESS.

7.14.2.1. La structure MVC de l'application

La structure MVC de l'application est la suivante :

Image

Les trois vues seront incorporées dans le code de présentation du contrôleur [main.aspx] sous la forme de conteneurs. Aussi cette application a-t-elle une unique page [main.aspx].

7.14.2.2. Les vues de l'application web

La vue [formulaire] est le formulaire de saisie des informations permettant le calcul de l'impôt d'un utilisateur :

Image

L'utilisateur remplit le formulaire :

Image

Il utilise le bouton [Envoyer] pour demander le calcul de son impôt. Il obtient la vue [simulations] suivante :

Image

Il retourne au formulaire par le lien ci-dessus. Il le retrouve dans l'état où il l'a saisi. Il peut faire des erreurs de saisie :

Image

Celles-ci lui sont signalées par la vue [erreurs] :

Image

Il retourne au formulaire par le lien ci-dessus. Il le retrouve dans l'état où il l'a saisi. Il peut faire de nouvelles simulations :

Image

Il obtient alors la vue [simulations] avec une simulation de plus :

Image

Enfin, si la source de données n'est pas disponible, cela est signalé à l'utilisateur dans la vue [erreurs] :

Image

7.14.2.3. Le code de présentation de l'application

Rappelons que la page [main.aspx] rassemble toutes les vues. C'et un unique formulaire avec trois conteneurs :

  • [panelform] pour la vue [formulaire]
  • [panelerreurs] pour la vue [erreurs]
  • [panelsimulations] pour la vue [simulations]

Nous renouons avec la séparation code de de présentation et code de contrôle dans deux fichiers séparés. Le premier sera dans [main.aspx] et le second dans [main.aspx.vb]. Le code de [main.aspx] est le suivant :


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

Nous avons délimité les trois conteneurs. On notera qu'ils sont tous dans la balise <form runat="server">. C'est obligatoire, car si on veut pouvoir profiter des avantages des composants serveur, ceux-ci doivent être placés dans une telle balise. Le point important à saisir est qu'on a ici un unique formulaire que vont s'échanger le client et le serveur web. On est donc bien dans la configuration utilisée dans tout ce chapitre sur les composants serveur. Détaillons les composants de chaque conteneur :

Conteneur [panelform] :

nom

type

propriétés

rôle


panelform

Panel

EnableViewState=true

vue formulaire


rdOui
rdNon

RadioButton

EnableViewState=true

GroupName=rdmarie

boutons radio


txtEnfants

TextBox

EnableViewState=true

nombre d'enfants


txtSalaire

TextBox

EnableViewState=true

salaire annuel


btnCalculer

Button

 

bouton [submit] du formulaire - lance le calcul de l'impôt


btnEffacer

Button

 

bouton [submit] du formulaire - vide le formulaire

Conteneur [panelerreurs] :

nom

type

propriétés

rôle


panelerreurs

Panel

EnableViewState=true

vue erreurs


lnkForm1

LinkButton

EnableViewState=true

lien vers le formulaire


erreursHTML

Literal

 

code HTML de la liste d'erreurs

Conteneur [panelsimulations] :

nom

type

propriétés

rôle


panelsimulations

Panel

EnableViewState=true

vue simulations


lnkForm2

LinkButton

EnableViewState=true

lien vers le formulaire


simulationsHTML

Literal

 

code HTML de la liste des simulations dans une table HTML

7.14.2.4. Le code de contrôle de l'application

Le code de contrôle de l'application se trouve réparti dans les fichiers [global.asax.vb] et [main.aspx.vb]. Le fichier [global.asax] est défini comme suit :

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

Le fichier [global.asax.vb] est le suivant :


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

Public Class Global
    Inherits System.Web.HttpApplication

   Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
       ' on crée un objet impot
       Dim objImpot As impot
       Try
           objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
           ' on met l'objet dans l'application
           Application("objImpot") = objImpot
           ' pas d'erreur
           Application("erreur") = False
       Catch ex As Exception
           'il y a eu erreur, on le note dans l'application
           Application("erreur") = True
           Application("message") = ex.Message
       End Try
   End Sub

   Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
       ' début de session - on crée une liste de simulations vides
       Session.Item("simulations") = New ArrayList
   End Sub
End Class

Lorsque l'application démarre (1ère requête faite à l'application), la procédure [Application_Start] est exécutée. Elle cherche à créer un objet de type [impot] prenant ses données dans une source OLEDB. Le lecteur est invité à lire le chapitre 5 où cette classe a été définie, s'il l'a oubliée. La construction de l'objet [impot] peut échouer si la source de données n'est pas disponible. Dans ce cas, l'erreur est mémorisée dans l'application afin que toutes les requêtes ultérieures sachent que celle-ci n'a pu s'initialiser correctement. Si la construction se passe bien, l'objet [impot] créé est lui aussi mémorisé dans l'application. Il sera utilisé par toutes les requêtes de calcul d'impôt. Lorsqu'un client fait sa première requête, une session est créée pour lui par la procédure [Application_Start]. Cette session est destinée à mémoriser les différentes simulations de calcul d'impôt qu'il va faire. Celles-ci seront mémorisées dans un objet [ArrayList] associé à la clé de session "simulations". Lorsque la session démarre, cette clé est associée à un objet [ArrayList] vide. Les informations nécessaires à l'application sont placées dans son fichier de configuration [wenConfig] :


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

La clé [chaineConnexion] désigne la chaîne de connexion à la source OLEDB. L'autre partie du code de contrôle se trouve dans [main.aspx.vb] :


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

Public Class main
   Inherits System.Web.UI.Page

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

   ' variables locales

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

   Private Sub afficheFormulaire()
...
   End Sub

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

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

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

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

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

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

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

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

   Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
       ' tout d'abord, on regarde dans quel état est l'application
       If CType(Application("erreur"), Boolean) Then
           ' l'application n'a pas pu s'initialiser
           ' on affiche la vue erreurs
           Dim erreurs As New ArrayList
           erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
           afficheErreurs(erreurs, "")
           Exit Sub
       End If
       ' pas d'erreurs - à la 1ère requête, on présente le formulaire
       If Not IsPostBack Then afficheFormulaire()
   End Sub

Rappelons que lorsque la procédure [Page_Load] s'exécute sur un POST client, tous les composants du formulaire ont une valeur : soit la valeur postée par le client s'il y en a une, soit la valeur précédente du composant grâce au [VIEWSTATE]. Dans ce formulaire, tous les composants ont la propriété [EnableViewState=true]. Avant de commencer à traiter la requête, nous nous assurons que l'application a pu s'initialiser correctement. Si ce n'est pas le cas, on affiche la vue [erreurs] avec la procédure [afficheErreurs]. Si c'est la première requête (IsPostBack=false), nous faisons afficher la vue [formulaire] avec [afficheFormulaire].

La procédure affichant la vue [erreurs] est la suivante :


   Private Sub afficheErreurs(ByRef erreurs As ArrayList, ByRef lien As String)
       ' affiche le conteneur erreurs
       panelerreurs.Visible = True
       Dim i As Integer
       erreursHTML.Text = ""
       For i = 0 To erreurs.Count - 1
           erreursHTML.Text += "<li>" + erreurs(i).ToString + "</li>" + ControlChars.CrLf
       Next
       lnkForm1.Text = lien
       ' les autres conteneurs sont cachés
       panelform.Visible = False
       panelsimulations.Visible = False
   End Sub

La procédure a deux paramètres :

  • une liste de messages d'erreurs dans [erreurs]
  • un texte de lien dans [lien]

Le code HTML à générer pour la liste d'erreurs est placé dans le litéral [erreursHTML]. Le texte du lien est lui placé dans la propriété [Text] de l'objet [LinkButton] de la vue.

La procédure affichant la vue [formulaire] est la suivante :


   Private Sub afficheFormulaire()
       ' affiche le formulaire
       panelform.Visible = True
       ' les autres conteneurs sont cachés
       panelerreurs.Visible = False
       panelsimulations.Visible = False
   End Sub

Cette procédure se contente de rendre visible le conteneur [panelform]. Les composants sont affichés avec leur valeur postée ou précédente (VIEWSTATE).

Lorsque l'utilisateur clique sur le bouton [Calculer] de la vue [formulaire], un POST vers [main.aspx] est fait. La procédure [Page_Load] est exécutée puis la procédure [btnCalculer_Click] :


   Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
       ' on vérifie la validité des données saisies
       Dim erreurs As ArrayList = checkData()
       ' s'il y a des erreurs, on le signale
       If erreurs.Count <> 0 Then
           ' on affiche la page d'erreurs
           afficheErreurs(erreurs, "Retour au formulaire")
           Exit Sub
       End If
       ' pas d'erreurs - on calcule l'impôt
       Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
       rdOui.Checked, CType(txtEnfants.Text, Integer), CType(txtSalaire.Text, Long))
       ' on rajoute le résultat aux simulations existantes
       Dim simulation() As String = New String() {CType(IIf(rdOui.Checked, "oui", "non"), String), _
        txtEnfants.Text.Trim, txtSalaire.Text.Trim, impot.ToString}
       ' on rajoute le résultat aux simulations existantes
       Dim simulations As ArrayList = CType(Session.Item("simulations"), ArrayList)
       simulations.Add(simulation)
       ' on met les simulations dans la session et le contexte
       Session.Item("simulations") = simulations
       ' on affiche la page de résultat
       afficheSimulations(simulations, "Retour au formulaire")
   End Sub

La procédure commence par vérifier la validité des champs du formulaire avec la procédure [checkData] qui rend une liste [ArrayList] de messages d'erreurs. Si la liste n'est pas vide, alors la vue [erreurs] est affichée et la procédure est terminée. Si les données saisies sont valides, alors le montant de l'impôt est calculé grâce à l'objet de type [impot] qui avait été stocké dans l'application au démarrage de celle-ci. Cette nouvelle simulation est ajoutée à la liste des simulations déjà faites et stockée dans la session.

La fonction [CheckData] vérifie la validité des données. Elle rend une liste [ArrayList] de messages d'erreurs, vide si les données sont valides :


   Private Function checkData() As ArrayList
       ' au départ pas d'erreurs
       Dim erreurs As New ArrayList
       ' nbre d'enfants
       Try
           Dim nbEnfants As Integer = CType(txtEnfants.Text, Integer)
           If nbEnfants < 0 Then Throw New Exception
       Catch
           erreurs.Add("Le nombre d'enfants est incorrect")
       End Try
       ' salaire
       Try
           Dim salaire As Long = CType(txtSalaire.Text, Long)
           If salaire < 0 Then Throw New Exception
       Catch
           erreurs.Add("Le salaire annuel est incorrect")
       End Try
       ' on rend la liste des erreurs
       Return erreurs
   End Function

Enfin la vue [simulations] est affichée par la procédure [afficheSimulations] suivante :


   Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
       ' affiche la vue simulations
       panelsimulations.Visible = True
       ' les autres conteneurs sont cachés
       panelerreurs.Visible = False
       panelform.Visible = False
       ' contenu de la vue simulations
       ' chaque simulation est un tableau de 4 éléments string
       Dim simulation() As String
       Dim i, j As Integer
       simulationsHTML.Text = ""
       For i = 0 To simulations.Count - 1
           simulation = CType(simulations(i), String())
           simulationsHTML.Text += "<tr>"
           For j = 0 To simulation.Length - 1
               simulationsHTML.Text += "<td>" + simulation(j) + "</td>"
           Next
           simulationsHTML.Text += "</tr>" + ControlChars.CrLf
       Next
       ' lien
       lnkForm2.Text = lien
   End Sub

La procédure a deux paramètres :

  • une liste de simulations dans [simulations]
  • un texte de lien dans [lien]

Le code HTML à générer pour la liste de simulations est placé dans le litéral [simulationsHTML]. Le texte du lien est lui placé dans la propriété [Text] de l'objet [LinkButton] de la vue.

Lorsque l'utilisateur clique sur le bouton [Effacer] de la vue [formulaire], c'est la procédure [btnEffacer_click] qui s'exécute (toujours après [Page_Load]) :


   Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
       ' affiche le formulaire vide
       razForm()
       afficheFormulaire()
   End Sub

   Private Sub razForm()
       ' vide le formulaire
       rdOui.Checked = False
       rdNon.Checked = True
       txtEnfants.Text = ""
       txtSalaire.Text = ""
   End Sub

Le code ci-dessus est suffisamment simple pour ne pas avoir à être commenté. Il nous reste à gérer le clic sur les liens des vues [erreurs] et [simulations] :


   Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
       ' affiche le formulaire
       afficheFormulaire()
   End Sub

   Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
       ' affiche le formulaire
       afficheFormulaire()
   End Sub

Les deux procédures se contentent de faire afficher la vue [formulaire]. Nous savons que les champs de celle-ci vont obtenir une valeur qui est soit la valeur postée pour eux soit leur valeur précédente. Comme ici, le POST du client n'envoie aucune valeur pour les champs du formulaire, ceux-ci vont retrouver leur valeur précédente. Le formulaire est donc affiché avec les valeurs saisies par l'utilisateur. Nous nous souvenons que, dans la version sans composants serveur, nous avions fait ce travail de restauration nous-mêmes.

7.14.2.5. Tests

Tous les fichiers nécessaires à l'application sont placés dans un dossier <application-path> :

Image

Le dossier [bin] contient la dll contenant les classes [impot], [impotsData], [impotsOLEDB] nécessaires à l'application :

Image

Le lecteur pourra, s'il le souhaite, relire le chapitre 5 où est expliquée la façon de créer le fichier [impot.dll] ci-dessus. Ceci fait, le serveur Cassini est lancé avec les paramètres (<application-path>,/impots5). On demande l'url [http://impots5/main.aspx] avec un navigateur :

Image

Si on renomme le fichier ACCESS [impots.mdb] en [impots1.mdb], on aura la page suivante :

Image

7.14.3. Exemple 3

Nous avons montré sur ces deux exemples qu'il était possible de construire des applications web respectant l'architecture MVC avec des composants serveur. Le dernier exemple montre que la solution avec composants serveur est plus simple que la solution utilisant des balises HTML standard. Nos deux exemples n'avaient qu'une page avec plusieurs vues au sein de la même page. On peut avoir une architecture MVC avec plusieurs formulaires ASP serveur tant que le fait que ceux-ci se postent à eux-mêmes les valeurs du formulaire ne pose pas de problème. C'est très souvent le cas des applications avec menu. Prenons l'exemple suivant :

Image

Nous avons réuni dans une même page des liens vers les applications que nous avons écrites jusqu'à maintenant. Ce type d'application se prête bien à une architecture MVC. Simplement il n'y a plus un mais plusieurs contrôleurs.

Image

Le contrôleur [main.aspx] joue le rôle de contrôleur principal. C'est lui qui est appelé par les liens de la page d'accueil de l'application. Il va pouvoir faire des opérations communes à toutes les actions possibles puis il fera exécuter l'action particulière associée au lien utilisé. Il passera alors la main à l'un des contrôleurs secondaires, celui chargé de faire exécuter l'action. A partir de ce moment, les échanges se font entre le client et ce contrôleur particulier. On ne passe plus par le contrôleur principal [main.aspx]. On n'est donc plus dans le cadre MVC avec contrôleur unique qui filtre toutes les requêtes. Chacun des contrôleurs ci-dessus peut présenter plusieurs vues avec le mécanisme des conteneurs au sein d'une unique page que nous avons présenté.

Le fait qu'on n'ait plus un contrôleur unique qui choisit les vues à envoyer au client présente des inconvénients. Prenons l'exemple de la gestion des erreurs. Chacune des actions exposées par l'application peut être amenée à afficher une vue d'erreurs. Chaque contrôleur [applix.aspx] va avoir sa propre vue [erreurs] parce que celle-ci est simplement un conteneur particulier de la page du contrôleur. Il n'y a pas moyen d'avoir une vue [erreurs] unique qui serait utilisée par toutes les applications individuelles. En effet, une telle vue présente généralement un lien de retour vers le formulaire erroné et celui-ci doit être restauré dans l'état où il a été validé afin de permettre à l'utlisateur de corriger ses erreurs. Cette restauration se fait par le mécanisme du [VIEWSTATE] qui ne fonctionne pas entre contrôleurs différents. Si les applications sont développées par des personnes différentes, on risque d'avoir des pages d'erreurs à l'aspect différent selon l'action choisie par l'utilisateur entachant l'homogénéité de l'application globale. Nous verrons un peu plus tard, qu'ASP.NET offre une solution à ce problème particulier de vue à partager. Celle-ci peut faire l'objet d'un nouveau composant serveur que nous construisons nous-mêmes. Il suffit d'utiliser ce composant dans les différentes applications pour assurer l'homgénéité de l'application globale. Plus difficile à gérer, le problème de l'ordre des actions. Lorsque toutes les requêtes passent par un contrôleur unique, celui-ci peut vérifier que l'action demandée est compatible avec la précédente. Ce code de contrôle est à un unique endroit. Ici, il va falloir le répartir sur les différents contrôleurs compliquant la maintenance de l'application globale.

Revenons à notre application ci-dessus. La page d'entrée de celle-ci est une page HTML classique [accueil.htm] :

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

Cette page d'accueil est un ensemble de trois cadres appelés banner, contents et main :     

Image

La page [bandeau.htm] placée dans le cadre [banner] est la suivante :

Image

Son code HTML est le suivant :


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

La page [options.htm] est placée dans le bandeau [contents]. C'est un ensemble de liens :

Image

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

Les différents liens pointent tous sur le contrôleur principal [main.aspx] avec un paramètre [action] indiquant l'action à faire. On demande à ce que la cible des liens soit affichée dans le cadre [main] (target="main").

La première page affichée dans le cadre [main] est [main.htm] :


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

Le contrôleur principal [main.aspx, main.aspx.vb] est le suivant :

[main.aspx]

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

[main.aspx.vb]

Public Class main

Inherits System.Web.UI.Page

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

    ' on récupère l'action à faire

    Dim action As String

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

        action = "label"

    Else

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

    End If

    ' on exécute l'action

    Select Case action

        Case "label"

            Server.Transfer("form2.aspx")

        Case "button"

            Server.Transfer("form3.aspx")

        Case "textbox1"

            Server.Transfer("form4.aspx")

        Case "textbox2"

            Server.Transfer("form5.aspx")

        Case "dropdownlist"

            Server.Transfer("form6.aspx")

        Case "listbox"

            Server.Transfer("form7.aspx")

        Case "casesacocher"

            Server.Transfer("form8.aspx")

        Case "listecasesacocher"

            Server.Transfer("form8b.aspx")

        Case "panel"

            Server.Transfer("form9.aspx")

        Case Else

            Server.Transfer("form2.aspx")

    End Select

End Sub

End Class

Notre contrôleur est simple. Selon la valeur du paramètre [action], il transfère le traitement de la requête a la page adéquate. Il n'apporte aucune plus-value vis à vis d'une page HTML avec des liens. Néanmoins, il suffirait d'ajouter une page d'authentification pour voir son intérêt. Si l'utilisateur devait s'authentifier (login, mot de passe) pour avoir accès aux applications, le contrôleur [main.aspx] serait un bon endroit pour vérifier que cette authentification a été faite.