7. Componenti server ASP - 1
7.1. Introduction
In questo capitolo descriviamo la tecnologia raccomandata in ASP.NET per la creazione dell’interfaccia utente. Sappiamo che l’elaborazione di una pagina .aspx da parte del server web prevede due fasi ben distinte:
- innanzitutto viene eseguito il controller della pagina. Questo è costituito da codice situato o nella pagina .aspx stessa (soluzione WebMatrix) o in un file separato (soluzione Visual Studio.NET).
- Successivamente viene eseguito il codice di presentazione della pagina .aspx, che viene trasformato in codice HTML inviato al client.

ASP.NET offre tre librerie di tag per scrivere il codice di presentazione della pagina:
- i tag HTML classici. È ciò che abbiamo utilizzato finora.
- i tag HTML server
- i tag webforms
Indipendentemente dalla libreria di tag utilizzata, il ruolo del controller di pagina rimane invariato. Esso deve calcolare il valore dei parametri dinamici presenti nel codice di presentazione. Finora questi parametri dinamici erano semplici: si trattava di oggetti di tipo [String]. Pertanto, se nel codice di presentazione è presente un tag <%=nome%>:
- il controller di pagina dichiara una variabile [nom] di tipo [String] e ne calcola il valore
- quando il controller di pagina ha terminato il proprio lavoro e il codice di presentazione viene eseguito per generare la risposta HTML, il tag <%=nom%> viene sostituito dal valore calcolato dal codice di controllo
È noto che la separazione tra controller e presentazione è arbitraria e che è possibile mescolare codice del controller e codice di presentazione nella stessa pagina. Abbiamo spiegato perché questo metodo è sconsigliato e continueremo a rispettare la separazione tra controller e presentazione.
I tag [HTML serveur] e [WebForms] consentono di inserire nel codice di presentazione oggetti più complessi rispetto al semplice oggetto [String]. Ciò a volte presenta un reale interesse. Prendiamo l’esempio di un modulo con un elenco. Quest’ultimo deve essere presentato al cliente con un codice HTML simile al seguente:
<select name="uneListe" size="3">
<option value="opt1">option1</option>
<option value="opt2">option2</option>
<option value="opt3" selected>option3</option>
</select>
Il contenuto dell'elenco e l'opzione da selezionare sono elementi dinamici e devono quindi essere generati dal controller della pagina. Abbiamo già riscontrato questo problema e lo abbiamo risolto inserendo nel codice di presentazione il tag
Questo tag verrà sostituito dal valore [String] della variabile [uneListeHTML]. Questo valore, calcolato dal controller, dovrà corrispondere al codice HTML presente nell’elenco, ovvero c.a.d. "<select name=..>...</select>". Non è particolarmente difficile da realizzare e sembra una soluzione elegante che evita di inserire codice di generazione direttamente nella parte di presentazione della pagina. In questo caso, infatti, ci sarebbe un ciclo con dei test da inserire in essa, che risulterebbe così fortemente “inquinata”. Tuttavia, questo metodo presenta uno svantaggio. La separazione tra controllo e presentazione di una pagina serve anche a delimitare due ambiti di competenza:
- quello dello sviluppatore .NET, che si occupa del controller della pagina
- quello del grafico, che si occupa della parte di presentazione della pagina
Qui si vede che la generazione del codice HTML dell’elenco è stata trasferita nel controller. Il grafico potrebbe voler intervenire su questo codice HTML per modificare l’aspetto “visivo” dell’elenco. Sarà costretto a lavorare nella parte [contrôleur] e quindi a uscire dal proprio ambito di competenza, con il rischio di introdurre involontariamente errori nel codice.
Le librerie di tag server risolvono questo problema. Esse offrono un oggetto che rappresenta un elenco HTML. Pertanto, la libreria [WebForms] offre il seguente tag:
Questo tag rappresenta un oggetto di tipo [ListBox] che può essere gestito dal controller di pagina. Questo oggetto dispone di proprietà per rappresentare le diverse opzioni dell’elenco HTML e indicare l’opzione selezionata. Il controller di pagina assegnerà quindi i valori appropriati a queste proprietà. Quando verrà eseguita la parte di presentazione, il tag
verrà sostituito dal codice HTML che rappresenta l’oggetto [uneListe], c.a.d. il codice "<select ..>...</select>". Per il momento, non vi è alcuna differenza fondamentale rispetto al metodo precedente, se non un approccio alla programmazione orientato agli oggetti, il che è interessante. Torniamo al nostro grafico che ha bisogno di modificare l’aspetto dell’elenco. I tag server dispongono di attributi di stile (BackColor, Bordercolor, BorderWidth, ...) che consentono di definire l’aspetto visivo dell’oggetto HTML corrispondente. Pertanto, si potrà scrivere:
<asp:ListBox id="ListBox1" runat="server" BackColor="#ffff99"></asp:ListBox></P>
Il vantaggio è che il grafico opera direttamente nel codice di presentazione per apportare queste modifiche. Si tratta di un indubbio vantaggio rispetto al metodo precedente. Le librerie di tag server facilitano quindi la costruzione della parte di presentazione delle pagine, quella che nei capitoli precedenti abbiamo definito «interfaccia utente». Lo scopo di questo capitolo è proprio quello di presentarle. Vedremo che talvolta propongono oggetti complessi come calendari o tabelle collegate a fonti di dati. Sono estensibili, c.a.d, in quanto l’utente può creare la propria libreria di tag. Può così realizzare un tag che generi un banner in una pagina. Tutte le pagine che utilizzano questo tag avranno quindi lo stesso banner.
La generazione HTML effettuata per un tag si adatta al tipo di browser del client. Quando il browser effettua una richiesta al server web, invia tra le sue intestazioni HTTP un’intestazione [User-Agent: xx] in cui [xx] identifica il client. Ecco un esempio:
Grazie a queste informazioni, il server web può conoscere le capacità del client, in particolare il tipo di codice HTML che è in grado di gestire. Nel corso del tempo, infatti, sono state rilasciate diverse versioni del linguaggio HTML. I browser più recenti supportano le versioni più recenti del linguaggio, cosa che i browser più datati non sono in grado di fare. A seconda dell’intestazione HTTP [User-Agent:] inviata dal client, il server invierà una versione HTML che il client sarà in grado di comprendere. Si tratta di un'idea interessante e utile, poiché lo sviluppatore non deve preoccuparsi del tipo di browser utilizzato dal cliente della propria applicazione.
Infine, versioni avanzate di IDE come Visual Studio.NET, WebMatrix, ... consentono una progettazione "in stile Windows" dell'interfaccia web. Questi strumenti, pur non essendo indispensabili, forniscono tuttavia un aiuto decisivo allo sviluppatore. Lo sviluppatore progetta l’interfaccia web utilizzando componenti grafici che inserisce nell’interfaccia stessa. Ha accesso diretto alle proprietà di ciascuno dei componenti dell’interfaccia, che può quindi configurare a proprio piacimento. Queste proprietà verranno tradotte nel codice HTML di presentazione dell’interfaccia in attributi del tag <asp:> del componente. Il vantaggio per lo sviluppatore è che non deve ricordare né l’elenco né la sintassi degli attributi di ciascun tag. Si tratta di un vantaggio apprezzabile quando non si conoscono perfettamente le librerie di tag server offerte da ASP.NET. Una volta acquisita questa sintassi, alcuni sviluppatori potrebbero preferire scrivere direttamente i tag nel codice di presentazione della pagina senza passare attraverso la fase di progettazione grafica. In tal caso, un IDE non è più necessario. È sufficiente un semplice editor di testo. A seconda del metodo di lavoro, l’attenzione si concentra quindi sui componenti (utilizzo di un IDE) o sui tag (utilizzo di un editor di testo). Esiste un'equivalenza tra questi due termini. Il componente è l’oggetto che verrà gestito dal codice di controllo della pagina. Il IDE ci consente di accedere alle sue proprietà in fase di progettazione. I valori assegnati a queste proprietà vengono immediatamente tradotti negli attributi del tag del componente nel codice di presentazione. In fase di esecuzione, il codice di controllo della pagina manipolerà il componente e assegnerà valori ad alcune delle sue proprietà. Il codice di presentazione genererà a sua volta il codice HTML del componente utilizzando, da un lato, gli attributi impostati in fase di progettazione per il tag server corrispondente e, dall’altro, i valori delle proprietà del componente calcolati dal codice di controllo.
7.2. Il contesto di esecuzione degli esempi
Illustreremo la progettazione di interfacce web basate su componenti server con programmi il cui contesto di esecuzione sarà, nella maggior parte dei casi, il seguente:
- l’applicazione web sarà composta da un’unica pagina P contenente un modulo F,
- il client effettuerà la sua prima richiesta direttamente a questa pagina P. Ciò consisterà nel richiedere l’URL della pagina P tramite un browser. Si tratterà quindi di una richiesta GET che verrà effettuata su tale URL P. Il server fornirà la pagina P e quindi il modulo F in essa contenuto,
- l’utente lo compilerà e lo invierà, c.a.d, ovvero effettuerà un’azione che costringerà il browser a inviare il modulo F al server. L'operazione POST del browser sarà sempre diretta alla pagina P. Il server fornirà nuovamente la pagina P con il modulo F, il cui contenuto potrebbe essere stato modificato dall'azione dell'utente.
- Successivamente, riprenderanno le fasi 2 e 3.
Si tratta di un processo di esecuzione ben particolare, al di fuori del quale alcuni concetti esposti di seguito non funzionano più. Non ci troviamo più in un contesto di architettura MVC in cui un’applicazione multipagina è controllata da una pagina specifica che abbiamo chiamato “controller dell’applicazione”. In questo tipo di architettura, il POST dei moduli ha come destinazione il controller e non i moduli stessi. Vedremo però che la creazione di un modulo con componenti server implica che tale modulo venga inviato a se stesso.
7.3. Il componente Label
7.3.1. Utilizzo
Il tag <asp:label> consente di inserire un testo dinamico nel codice di presentazione di una pagina. Non fa quindi nulla di più del tag <%=variable%> utilizzato finora. L’analisi di questo primo tag ci consentirà di scoprire il meccanismo dei tag server. Creiamo una pagina con una parte di controllo [form1.aspx.vb] e una parte di presentazione [form1.aspx]. Si tratta di visualizzare l’ora:

Questo argomento è già stato trattato nel capitolo 2 e il lettore è invitato a consultarlo se desidera sapere come è stato affrontato. Il codice di presentazione [form1.aspx] è il seguente:
<%@ page src="form1.aspx.vb" inherits="form1" AutoEventWireup="false" %>
<HTML>
<HEAD>
<title>Webforms</title>
</HEAD>
<body>
<asp:Label Runat="server" ID="lblHeure" />
</body>
</HTML>
Introduciamo il tag <asp:label>. Nelle librerie dei tag, l'attributo [runat="server"] è obbligatorio. L'attributo ID identifica il componente. Il controller dovrà fare riferimento a esso utilizzando questo identificatore. Il codice del controller [form1.aspx.vb] è il seguente:
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
' salva la richiesta corrente in request.txt nella cartella della pagina
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
' inserisce l'ora in lblHeure
lblHeure.Text = "Il est " + Date.Now.ToString("T")
End Sub
End Class
Il controller deve assegnare un valore all'oggetto [lblHeure] di tipo [System.Web.UI.WebControls.Label]. Tutti gli oggetti visualizzati dai tag <asp:> appartengono allo spazio dei nomi [System.Web.UI.WebControls]. È quindi possibile importare sistematicamente questo spazio dei nomi:
Imports System.Web.UI.WebControls
L'oggetto [Label] presenta diverse proprietà, tra cui la proprietà [Text] che rappresenta il testo che verrà visualizzato dal tag <asp:label> corrispondente. In questo caso, inseriamo in questa proprietà l'ora corrente. Lo facciamo nella procedura [Form_Load] del controller, che viene eseguita sistematicamente. Nella procedura [Form_Init], anch’essa sempre eseguita ma prima della procedura [Form_Load], memorizziamo la richiesta del client in un file [request.txt] nella cartella dell’applicazione. Avremo modo di esaminare questo file per comprendere alcuni aspetti del funzionamento delle pagine che utilizzano i tag server.
L’oggetto [Label] possiede numerose proprietà, metodi ed eventi. Il lettore è invitato a consultare la documentazione relativa alla classe [Label] per scoprirli. Di seguito, procederemo sempre in questo modo. Per ogni tag, presenteremo solo le poche proprietà di cui abbiamo bisogno.
7.3.2. I test
Inseriamo i file (form1.aspx, form1.aspx.vb) in una cartella <application-path> e avviamo Cassini con i parametri (<application-path>,/form1). Quindi richiediamo l’URL [http://localhost/form1/form1.aspx]. Otteniamo il seguente risultato:

Il codice HTML ricevuto dal browser è il seguente:
<HTML>
<HEAD>
<title>Webforms</title>
</HEAD>
<body>
<span id="lblHeure">Il est 19:39:37</span>
</body>
</HTML>
Si nota che il tag server
<asp:Label Runat="server" ID="lblHeure" />
è stato trasformato nel seguente codice HTML:
È la proprietà [Text] dell’oggetto [lblHeure] che è stata inserita tra i tag <span> e </span>. La richiesta effettuata dal client e memorizzata in [request.txt] è la seguente:
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
Tutto nella norma.
7.3.3. Creazione dell’applicazione con WebMatrix
Abbiamo creato manualmente il codice di presentazione della pagina [form1.aspx]. Questo metodo è utilizzabile se si conoscono i tag. In tal caso, è sufficiente un semplice editor di testo per creare l’interfaccia utente. All’inizio, è spesso necessario uno strumento di progettazione grafica abbinato alla generazione automatica del codice, poiché non si conosce la sintassi dei tag necessari. Ora realizziamo la stessa applicazione con lo strumento WebMatrix. Una volta avviato WebMatrix, selezioniamo l’opzione [File/New File]:

Creiamo una pagina ASP.NET denominata [form2.aspx]. Una volta confermato la procedura guidata precedente, otteniamo la finestra di progettazione della pagina [form2.aspx]:


Si ricorda che WebMatrix inserisce il codice di controllo della pagina e quello di presentazione in un unico file, in questo caso [form2.aspx]. La scheda [All] mostra il contenuto di questo file di testo. Si può notare fin da subito che non è vuoto:

Lo svantaggio di questo tipo di strumenti è che spesso generano codice superfluo. È il caso in questione, dove WebMatrix ha generato un tag <form> HTML, mentre non si intende creare alcun modulo... Inoltre, si può notare che il documento non contiene il tag <title>. Risolviamo immediatamente questi due problemi per ottenere la seguente nuova versione:

Quello che chiamiamo «codice di controllo» verrà inserito tra i tag <script> e </script>, garantendo così una separazione, almeno visiva, tra i due tipi di codice: controllo e presentazione. Torniamo alla scheda [Design] per disegnare la nostra interfaccia. Un elenco di componenti è disponibile in una finestra degli strumenti a sinistra della finestra di progettazione:

La finestra degli strumenti offre l’accesso a due tipi di componenti:
- i componenti [WebControls], che si traducono in tag <asp:>
- i componenti [HTML Elements] che si traducono in tag HTML classici. Tuttavia, è possibile aggiungere agli attributi di un tag HTML l’attributo [runat="server"]. In questo caso, il tag HTML e i suoi attributi sono accessibili al controller tramite un oggetto le cui proprietà corrispondono a quelle del tag HTML che esso rappresenta. In precedenza, questi tag sono stati definiti tag HTML del server.
Facciamo doppio clic sul componente [Label] dell’elenco dei controlli [WebControls]. Nella scheda [Design] si ottiene il seguente risultato:

Nella scheda [All] il codice è diventato il seguente:
<%@ Page Language="VB" %>
<html>
<head>
<title>webforms</title>
</head>
<body>
<asp:Label id="Label1" runat="server">Label</asp:Label>
</body>
</html>
Si nota innanzitutto che è andato perso il tag <script>. È stato generato un tag <asp:label> con il nome [Label1] e il valore [Label]. Torniamo alla scheda [Design] per modificare questi due valori. Facciamo clic una volta sul componente [Label] per far apparire la finestra delle proprietà di questo componente, in basso a destra:

Si invita il lettore a consultare le proprietà dell’oggetto [Label]. Due di esse ci interessano in questo contesto:
- Text: è il testo che deve visualizzare l’etichetta; inseriamo la stringa vuota (c.a.d. nulla)
- ID: è il suo identificativo; inseriamo lblHeure
La scheda [Design] diventa così:

e il codice di [All] diventa:
<%@ Page Language="VB" %>
<html>
<head>
<title>webforms</title>
</head>
<body>
<asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>
La parte relativa alla presentazione della pagina è terminata. Resta da scrivere il codice di controllo incaricato di inserire l'ora nella proprietà [Text] di [lblHeure]. Aggiungiamo nella scheda [All] il seguente codice:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
' inserisce l'ora in 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>
Si noti che nel codice del controller non viene dichiarato l'oggetto [lblHeure] come era stato fatto in precedenza:
Protected lblHeure As New System.Web.UI.WebControls.Label
Infatti, tutti i componenti server <asp:> della parte di presentazione sono oggetto di una dichiarazione implicita nella parte del codice di controllo. Dichiararli comunque provoca un errore di compilazione, che indica che l’oggetto è già stato dichiarato. Siamo pronti per l’esecuzione. Scegliamo l’opzione [View/Start] o il collegamento [F5]. Cassini viene avviato automaticamente con i seguenti parametri:

Accettiamo questi valori. Il browser predefinito del sistema viene avviato automaticamente per richiedere l’URL [http://localhost/form2.aspx]. Otteniamo il seguente risultato:

In seguito, utilizzeremo principalmente lo strumento WebMatrix per facilitare la creazione e il collaudo dei brevi programmi che scriveremo.
7.4. Il componente Literal
7.4.1. Utilizzo
Il tag <asp:literal> consente di inserire un testo dinamico nel codice di presentazione di una pagina, proprio come il tag <asp:label>. Il suo attributo principale è [Text], che rappresenta il testo che verrà inserito tal quale nel flusso HTML della pagina. Questo tag è sufficiente se non si intende formattare il testo che si desidera inserire nel flusso HTML. Infatti, mentre la classe [Label] consente di applicare la formattazione tramite attributi quali [BorderColor, BorderWidth, Font, ...], la classe [Literal] non presenta nessuno di questi attributi. Il lettore potrà riprendere integralmente l’esempio precedente sostituendo il componente [Label] con un componente [Literal].
7.5. Il componente Button
7.5.1. Utilizzo
Il tag <asp:Button> consente di inserire in un modulo un pulsante di tipo [Submit], che comporta una gestione degli eventi simile a quella che si trova nelle applicazioni Windows. È proprio questo aspetto che vogliamo approfondire in questa sede. Creiamo la seguente pagina [form3.aspx]:

Questa pagina, realizzata con WebMatrix, presenta tre componenti:
n. | nome | tipo | proprietà | ruolo |
1 | Pulsante | text=Pulsante1 | pulsante di invio | |
2 | Pulsante | text=Pulsante2 | pulsante di invio | |
3 | Etichetta | text= | messaggio informativo |
Il codice generato da WebMatrix per questa sezione è il seguente:
<%@ 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>
Troviamo i componenti [Button] e [Label] utilizzati nella progettazione grafica della pagina all'interno dei tag <asp:>. Da notare il tag <form runat="server"> che è stato generato automaticamente. Si tratta di un tag server HTML, c.a.d. Un tag classico HTML rappresentato tuttavia da un oggetto gestibile dal controller. Il codice del tag <form> verrà generato in base al valore che il controller assegnerà a tale oggetto.
Aggiungiamo nella parte del codice relativa al controller la procedura [Page_Init] che gestisce l’evento [Init] della pagina. In essa inseriamo il codice che salva la richiesta del client nel file [request.txt]. Ne avremo bisogno per comprendere il funzionamento dei pulsanti.
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' salva la richiesta corrente in request.txt nella cartella della pagina
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
</script>
Si noti che non abbiamo inserito la clausola [Handles MyBase.Init] dopo la dichiarazione della procedura [Page_Init]. Infatti, l'evento [Init] dell'oggetto [Page] ha un gestore predefinito denominato [Page_Init]. Se si utilizza questo nome di gestore, la clausola [Handles Page.Init] diventa superflua. La sua inclusione, tuttavia, non provoca alcun errore.
7.5.2. Test
Avviamo l’applicazione con WebMatrix tramite [F5]. Otteniamo la seguente pagina:

Il codice HTML ricevuto dal browser è il seguente:
<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>
Da notare quanto segue:
- il tag <form runat="server"> è stato trasformato nel tag HTML
Sono stati impostati due attributi: [method="post"] e [action="form3.aspx"]. È possibile assegnare valori diversi a questi attributi? Cercheremo di chiarire questo punto più avanti. Per ora basti ricordare che il modulo verrà inviato all'URL [form3.aspx]. Sono stati impostati anche altri due attributi: [name, id]. Nella maggior parte dei casi vengono ignorati. Tuttavia, se la pagina contiene codice JavaScript eseguito dal browser, l’attributo [name] del tag <form> risulta utile.
- I tag <asp:button> sono diventati tag HTML dei pulsanti [submit]. Cliccando su uno qualsiasi di questi pulsanti si attiverà quindi un "post" del modulo [_ctl10] all'URL [form3.aspx].
- Il tag <asp:label> è diventato un tag HTML <span>
- È stato generato un campo nascosto [__VIEWSTATE] con un valore strano:
Questo campo rappresenta, in forma codificata, lo stato del modulo inviato al client. Tale stato rappresenta il valore di tutti i suoi componenti. Poiché [__VIEWSTATE] fa parte del modulo, il suo valore verrà inviato insieme al resto al server. Ciò consentirà al server di sapere quale componente del modulo ha cambiato valore e, eventualmente, di prendere delle decisioni. Queste assumeranno la forma di eventi del tipo «il TextBox tale e tale ha cambiato valore».
7.5.3. Le richieste del client
Una volta ottenuta la pagina [form3.aspx] nel browser, richiediamola nuovamente e osserviamo la richiesta che è stata inviata dal browser per ottenerla. Ricordiamo che la nostra applicazione la memorizza nel file [request.txt] nella cartella dell’applicazione:
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
Abbiamo qui un classico GET. Ora clicchiamo sul pulsante [Bouton1] della pagina nel browser. Apparentemente non succede nulla. Eppure sappiamo che il modulo è stato inviato. È il codice HTML della pagina a dircelo. Ciò è confermato dal nuovo contenuto di [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
La prima intestazione HTTP indica chiaramente che il cliente ha effettuato un POST verso l'URL [/form3.aspx]. L'ultima riga mostra i valori inviati:
- il valore del campo nascosto __VIEWSTATE
- il valore del pulsante su cui è stato cliccato
Se clicchiamo su [Bouton2], i valori inviati dal browser sono i seguenti:
Il valore del campo nascosto è sempre lo stesso, ma è stato inviato il valore di [Button2]. Il server può quindi sapere quale pulsante è stato utilizzato. Lo utilizzerà per attivare un evento che potrà essere gestito dalla pagina, una volta che questa sarà stata caricata.
7.5.4. Gestire l’evento Click di un oggetto Button
Ricordiamo il funzionamento di una pagina .aspx. Si tratta di un oggetto derivato dalla classe [Page]. Chiamiamo la classe derivata [unePage]. Quando il server riceve una richiesta per una pagina di questo tipo, viene istanziato un oggetto di tipo [unePage] tramite l’operazione new unePage(...). Successivamente, il server genera due eventi denominati [Init] e [Load] in quest’ordine. L’oggetto [unePage] può gestirli fornendo i gestori di eventi [Page_Init] e [Page_Load]. Successivamente verranno generati altri eventi. Avremo modo di tornare su questo argomento. Se la richiesta del client è un POST, il server genererà l’evento [Click] relativo al pulsante che ha provocato tale POST. Se la classe [unePage] ha previsto un gestore per questo evento, quest’ultimo verrà chiamato. Vediamo questo meccanismo con WebMatrix. Nella scheda [Design] di [form3.aspx], facciamo doppio clic sul pulsante [Bouton1]. Verrà quindi visualizzata automaticamente la scheda [Code], all’interno del corpo di una procedura denominata [Button1_Click]. Per comprendere meglio, passiamo alla scheda [All] e osserviamo l’intero codice. Sono state apportate le seguenti modifiche:
<%@ 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>
È stato aggiunto un nuovo attributo [onclick="Button1_Click"] al tag <asp:Button> di [Button1]. Questo attributo indica la procedura incaricata di gestire l'evento [Click] sull'oggetto [Button1], in questo caso la procedura [Button1_Click]. Non ci resta che scriverla:
Sub Button1_Click(sender As Object, e As EventArgs)
' clic sul pulsante 1
lblInfo.Text="Vous avez cliqué sur [Bouton1]"
End Sub
La procedura inserisce un messaggio informativo nell’etichetta [lblInfo]. Procediamo allo stesso modo per il pulsante [Bouton2] per ottenere la seguente nuova pagina [form3.aspx]:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' salva la richiesta corrente in request.txt nella cartella della pagina
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 sul pulsante 1
lblInfo.Text="Vous avez cliqué sur [Bouton1]"
End Sub
Sub Button2_Click(sender As Object, e As EventArgs)
' clic sul pulsante 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>
Avviamo l'esecuzione tramite [F5] per ottenere la pagina seguente:

Se osserviamo il codice HTML ricevuto, noteremo che non è cambiato rispetto a quello della versione precedente della pagina:
<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>
Se clicchiamo su [Bouton1], otteniamo la seguente risposta:

Il codice HTML ricevuto per questa risposta è il seguente:
<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>
Possiamo notare che il campo nascosto [__VIEWSTATE] ha cambiato valore. Ciò riflette la modifica del valore del componente [lblInfo].
7.5.5. Gli eventi del ciclo di vita di un'applicazione ASP.NET
La documentazione ASP.NET fornisce l’elenco degli eventi generati dal server nel corso del ciclo di vita di un’applicazione:
- alla prima richiesta ricevuta dall’applicazione, verrà generato l’evento [Start] sull’oggetto [HttpApplication] dell’applicazione. Questo evento può essere gestito dalla procedura [Application_Start] contenuta nel file [global.asax] dell'applicazione.
Successivamente si verificherà una serie di eventi che si ripeteranno per ogni richiesta ricevuta:
- se la richiesta non ha inviato un token di sessione, viene avviata una nuova sessione e viene generato un evento [Start] sull'oggetto [Session] associato alla richiesta. Questo evento può essere gestito dalla procedura [Session_Start] contenuta nel file [global.asax] dell’applicazione.
- Il server genera l’evento [BeginRequest] sull’oggetto [HttpApplication]. Esso può essere gestito dalla procedura [Application_BeginRequest] contenuta nel file [global.asax] dell’applicazione.
- Il server carica la pagina richiesta dalla richiesta. Creerà un'istanza dell'oggetto [Page] e genererà due eventi su tale oggetto: [Init] e poi [Load]. Questi due eventi possono essere gestiti dalle procedure [Page_Init] e [Page_Load] della pagina.
- Sulla base dei valori inviati ricevuti, il server genererà altri eventi: [TextChanged] per un componente [TextBox] il cui valore è cambiato, [CheckedChanged] per un pulsante di opzione il cui valore è cambiato, [SelectedIndexChanged] per un elenco il cui elemento selezionato è cambiato, ... Avremo modo di menzionare gli eventi principali per ciascuno dei componenti server che presenteremo. Ogni evento E su un oggetto denominato O può essere gestito da una procedura denominata O_E.
- L’ordine di gestione degli eventi precedenti non è garantito. Pertanto, i gestori non devono formulare alcuna ipotesi su tale ordine. È tuttavia certo che l’evento [Click] del pulsante che ha generato l’evento POST venga elaborato per ultimo.
- Una volta che la pagina è pronta, il server la invierà al client. Prima di farlo, genera l’evento [PreRender], che può essere gestito dalla procedura [Page_PreRender] della pagina.
- Una volta inviata la risposta HTML al cliente, la pagina verrà scaricata dalla memoria. In questa occasione verranno generati due eventi: [Unload] e [Disposed]. La pagina può utilizzare questi eventi per liberare risorse.
L’applicazione può inoltre ricevere eventi al di fuori di una richiesta del cliente:
- l’evento [End] su un oggetto [Session] dell’applicazione si verifica quando una sessione termina. Ciò può avvenire su richiesta esplicita del codice di una pagina oppure perché è stata superata la durata assegnata alla sessione. La procedura [Session_End] del file [global.asax] gestisce questo evento. In genere, in essa vengono liberate le risorse ottenute in [Session_Start].
- L'evento [End] sull'oggetto [HttpApplication] dell'applicazione si verifica quando l'applicazione termina. Ciò avviene in particolare quando si arresta il server Web. La procedura [Application_End] del file [global.asax] gestisce questo evento. In genere, in questa procedura vengono liberate le risorse ottenute in [Application_Start].
È importante tenere presente quanto segue:
- il modello di eventi precedente si basa su scambi client-server HTTP classici. Ciò risulta evidente esaminando le intestazioni HTTP scambiate.
- l’elaborazione degli eventi precedenti avviene sempre lato server. Il clic su un pulsante può ovviamente essere gestito da uno script JavaScript lato server. Ma in tal caso non si tratta di un evento server e ci troviamo di fronte a una tecnologia indipendente da ASP.NET.
In quale momento vengono gestiti gli eventi, sia che vengano gestiti lato server (eventi legati ai componenti server) sia che vengano gestiti lato browser tramite script JavaScript?
Prendiamo l’esempio di un menu a tendina. Quando l’utente modifica l’elemento selezionato al suo interno, l’evento (cambio dell’elemento selezionato) può essere elaborato o meno e, se lo è, può avvenire in momenti diversi.
- Se si desidera elaborarlo immediatamente, ci sono due soluzioni:
- può essere gestito dal browser tramite uno script JavaScript. In questo caso il server non interviene. Affinché ciò sia possibile, la pagina deve poter essere ricostruita utilizzando i valori presenti al suo interno.
- può essere elaborato dal server. A tal fine, esiste un’unica soluzione: il modulo deve essere inviato al server per l’elaborazione. Si tratta quindi dell’operazione [submit]. Vedremo che in questo caso si utilizza un componente server denominato [DropDownList] e si imposta il suo attributo [AutoPostBack] su [true]. Ciò significa che, in caso di modifica dell’elemento selezionato nell’elenco a discesa, il modulo deve essere immediatamente inviato al server. In questo caso, il server genera, per l’oggetto [DropDownList], un codice HTML associato a una funzione JavaScript incaricata di eseguire un [submit] non appena si verifica l’evento “cambio dell’elemento selezionato”. Questo [submit] invierà il modulo al server e al suo interno saranno inseriti dei campi nascosti per indicare che il [post] deriva da un cambiamento di selezione nell’elenco a discesa. Il server genererà quindi l’evento [SelectedIndexChanged] che la pagina potrà gestire.
- Se si desidera elaborarlo ma non immediatamente, si imposta l’attributo [AutoPostBack] del componente server [DropDownList] su [false]. In questo caso, il server genera, per l’oggetto [DropDownList], il codice HTML classico di un elenco <select> senza alcuna funzione JavaScript associata. Non accade quindi nulla quando l’utente modifica la selezione dall’elenco a discesa. Tuttavia, quando l’utente invia il modulo tramite un pulsante, ad esempio [submit], il server sarà in grado di riconoscere che c’è stato un cambiamento nella selezione. Abbiamo infatti visto che il modulo inviato al server conteneva un campo nascosto [__VIEWSTATE] che rappresentava, in forma codificata, lo stato di tutti gli elementi del modulo inviato. Quando il server riceve il nuovo modulo inviato dal client, potrà verificare se l’elemento selezionato nell’elenco a discesa è cambiato o meno. In caso affermativo, genererà l’evento [SelectedIndexChanged] che la pagina potrà quindi gestire. Per distinguere questo meccanismo dal precedente, alcuni autori affermano che l’evento «cambio di selezione» è stato «memorizzato nella cache» quando si è verificato sul browser. Verrà elaborato dal server solo quando il browser gli invierà il modulo, spesso a seguito di un clic su un pulsante [submit].
- Infine, se non si desidera elaborare l’evento, si imposta l’attributo [AutoPostBack] del componente server [DropDownList] su [false] e non si scrive il gestore del relativo evento [SelectedIndexChanged].
Una volta compreso il meccanismo di gestione degli eventi, lo sviluppatore non progetterà un’applicazione web come un’applicazione Windows. Infatti, mentre in un'applicazione Windows una modifica della selezione in una casella combinata può essere utilizzata per cambiare immediatamente l'aspetto del modulo in cui essa si trova, in un'applicazione web si tenderà a esitare maggiormente a gestire immediatamente tale evento se ciò comporta un "post" del modulo al server, e quindi uno scambio di dati tra client e server. Per questo motivo, la proprietà [AutoPostBack] dei componenti server è impostata di default su [false]. Inoltre, il meccanismo [AutoPostBack], che si basa su script JavaScript generati automaticamente dal server web nel modulo inviato al client, può essere utilizzato solo se si è certi che il browser del client abbia autorizzato l’esecuzione degli script JavaScript sul proprio browser. I moduli sono quindi spesso strutturati nel modo seguente:
- i componenti server del modulo hanno la proprietà impostata su valori compresi tra [AutoPostBack] e [false]
- il modulo presenta uno o più pulsanti incaricati di eseguire il [POST] del modulo
- nel codice del controller della pagina si scrivono i gestori solo degli eventi che si desidera gestire, il più delle volte l’evento [Click] su uno dei pulsanti.
7.6. Il componente TextBox
7.6.1. Utilizzo
Il tag <asp:TextBox> consente di inserire un campo di immissione nel codice di presentazione di una pagina. Creiamo una pagina [form4.aspx] per ottenere la seguente presentazione:

4321Questa pagina, realizzata con WebMatrix, presenta i seguenti componenti:
n. | nome | tipo | proprietà | ruolo |
1 | TextBox | AutoPostback=true Text= | campo di immissione | |
2 | TextBox | AutoPostback=false Text= | campo di immissione | |
3 | Etichetta | text= | messaggio informativo sul contenuto di [TextBox1] | |
3 | Etichetta | text= | messaggio informativo sul contenuto di [TextBox2] |
Il codice generato da WebMatrix per questa parte è il seguente:
<%@ 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>
Nella scheda [Design], fare doppio clic sul componente [TextBox1]. Viene quindi generato lo scheletro del gestore dell'evento [TextChanged] di questo oggetto (scheda [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'attributo [OnTextChanged="TextBox1_TextChanged"] è stato aggiunto al tag <asp:TextBox id="TextBox1"> per indicare il gestore dell'evento [TextChanged] su [TextBox1]. Ora scriveremo la procedura [TextBox1_Changed].
Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
' modifica del testo
lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
End Sub
All’interno della procedura, inseriamo nell’etichetta [lblInfo1] un messaggio che segnala l’evento e indica il contenuto di [TextBox1]. Si procede allo stesso modo per [TextBox2]. Indichiamo inoltre l’ora per monitorare meglio l’elaborazione degli eventi. Il codice finale di [form4.aspx] è il seguente:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' salva la richiesta corrente in request.txt nella cartella della pagina
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)
' modifica del testo
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)
' modifica del testo
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>
Abbiamo aggiunto la procedura [Page_Init] per memorizzare la richiesta del cliente come nell'esempio precedente.
7.6.2. Test
Avviamo l’applicazione con WebMatrix tramite [F5]. Otteniamo la seguente pagina:

Il codice HTML ricevuto dal browser è il seguente:
<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>
In questo codice sono presenti molti elementi generati automaticamente dal server. Si notino i seguenti punti:
- ci sono tre campi nascosti: [__VIEWSTATE], che abbiamo già incontrato, [__EventTarget] e [__EventArgument]. Questi ultimi due campi vengono utilizzati per gestire l'evento "change" del browser sul campo di immissione [TextBox1]
- i tag server <asp:textbox> hanno generato i tag HTML <input type="text" ...> che corrispondono ai campi di immissione
- il tag server <asp:textbox id="TextBox1" AutoPostBack="true" ...> ha generato un tag <input type="text" ...> con un attributo [onchange="__doPostBack('TextBox1','')"]. Questo attributo indica che, in caso di modifica del contenuto di [TextBox1], deve essere eseguita la funzione JavaScript [_doPostBack(...)]. Questa è la seguente:
<script language="javascript">
<!--
function __doPostBack(eventTarget, eventArgument) {
var theform = document._ctl0;
theform.__EVENTTARGET.value = eventTarget;
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
</script>
Cosa fa la funzione sopra riportata? Assegna un valore a ciascuno dei due campi nascosti [__EventTarget] e [__EventArgument], quindi invia il modulo. Quest'ultimo viene quindi inviato al server. Questo è l'effetto di [AutoPostBack]. L’evento del browser «change» provoca l’esecuzione del codice «__doPostBack('TextBox1','')». Ne consegue che nel modulo inviato, il campo nascosto [__EventTarget] avrà il valore 'TextBox1' e il campo nascosto [__EventArgument] il valore ''. Ciò consentirà al server di identificare il componente che ha generato il POST.
- Il tag server <asp:textbox id="TextBox2"...> ha generato un tag <input type="text" ...> classico perché il suo attributo [AutoPostBack] non era impostato su [true].
- Il tag <form> indica che il modulo verrà inviato a [form4.aspx]:
Facciamo il nostro primo test. Digitiamo un testo nel primo campo di immissione:

poi posizioniamo il cursore nel secondo campo di immissione. Immediatamente, otteniamo una nuova pagina:

Cosa è successo? Quando il cursore ha lasciato il primo campo di immissione, il browser ha verificato se il contenuto di quest’ultimo fosse cambiato. Era così. Ha quindi generato, a livello del browser, l’evento [change] sul campo HTML [TextBox1]. Abbiamo quindi visto che una funzione JavaScript è stata eseguita e ha inviato il modulo a [form4.aspx]. Questa pagina è stata quindi ricaricata dal server. I valori inviati dal modulo hanno permesso al server di rilevare a sua volta che il contenuto del tag server [TextBox1] era cambiato. La procedura [TextBox1_Changed] è stata quindi eseguita sul lato server. Ha inserito un messaggio nell’etichetta [lblInfo1]. Una volta terminata questa procedura, [form4.aspx] è stata inviata al browser. Ecco perché ora abbiamo un testo in [lblInfo1]. Detto questo, ci si potrebbe stupire di trovare qualcosa nel campo di immissione [TextBox1]. Infatti, nessuna procedura eseguita lato server assegna un valore a questo campo. Si tratta di un meccanismo generale dei moduli web ASP.NET: il server restituisce il modulo nello stato in cui lo ha ricevuto. A tal fine, riassegna ai componenti il valore che è stato inviato per essi dal client. Per alcuni componenti, il client non invia alcun valore. È il caso, ad esempio, dei componenti <asp:label>, che vengono tradotti nei tag <span>. Ricordiamo che il modulo ha un campo nascosto che rappresenta lo stato del modulo al momento dell’invio al client. Questo stato è la somma degli stati di tutti i componenti del modulo, compresi i componenti <asp:label> se presenti. Poiché il campo nascosto [__VIEWSTATE] viene inviato dal browser del cliente, il server è in grado di ripristinare lo stato precedente di tutti i componenti del modulo. Non gli resta che modificare quelli il cui valore è stato modificato da POST.
Esaminiamo ora in [request.txt] la richiesta effettuata dal browser:
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=
Si vede chiaramente il codice POST e i parametri inviati. Torniamo al nostro browser e inseriamo un testo nel secondo campo di immissione:

Torniamo al campo di immissione n. 1 per inserire un nuovo testo: questa volta non succede nulla. Perché? Perché il campo di immissione [TextBox2], non avendo la proprietà [AutoPostBack] impostata su [true], il tag <input type="text"...> generato per esso non gestisce l’evento [Change], come mostra il suo codice HTML:
Non si verifica quindi alcun evento quando si esce dal campo di immissione n. 2. Ora inseriamo un nuovo testo nel campo n. 1:

Usciamo dal campo di immissione n. 1. Immediatamente viene rilevato l’evento [Change] su questo campo e il modulo viene inviato al server, che restituisce la seguente pagina:

Cosa è successo? Innanzitutto, il browser ha inviato il modulo. Questo fatto è riportato nella richiesta del client memorizzata in [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
Il server inizia restituendo ai componenti i loro valori precedenti grazie al campo nascosto [__VIEWSTATE] che il client gli ha inviato. Grazie ai campi inviati [TextBox1] e [TextBox2], assegnerà ai componenti [TextBox1] e [TextBox2] i valori che gli sono stati inviati. È attraverso questo meccanismo che il client recupererà il modulo così come è stato inviato. Successivamente, sempre grazie ai campi inviati [__VIEWSTATE], [TextBox1] e [TextBox2], il server rileverà che i valori dei campi di immissione [TextBox1] e [TextBox2] sono cambiati. Genererà quindi gli eventi [TextChanged] per questi due oggetti. Verranno eseguite le procedure [TextBox1_TextChanged] e [TextBox2_TextChanged] e le etichette [labelInfo1] e [labelInfo2] riceveranno un nuovo valore. Successivamente, la pagina [form4.aspx] così modificata viene rinviata al cliente.
Ora modifichiamo nuovamente il campo di immissione n. 1:

Quando si sposta il cursore di immissione fuori dal campo 1, nel browser si verifica l’evento [Change]. Successivamente si svolge la sequenza di eventi già spiegata (post dal browser al server, ..., invio della risposta del server). Si ottiene la seguente risposta:

Grazie all’ora visualizzata per ciascuno dei messaggi, si nota che sul server è stata eseguita solo la procedura [TextBox1_Changed]. La procedura [TextBox2_TextChanged] non è stata eseguita proprio perché il valore di [TextBox2] non è cambiato. Infine, inseriamo un nuovo testo nel campo n. 2:

Quindi posizioniamo il cursore sul campo n. 1 e poi lo riportiamo sul campo n. 2. La pagina non cambia. Perché? Poiché non modifichiamo il valore del campo n. 1, l’evento del browser [Change] non si verifica quando usciamo da quel campo. Di conseguenza, il modulo non viene inviato al server. Quindi nulla cambia sulla pagina. È proprio il fatto che il contenuto di [lblInfo2] non cambi a dimostrarci che non esiste alcun POST. Se ce ne fosse uno, il server rileverebbe che il contenuto di [TextBox2] è cambiato e dovrebbe riflettere tale cambiamento in [lblInfo2].
Da questo esempio si evince che non ha senso impostare la proprietà [AutoPostBack] di un [TextBox] su [true]. Ciò provoca, nella maggior parte dei casi, un inutile scambio client-server.
7.6.3. Il ruolo del campo __VIEWSTATE
Abbiamo visto che il server inseriva sistematicamente un campo nascosto denominato __VIEWSTATE nel modulo che generava. Abbiamo spiegato che questo campo rappresentava lo stato del modulo e che, se gli veniva restituito, il server era in grado di ricostruire il valore precedente del modulo. Lo stato di un modulo è la somma degli stati dei suoi componenti. Ciascuno di essi possiede una proprietà [EnableViewState] con valore booleano che indica se lo stato del componente debba essere inserito o meno nel campo nascosto [__VIEWSATE]. Per impostazione predefinita, questa proprietà ha il valore [true], il che fa sì che lo stato di tutti i componenti di un modulo venga inserito in [__VIEWSTATE]. A volte, ciò non è auspicabile.
Facciamo alcuni esperimenti per comprendere meglio il ruolo della proprietà [EnableViewState]. Impostiamo questa proprietà su [false] per entrambi i campi di immissione:
...
<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>
...
Eseguiamo ora l'applicazione e inseriamo un primo testo nel campo n. 1 prima di passare al campo n. 2. Viene quindi inviata una richiesta POST al server e si ottiene la seguente risposta:

Digitiamo un testo nel campo n. 2, modifichiamo quello del campo n. 1, quindi torniamo al campo n. 2 (in quest'ordine). Viene inviata una nuova richiesta POST al server e otteniamo la seguente risposta:

Per ora, tutto è come prima. Ora modifichiamo il contenuto del campo n. 1 e poi passiamo al campo n. 2. Viene inviata una nuova richiesta POST. La risposta del server è la seguente:

Questa volta c’è un cambiamento. Il server ha rilevato un evento [TextChanged] sul campo n. 2 poiché l’ora di [lblInfo2] è stata modificata. Eppure non c’era stato alcun cambiamento. Ciò si spiega con la proprietà [EnableViewState=false] di [TextBox2]. Essa fa sì che il server non abbia inserito nel campo [__VIEWSTATE] del modulo lo stato precedente di [TextBox2]. Ciò equivale ad assegnargli la stringa vuota come stato precedente. Quando si è verificato il POST a causa della modifica del contenuto di [TextBox1], il server ha confrontato il valore attuale di [TextBox2], che era [deux], con il suo valore precedente (la stringa vuota). Ne ha quindi dedotto che il valore di [TextBox2] fosse cambiato e ha generato l'evento [TextChanged] per [TextBox2]. È possibile verificare questo comportamento inserendo la stringa vuota in [TextBox2]. In base a quanto appena spiegato, il server non dovrebbe generare l’evento [TextChanged] per [TextBox2]. Proviamo:

È proprio quello che è successo. L’ora e il contenuto di [lblInfo2] dimostrano che la procedura [TextBox2_TextChanged] non è stata eseguita. Detto questo, esaminiamo la proprietà [EnableViewState] dei quattro componenti del modulo:
si desidera mantenere lo stato di questo componente in modo che il server sappia se è cambiato o meno | |
idem | |
non si desidera mantenere lo stato di questo componente. Si vuole che il testo venga ricalcolato ad ogni nuovo POST. Se non viene ricalcolato, deve essere vuoto. Tutto ciò si ottiene con [EnableViewState=false] | |
idem |
La nostra pagina di presentazione diventa la seguente:
...
<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>
...
Ripetiamo la stessa serie di test di prima. Nel punto in cui abbiamo ottenuto la schermata
version 1

ora otteniamo:
version 2

In questa fase, si modificava il contenuto del campo 1 senza modificare quello del campo 2, facendo sì che la procedura [TextBox2_TextChanged] non venisse eseguita sul lato server, il che implicava che il campo [lblInfo2] non ricevesse un nuovo valore. Viene quindi visualizzato con il suo valore precedente. Nella versione 1 ([EnableViewState=true]) tale valore precedente era il valore immesso. Nella versione 2 ([EnableViewState=false]), tale valore precedente è la stringa vuota.
A volte non è necessario conservare lo stato precedente dei componenti. Anziché impostare [EnableViewState=false] per ciascuno di essi, è possibile specificare che la pagina non debba mantenere il proprio stato. Ciò avviene nella direttiva [Page] del codice di presentazione:
In questo caso, indipendentemente dal valore della sua proprietà [EnableViewState], lo stato di un componente non viene inserito nel campo nascosto [__VIEWSTATE]. È quindi come se il suo stato precedente fosse la stringa vuota.
Utilizziamo ora il client [curl] per mettere in luce altri meccanismi. Richiediamo innanzitutto 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>
Riceviamo dal server il codice HTML da [form4.aspx]. Non è diverso da quello ricevuto dal browser. Ricordiamo qui la richiesta effettuata dal browser al momento dell'invio del modulo:
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=
Facciamo lo stesso POST ma senza inviare il campo [__VIEWSTATE]:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=primo+testo --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>
..............
Si notino i seguenti punti:
- il server ha rilevato un evento [TextChanged] su [TextBox1] poiché ha generato il testo [lblInfo1]. L'assenza di [__VIEWSTATE] non ha causato alcun problema. In sua assenza, presume che il valore precedente di un campo di immissione sia la stringa vuota.
- È riuscito a reinserire il testo inviato per [TextBox1] nell’attributo [value] del tag [TextBox1], in modo che il campo [TextBox1] riapparisse con il valore inserito. Per farlo, non ha bisogno di [__VIEWSTATE], ma solo del valore inviato per [TextBox1]
Ora ripetiamo la stessa richiesta senza apportare alcuna modifica. Otteniamo la seguente nuova risposta:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=primo+testo --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>
In assenza di [__VIEWSTATE], il server non è riuscito a rilevare che il valore del campo [TextBox1] non era cambiato. Ha quindi trattato il valore precedente come se fosse una stringa vuota. Ha quindi generato qui l'evento [TextChanged] su [TextBox1]. Ripetiamo la stessa richiesta, ma questa volta con il campo [TextBox1] vuoto e [TextBox2] non vuoto:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox2=secondo+testo --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>
......
In assenza di [__VIEWSTATE], il valore precedente di [TextBox1] è stato considerato come stringa vuota. Poiché anche il valore registrato di [TextBox1] era una stringa vuota, l'evento [TextChanged] su [TextBox1] non è stato generato. La procedura [TextBox1_TextChanged] non è stata eseguita e quindi il campo [lblInfo1] non ha ricevuto un nuovo valore. È noto che in questo caso il componente mantiene il suo valore precedente. Tuttavia, in questo caso non è così: [lblInfo1] ha perso il suo valore precedente. Ciò è dovuto al fatto che tale valore viene ricercato in [__VIEWSTATE]. Poiché questo campo è assente, a [lblInfo1] è stata assegnata una stringa vuota. Per [TextBox2], il server ha confrontato il suo valore inviato ([second texte]) con il suo valore precedente. In assenza di [__VIEWSTATE], tale valore precedente è pari alla stringa vuota. Poiché il valore inviato di [TextBox2] era diverso dalla stringa vuota, è stato generato l'evento [TextChanged] su [TextBox2]. La procedura [TextBox2_TextChanged] è stata eseguita e il campo [lblInfo2] ha ricevuto un nuovo valore.
Ci si può chiedere se i parametri [__EVENTTARGET] e [__EVENTARGUMENT] siano effettivamente utili. Se non si inviano questi parametri, il server non saprà quale evento abbia provocato il [submit]. Proviamo:
dos>curl --include --url http://localhost/form4.aspx --data TextBox2=secondo+testo --data TextBox1=primo+testo
..............................
<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>
.....................
Si nota che nessun evento [TextChanged] è stato elaborato. Inoltre, i campi inviati [TextBox1] e [TextBox2] non riportano i valori inviati. In effetti, è come se fosse stato eseguito un GET. Tutto torna alla normalità se tra i campi inviati è presente il campo [__EVENTTARGET], anche se privo di valore:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET= --data TextBox2=secondo+testo --data TextBox1=primo+testo
.......
<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. Altre proprietà del componente TextBox
Il componente server [TextBox] consente inoltre di generare i tag HTML <input type="password"..> e <textarea>..</textarea>, c.a.d. I tag corrispondono rispettivamente a un campo di immissione protetto e a un campo di immissione multilinea. Questa generazione è controllata dalla proprietà [TextMode] del componente [TextBox]. Ha tre possibili valori:
valore | tag HTML generato |
<input type="text" ...> | |
<textarea>...</textarea> | |
<input type="password ...> |
Esaminiamo l'uso di queste proprietà con il seguente esempio [form5.aspx]:

n. | nome | tipo | proprietà | ruolo |
1 | Pulsante | pulsante [submit] - serve ad aggiungere il contenuto di [TextBox1] a quello di [TextBox2] | ||
2 | TextBox | TextMode=Password Text= | campo di immissione protetto | |
3 | TextBox | TextMode=Multiriga Text= | aggrega i dati inseriti in [TextBox1] |
La proprietà [EnableViewState] della pagina viene impostata su [false]. Sul lato server, gestiamo l'evento clic sul pulsante [btnAjouter]:
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' si aggiunge il contenuto di [textBox1] a quello di [TextBox2]
textbox2.text=textbox2.text + textbox1.text+controlchars.crlf
End Sub
Per comprendere questo codice, è necessario ricordare la modalità di elaborazione dell’evento POST di un modulo. Le procedure [Page_Init] e [Page_Load] vengono eseguite per prime. Seguono poi tutte le procedure relative agli eventi memorizzati nella cache. Infine viene eseguita la procedura che gestisce l’evento che ha generato il codice [POST], in questo caso la procedura [btnAjouter_Click]. Quando vengono eseguiti i gestori di eventi, tutti i componenti della pagina che hanno un valore nel campo POST hanno assunto tale valore. Gli altri hanno ripreso il loro valore precedente se la loro proprietà [EnableViewState] era impostata su [true], oppure il loro valore di progettazione se la loro proprietà [EnableViewState] era impostata su [false]. In questo caso, i valori dei campi [TextBox1] e [TextBox2] entreranno a far parte del campo POST creato dal cliente. Inoltre, nel codice precedente, [textbox1.text] assumerà il valore inviato dal cliente, così come [textbox2.text]. La procedura [btnAjouter_Click] inserisce nel campo [TextBox2] il valore inviato per [TextBox2] sommato a quello inviato per [TextBox1], sommato al carattere di fine riga [ControlChars.CrLf] definito nello spazio dei nomi [Microsoft.VisualBasic]. Non è necessario importare questo spazio, poiché il server web lo importa per impostazione predefinita.
Il codice finale di [form5.aspx] è il seguente:
<%@ Page Language="VB" EnableViewState="False" %>
<script runat="server">
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' si aggiunge il contenuto di [textBox1] a quello di [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>
Poco più sopra è stata riportata la schermata di un'esecuzione.
7.7. Il componente DropDownList
Il tag <asp:DropDownList> consente di inserire un menu a tendina nel codice di presentazione di una pagina. Creiamo una pagina [form6.aspx] per ottenere la seguente presentazione:

n. | nome | tipo | proprietà | ruolo |
1 | DropDownList | AutoPostback=true EnableViewState=true | elenco a discesa | |
2 | Etichetta | EnableViewState=false | Messaggio informativo |
Il codice di presentazione generato è il seguente:
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>
Al momento, l'elenco a discesa non contiene alcun elemento. Lo compileremo nella procedura [Page_Load]. A tal fine, dobbiamo conoscere alcune delle proprietà e dei metodi della classe [DropDownList]:
collezione di tipo [ListItemCollection] degli elementi dell'elenco a discesa. I membri di questa collezione sono di tipo [ListItem]. | |
numero di elementi della collezione [Items] | |
elemento n. i dell’elenco - di tipo [ListItem] | |
per aggiungere un nuovo elemento [ListItem] alla collezione [Items] | |
per eliminare tutti gli elementi della collezione [Items] | |
per eliminare l'elemento n. i dalla collezione [Items] | |
Primo elemento [ListItem] della collezione [Items] con la proprietà [Selected] impostata su vero | |
numero dell'elemento [SelectedItem] nella collezione [Items] |
Gli elementi della collezione [Items] della classe [DropDownList] sono di tipo [ListItem]. Ogni elemento [ListItem] genera un tag HTML <option>:
Descriviamo alcune proprietà e metodi della classe [ListItem]:
costruttore - crea un elemento [ListItem] con le proprietà [texte] e [value]. Un elemento ListItem(T,V) darà origine al tag HTML <option value="V">T</option>. La classe [ListITem] consente quindi di descrivere gli elementi di un elenco HTML | |
booleano. Se vero, l’opzione corrispondente dell’elenco HTML avrà l’attributo [selected="selected"]. Questo attributo indica al browser che l’elemento corrispondente deve apparire selezionato nell’elenco HTML | |
il testo T dell’opzione HTML <option value="V" [selected="selected"]>T</option> | |
il valore V dell'attributo [Value] dell'opzione HTML <option value="V" [selected="selected"]>T</option> |
Abbiamo informazioni sufficienti per inserire nella procedura [Page_Load] della pagina il codice di compilazione dell'elenco a discesa [DropDownList1]:
Sub Page_Load(sender As Object, e As EventArgs)
' si compila il menu a tendina se è la prima volta che viene richiamato
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
Una volta inizializzato il componente [DropDownList1], la sua traduzione HTML sarà la seguente:
<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>
Sappiamo che la procedura [Page_Load] viene eseguita ogni volta che viene visualizzata la pagina [form6.aspx]. Quest’ultima viene chiamata la prima volta da una GET, poi da una POST ogni volta che l’utente seleziona un nuovo elemento dall’elenco a discesa. È necessario eseguire ogni volta il codice di compilazione di questo elenco in [Page_Load]? La risposta dipende dall’attributo [EnableViewState] del componente [DropDownList1]. Se questo attributo è impostato su vero, si sa che lo stato del componente [DropDownList1] verrà mantenuto nel corso delle richieste nel campo nascosto [__VIEWSTATE]. Questo stato comprende due elementi:
- l'elenco di tutti i valori del menu a tendina
- il valore dell’elemento selezionato in tale elenco
Sembra quindi allettante impostare la proprietà [EnableViewState] del componente [DropDownList1] su [true], in modo da non dover ricalcolare i valori da inserire nell’elenco. Il problema, però, è che, poiché la procedura [Page_Load] viene eseguita ogni volta che viene richiesta la pagina [form6.aspx], questi valori verranno comunque calcolati. L’oggetto [Page], di cui [form6.aspx] è un’istanza, possiede un attributo [IsPostBack] con valore booleano. Se questo attributo è vero, significa che la pagina è stata richiamata da un POST. Se è falso, significa che la pagina è stata richiamata da un GET. Nel nostro sistema client-server bidirezionale, il client richiede sempre la stessa pagina [form6.aspx] al server. La prima volta la richiede con un GET, le volte successive con un POST. Ne deduciamo che la proprietà [IsPostBack] può servirci a rilevare la prima richiesta GET del client. I valori dell’elenco a discesa vengono generati solo durante questa prima chiamata. Nelle richieste successive, tali valori saranno generati dal meccanismo del [VIEWSTATE]. In altre situazioni, il contenuto di un elenco può variare da una richiesta all’altra e deve quindi essere ricalcolato per ciascuna di esse. In questo caso, si imposterà l’attributo [EnableViewState] dell’elenco su [false] per evitare un doppio calcolo inutile del contenuto dell’elenco, a meno che non sia necessario conoscere gli elementi selezionati in precedenza nell’elenco, poiché tale informazione è conservata nel [VIEWSTATE].
L'attributo [AutoPostBack] dell'elenco [DropDownList1] è stato impostato su vero. Ciò significa che il browser invierà il modulo non appena rileverà l'evento «modifica dell'elemento selezionato» nell'elenco a discesa. Il server, a sua volta, rileverà tramite [VIEWSTATE] e i valori inviati che l’elemento selezionato nel componente [DropDownList1] è cambiato. Attiverà quindi l’evento [SelectedIndexChanged] su tale componente. Lo gestiremo con la seguente procedura:
Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
' modifica della selezione
lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
" valeur=" + dropdownlist1.selecteditem.value + _
" index="+ dropdownlist1.selectedindex.tostring
End Sub
Quando questa procedura viene eseguita, l'oggetto [DropDownList1] ha recuperato i propri elementi di tipo [ListItem] grazie a [VIEWSTATE]. Inoltre, uno di essi, di tipo [ListItem], presenta l’attributo [Selected] impostato su vero, ovvero quello il cui valore è stato inviato dal browser. È possibile accedere a questo elemento in diversi modi:
è il primo elemento [ListItem] dell’elenco il cui attributo [Selected] è impostato su vero | |
corrisponde alla parte [texte] del tag HTML dell’elemento <option value="...">testo</option> selezionato dall’utente | |
corrisponde alla parte [value] del tag HTML dell'elemento <option value="...">testo</option> selezionato dall'utente | |
numero nella collezione [DropDownList1.Items] del primo elemento [ListItem] il cui attributo [Selected] è vero |
Il codice finale di [form6.aspx] è il seguente:
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
' si compila il menu a tendina se è la prima volta che si riceve la chiamata
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)
' modifica della selezione
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. Il componente ListBox
Il tag <asp:ListBox> consente di inserire un elenco nel codice di presentazione di una pagina. Creiamo [form7.aspx] per ottenere la seguente presentazione:

n. | nome | tipo | proprietà | ruolo |
1 | TextBox | EnableViewState=false | campo di immissione | |
2 | Pulsante | pulsante [submit] che trasferisce nell'Elenco 1 il contenuto di txtSaisie se non è vuoto. | ||
3 | ListBox | EnableViewState=true SelectionMode=Single | elenco di valori a selezione singola | |
4 | ListBox | EnableViewState=true SelectionMode=Multiple | elenco di valori a selezione multipla | |
5 | Pulsante | pulsante [submit] che trasferisce in [liste 2] l'elemento selezionato da [liste 1] | ||
6 | Pulsante | pulsante [submit] che trasferisce in [liste 1] gli elementi selezionati da [liste 2] |
Il codice di presentazione generato è il seguente:
<%@ 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] deriva dalla stessa classe [ListControl] della classe [DropDownList] esaminata in precedenza. In essa si ritrovano tutte le proprietà e i metodi visti per [DropDownList], poiché in realtà appartenevano a [ListControl]. Compare una nuova proprietà:
imposta la modalità di selezione dell’elenco HTML <select> che verrà generato dal componente. Se SelectionMode=Single, sarà possibile selezionare un solo elemento. Se SelectionMode=Multiple, sarà possibile selezionare più elementi. A tal fine, l’attributo [multiple="multiple"] verrà generato nel tag <select> dell’elenco HTML. |
Gestiamo gli eventi. Il clic sul pulsante [Ajouter] verrà gestito dalla seguente procedura [btnAjouter_Click]:
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' aggiunta all'elenco 1
dim texte as string=txtSaisie.text.trim
if texte<> "" then ListBox1.Items.Add(New ListItem(texte))
' azzeramento di txtSaisie
txtSaisie.text=""
End Sub
Se il testo inserito in [txtSaisie] non è una stringa vuota o bianca, viene aggiunto un nuovo elemento all'elenco [ListBox1]. Sappiamo che dobbiamo aggiungere un elemento di tipo [ListItem]. In precedenza, avevamo utilizzato il costruttore [ListItem(T as String, V as String)] per eseguire un'operazione analoga. Un elemento di questo tipo genera il tag HTML [<option value="V">T</option>]. In questo caso, utilizziamo il costruttore [ListItem(T as String)] che genera i tag HTML, [<option value="T">T</option>] e c.a.d. il testo [T] dell'opzione viene utilizzato per formare il valore dell'opzione. Una volta aggiunto il contenuto di [txtSaisie] all'elenco [ListBox1], il campo [txTSaisie] viene svuotato.
I clic sui pulsanti [Effacer] saranno gestiti dalle seguenti procedure:
Sub btnRaz1_Click(sender As Object, e As EventArgs)
' azzeramento lista 1
ListBox1.Items.Clear
End Sub
Sub btnRaz2_Click(sender As Object, e As EventArgs)
' azzeramento della lista 2
ListBox2.Items.Clear
End Sub
I clic sui pulsanti di trasferimento tra elenchi vengono invece gestiti dalle seguenti procedure:
Sub btn1vers2_Click(sender As Object, e As EventArgs)
' trasferimento dell'elemento selezionato dall'elenco 1 all'elenco 2
transfert(ListBox1,ListBox2)
End Sub
Sub btn2vers1_Click(sender As Object, e As EventArgs)
' trasferimento dell'elemento selezionato dall'elenco 2 all'elenco 1
transfert(ListBox2,ListBox1)
End Sub
sub transfert(l1 as listbox, l2 as listbox)
' trasferimento degli elementi selezionati da l1 a l2
' C'è qualcosa da fare?
if l1.selectedindex=-1 then return
dim i as integer
' si comincia dalla fine
for i=l1.items.count-1 to 0 step -1
' selezionato?
if l1.items(i).selected then
' non più selezionato
l1.items(i).selected=false
' trasferimento in l2
l2.items.add(l1.items(i))
' eliminazione in l1
l1.items.removeAt(i)
end if
next
end sub
Poiché entrambi i pulsanti svolgono la stessa funzione, ovvero trasferire elementi da un elenco a un altro, è possibile ridurre il tutto a un'unica procedura di trasferimento con due parametri:
- l1 di tipo [ListBox], che è l'elenco di origine
- l2 di tipo [ListBox], che è l'elenco di destinazione
Innanzitutto, si verifica se nell’elenco l1 sia presente almeno un elemento selezionato; in caso contrario, non c’è nulla da fare. A tal fine, si esamina la proprietà [l1.selectedindex], che rappresenta il numero del primo elemento selezionato nell’elenco. Se non ce n’è nessuno, il suo valore è -1. Se c’è almeno un elemento selezionato in l1, si esegue il trasferimento verso l2. A tal fine, si percorre l’intero elenco degli elementi di l1 e si verifica per ciascuno di essi se il suo attributo [selected] è vero. In caso affermativo, il suo attributo [selected] viene impostato su [false], quindi viene copiato nell’elenco l2 e infine rimosso dall’elenco l1. Questa rimozione provoca una rinumerazione degli elementi dell’elenco l1. Ecco perché l’elenco degli elementi di l1 viene percorso al contrario. Se lo si percorresse in ordine e si eliminasse l’elemento n. 10, l’elemento n. 11 diventerebbe il n. 10 e il n. 12 diventerebbe l’elemento n. 11. Dopo aver elaborato l’elemento n. 10, il nostro ciclo in avanti elaborerà l’elemento n. 11 che, secondo quanto appena spiegato, è l’ex n. 12. Quello che aveva il n. 11 e che ora porta il n. 10 ci sfugge. Percorrendo gli elementi della lista l1 in senso inverso, si evita questo problema.
7.9. I componenti CheckBox, RadioButton
I tag <asp:RadioButton> e <asp:CheckBox> consentono di inserire rispettivamente un pulsante di opzione e una casella di controllo nel codice di presentazione di una pagina. Creiamo una pagina [form8.aspx] per ottenere la seguente presentazione:

n. | nome | tipo | proprietà | ruolo |
1 | RadioButton | RadioButton1.Checked=true RadioButton1.Text=1 RadioButton2.Checked=false RadioButton2.Text=2 RadioButton3.Checked=false RadioButton3.Text=3 per i 3 pulsanti: GroupName=radio | pulsanti di opzione | |
2 | CheckBox | Checked=false per tutti CheckBoxA.Text=A CheckBoxB.Text=B CheckBoxC.Text=C | caselle di selezione | |
3 | Pulsante | pulsante [submit] | ||
4 | ListBox | elenco di informazioni |
Affinché il browser tratti i tre pulsanti di opzione come mutuamente esclusivi, è necessario raggrupparli in un gruppo di pulsanti di opzione. Ciò avviene tramite l'attributo [GroupName] della classe [RadioButton]. In questa applicazione non è necessario mantenere lo stato della pagina. Pertanto, aggiungiamo alla pagina l’attributo [EnableViewState="false"]. Il codice di presentazione è il seguente:
<html>
<head>
</head>
<body>
<form id="frmControls" runat="server">
<h3>Cases à cocher
</h3>
<p>
<asp:RadioButton id="RadioButton1" runat="server" Checked="True" EnableViewState="False" GroupName="radio" Text="1"></asp:RadioButton>
<asp:RadioButton id="RadioButton2" runat="server" EnableViewState="False" GroupName="radio" Text="2"></asp:RadioButton>
<asp:RadioButton id="RadioButton3" runat="server" EnableViewState="False" GroupName="radio" Text="3"></asp:RadioButton>
</p>
<p>
<asp:CheckBox id="CheckBoxA" runat="server" EnableViewState="False" Text="A"></asp:CheckBox>
<asp:CheckBox id="CheckBoxB" runat="server" EnableViewState="False" Text="B"></asp:CheckBox>
<asp:CheckBox id="CheckBoxC" runat="server" EnableViewState="False" Text="C"></asp:CheckBox>
</p>
<p>
<asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
<asp:Button id="btnTree" onclick="btnTree_Click" runat="server" Text="Contrôles"></asp:Button>
</p>
<p>
<asp:ListBox id="lstInfos" runat="server" EnableViewState="False" Rows="6" Height="131px"></asp:ListBox>
</p>
</form>
</body>
</html>
Dobbiamo scrivere la procedura [btnEnvoyer_Click] per gestire l'evento [Click] su questo pulsante. Lo stato di un pulsante di opzione o di una casella di controllo è dato dal suo attributo [Checked], un valore booleano pari a vero se la casella è selezionata, falso in caso contrario. È quindi sufficiente inserire nell’elenco [lstInfos] il valore dell’attributo [Checked] dei sei pulsanti di opzione e caselle di controllo. Poiché non vi sono particolari difficoltà in questo, innoviamo un po’:
<script runat="server">
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' si inseriscono le informazioni nella casella di riepilogo
for each c as control in FindControl("frmControls").controls
' il controllo deriva da CheckBox
if TypeOf(c) is CheckBox then
lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
end if
next
End Sub
</script>
La pagina può essere vista come una struttura ad albero di controlli. Nel nostro esempio, la pagina contiene testi e controlli server. I testi sono considerati come un controllo specifico denominato [LiteralControl]. Ogni singolo testo dà origine a questo controllo, anche una sequenza di spazi tra due controlli. Ogni controllo possiede un attributo ID che lo identifica. È l’attributo ID che compare nei tag:
Se ignoriamo i controlli [LiteralControl], la pagina in esame presenta i seguenti controlli:
- [HtmlForm], che è il modulo [ID=frmControls]. Quest'ultimo è a sua volta un contenitore di controlli. Contiene i seguenti controlli:
-- [ID=RadioButton1] di tipo [RadioButton]
-- [ID=RadioButton2] di tipo [RadioButton]
-- [ID=RadioButton3] di tipo [RadioButton]
-- [ID=CheckBoxA] di tipo [CheckBox]
-- [ID=CheckBoxA] di tipo [CheckBox]
-- [ID= CheckBoxA] di tipo [CheckBox]
-- [ID=btnEnvoyer] di tipo [Button]
Un controllo possiede le seguenti proprietà:
restituisce l'insieme dei controlli figli di [Control], se presenti | |
restituisce il controllo identificato da ID che si trova alla radice dell'albero dei controlli figli di [Control]. Nell'esempio sopra riportato: Page.FindControl("frmControls") indica il contenitore [HtmlForm]. Per raggiungere il pulsante di opzione [RadioButton1], sarà necessario scrivere Page.FindControl("frmControls").FindControl("RadioButton1") | |
ID di [Control] |
Torniamo al codice della procedura [btnEnvoyer_Click]:
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' si inseriscono informazioni nella casella di riepilogo
for each c as control in FindControl("frmControls").controls
' Il controllo deriva da CheckBox?
if TypeOf(c) is CheckBox then
lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
end if
next
End Sub
Si desidera visualizzare lo stato dei pulsanti di opzione e delle caselle di controllo presenti nel modulo. Si percorrono tutti i controlli del modulo. Se il controllo corrente è di un tipo derivato da [CheckBox], si visualizza la sua proprietà [Checked]. Poiché la classe [RadioButton] deriva dalla classe [CheckBox], il test è valido per entrambi i tipi di controlli. La schermata riportata sopra mostra un esempio di esecuzione.
7.10. I componenti CheckBoxList, RadioButtonList
A volte si desidera consentire all’utente di scegliere tra valori che non sono noti al momento della progettazione della pagina. Queste opzioni provengono da un file di configurazione, da un database, ecc. e sono note solo al momento dell’esecuzione. Esistono soluzioni a questo problema e le abbiamo esaminate. L'elenco a selezione singola è adatto quando l'utente può effettuare una sola scelta, mentre l'elenco a selezione multipla è indicato quando può effettuarne più di una. Da un punto di vista estetico e se il numero di opzioni non è elevato, si potrebbe preferire l’uso di pulsanti di opzione al posto dell’elenco a selezione singola o di caselle di controllo al posto dell’elenco a selezione multipla. Ciò è possibile con i componenti [CheckBoxList] e [RadioButtonList].
Le classi [CheckBoxList] e [RadioButtonList] derivano dalla stessa classe [ListControl] delle classi [DropDownList] e [ListBox] esaminate in precedenza. Troveremo quindi alcune delle proprietà e dei metodi già visti per queste classi, ovvero quelli che in realtà appartenevano alla classe [ListControl].
collezione di tipo [ListItemCollection] degli elementi dell’elenco a discesa. I membri di questa collezione sono di tipo [ListItem]. | |
numero di elementi della collezione [Items] | |
elemento n. i dell’elenco - di tipo [ListItem] | |
per aggiungere un nuovo elemento [ListItem] alla collezione [Items] | |
per eliminare tutti gli elementi della collezione [Items] | |
per eliminare l'elemento n. i dalla collezione [Items] | |
Primo elemento [ListItem] della collezione [Items] con la proprietà [Selected] impostata su vero | |
numero dell'elemento precedente nella collezione [Items] |
Alcune proprietà sono specifiche delle classi [CheckBoxList] e [RadioButtonList]:
[horizontal] o [vertical] per elenchi orizzontali o verticali. |
Gli elementi della collezione [Items] sono di tipo [ListItem]. Ogni elemento [ListItem] genererà un tag diverso a seconda che si tratti di un oggetto [CheckBoxList] o [RadioButtonList]:
Oppure
Di seguito descriviamo alcune proprietà e metodi della classe [ListItem]:
costruttore - crea un elemento [ListItem] con le proprietà text e value. Un elemento ListItem(T,V) genererà il tag HTML <input type="checkbox" value="V">T o <input type="radio" value="V">T a seconda dei casi. | |
booleano. Se vero, l’opzione corrispondente dell’elenco HTML avrà l’attributo [selected="selected"]. Questo attributo indica al browser che l’elemento corrispondente deve apparire selezionato nell’elenco HTML | |
il testo T dell’opzione HTML <input type=".." value="V" [selected="selected"]>T | |
il valore dell'attributo Value dell'opzione HTML <input type=".." value="V" [selected="selected"]>T |
Ci proponiamo di creare la seguente pagina [form8b.aspx]:

n. | nome | tipo | proprietà | ruolo |
1 | RadioButtonList | EnableViewState=true RepeatDirection=horizontal | elenco di pulsanti di opzione | |
2 | CheckBoxList | EnableViewState=true RepeatDirection=horizontal | elenco di caselle di controllo | |
3 | Pulsante | pulsante [submit] che visualizza in [4] l'elenco degli elementi selezionati in entrambi gli elenchi | ||
4 | ListBox | EnableViewState=false | elenco di valori |
Il codice di presentazione della pagina è il seguente:
<%@ 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>
Il codice di controllo è il seguente:
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs) handles MyBase.Load
' si compilano gli elenchi se è la prima volta che viene chiamato
if not IsPostBack then
' testi per l'elenco RadioButton
dim textesRadio() as String = {"1","2","3","4"}
' testi per l'elenco CheckBox
dim textesCheckBox() as String = {"un","deux","trois","quatre"}
' compilazione dell’elenco a radio
dim i as integer
for i=0 to textesRadio.length-1
RadioButtonList1.Items.Add(new ListItem(textesRadio(i)))
next
' selezione elemento n. 1
RadioButtonList1.SelectedIndex=1
' compilazione dell'elenco con caselle di controllo
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)
' inserimento di informazioni nella casella di riepilogo lstinfos
affiche(RadioButtonList1)
affiche(CheckBoxList1)
End Sub
sub affiche(l1 as ListControl)
' visualizza i valori degli elementi selezionati di l1
' C'è qualcosa da fare?
if l1.selectedindex=-1 then return
dim i as integer
' si parte dalla fine
for i= 0 to l1.items.count-1
' selezionato?
if l1.items(i).selected then
lstInfos.Items.Add("["+TypeName(l1)+"] ["+l1.items(i).text+"] sélectionné")
end if
next
end sub
</script>
Nella procedura [Page_Load], che viene eseguita ogni volta che si visualizza la pagina, vengono inizializzati entrambi gli elenchi. Per evitare che ciò avvenga ogni volta, si utilizza la proprietà [IsPostBack] per eseguire l'inizializzazione solo la prima volta. Nelle volte successive, gli elenchi verranno rigenerati automaticamente tramite il meccanismo di [VIEWSTATE]. Una volta visualizzata la pagina, l’utente seleziona alcune caselle e utilizza il pulsante [Envoyer]. I valori del modulo vengono quindi inviati al modulo stesso. Dopo l’esecuzione di [Page_Load], viene eseguita la procedura [btnEnvoyer_Click]. Quest’ultima richiama la procedura [affiche] per compilare l’elenco [lstInfos]. Quest’ultima riceve come parametro un oggetto di tipo [ListControl], il che consente di inviarle indifferentemente un oggetto [RadioButtonList] o un oggetto [CheckBoxList], classi derivate da [ListControl]. L'attributo [EnableViewState] della lista [lstInfos] può essere impostato su [false], poiché il suo stato non deve essere mantenuto tra le diverse richieste.
7.11. I componenti Panel, LinkButton
Il tag <asp:panel> consente di inserire un contenitore di controlli in una pagina. Il vantaggio del contenitore è che alcune delle sue proprietà si applicano a tutti i controlli che contiene. È il caso della sua proprietà [Visible]. Questa proprietà esiste per ogni controllo server. Se un contenitore possiede la proprietà [Visible=false], ciascuno dei suoi controlli sarà gestito dalla propria proprietà [Visible]. Se possiede la proprietà [Visible=false], allora il contenitore e tutto ciò che contiene non vengono visualizzati. Ciò può risultare più semplice rispetto alla gestione della proprietà [Visible] di ciascuno dei controlli presenti nel contenitore.
Il tag <asp:LinkButton> consente di inserire un collegamento nel codice di presentazione di una pagina. Ha una funzione analoga a quella del pulsante [Button]. Infatti, genera un evento POST sul lato client grazie a una funzione JavaScript ad essa associata. Creiamo una pagina [form9.aspx] per ottenere la seguente visualizzazione:

n. | nome | tipo | proprietà | ruolo |
1 | Pannello | EnableViewState=true | contenitore di controlli | |
2 | ListBox | EnableViewState=true | un elenco di tre valori | |
3 | LinkButton | EnableViewState=false | link per nascondere il contenitore |
Quando il contenitore è nascosto, compare un nuovo link:

n. | nome | tipo | proprietà | ruolo |
4 | LinkButton | EnableViewState=false | link per visualizzare il contenitore |
Il codice di presentazione della pagina è il seguente:
<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>
Si noti che questo codice inizializza l'elenco [ListBox1] con tre valori. I gestori degli eventi [Clic] sui due collegamenti sono i seguenti:
<%@ 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)
' visualizza il contenitore 1
panel1.Visible=true
' modifica i link
lnkVoir.visible=false
lnkCacher.visible=true
End Sub
Sub lnkCacher_Click(sender As Object, e As EventArgs)
' nasconde il contenitore 1
panel1.Visible=false
' modifica i link
lnkVoir.visible=true
lnkCacher.visible=false
End Sub
</script>
Utilizzeremo la procedura [Page_Load] per inizializzare il modulo. Lo faremo durante la prima richiesta (IsPostBack=false):
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
' la prima volta
if not IsPostBack then
' si visualizza il contenitore
lnkVoir_Click(nothing,nothing)
end if
end sub
.....
</script>
7.12. Per proseguire...
I paragrafi precedenti hanno presentato una serie di componenti server. In ciascuna occasione sono state illustrate solo alcune delle loro proprietà. Per approfondire lo studio di questi componenti, il lettore potrà procedere in vari modi:
- scoprire le proprietà di un componente con un IDE come WebMatrix. Questo, infatti, illustra le principali proprietà dei componenti utilizzati in un modulo
- consultare la documentazione di .NET per scoprire l’insieme completo delle classi corrispondenti a ciascuno dei componenti server. Questo è il metodo da preferire per una padronanza totale del componente. Vi si troverà l’albero delle classi che conducono ai componenti, le proprietà, i metodi, i costruttori e gli eventi di ciascuna di esse. Inoltre, la documentazione fornisce talvolta degli esempi.
In questo capitolo abbiamo utilizzato la tecnica «tutto in uno» ([WebMatrix]), ovvero c.a.d, in cui il codice di presentazione e il codice di controllo di una pagina sono stati inseriti nello stesso file. In generale, non raccomandiamo questo metodo, ma quello detto [codebehind] utilizzato in precedenza, che colloca questi due codici in due file separati. Ricordiamo che il vantaggio di questa separazione risiede nel fatto che il codice di controllo può essere compilato senza dover eseguire l’applicazione web. Inoltre, i nostri esempi, come abbiamo spiegato fin dall’inizio del capitolo, presentavano una caratteristica ben precisa: erano costituiti da un’unica pagina che fungeva da modulo scambiato tra client e server in cicli successivi di richiesta-risposta, dove la prima richiesta del client era un GET e le successive erano POST.
7.13. Componenti server e controller dell’applicazione
Nei capitoli precedenti abbiamo realizzato diverse applicazioni web. Sono state tutte costruite secondo l’architettura MVC (Modello-Vista-Controllore), che suddivide l’applicazione in blocchi ben distinti e ne facilita la manutenzione. Allora realizzavamo le nostre interfacce utente con tag HTML standard. Alla luce di quanto appena visto, è naturale voler ora utilizzare componenti server. Riprendiamo un problema già studiato a lungo, ovvero il calcolo di un’imposta. La sua architettura MVC era la seguente:

L’applicazione presenta due viste: [formulaire.aspx] e [erreurs.aspx]. La vista [formulaire.aspx] viene visualizzata quando l’URL [main.aspx] viene richiesto per la prima volta:

L'utente compila il modulo:

e utilizza il pulsante [Calculer] per ottenere la seguente risposta:

In un'applicazione MVC, ogni richiesta deve passare attraverso il controller, in questo caso [main.aspx]. Ciò significa che quando il modulo [formulaire.aspx] è stato compilato dall’utente, deve essere inviato a [main.aspx] e non a [formulaire.aspx]. Ciò non è semplicemente possibile se realizziamo l’interfaccia utente [formulaire.aspx] con componenti server ASP. Per renderci conto di ciò, realizziamo un modulo [formtest.aspx] con un componente <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>
Si noti l'attributo [action="main.aspx"] del tag <form...>. Eseguiamo questa applicazione. La pagina di presentazione presenta un solo pulsante:

Diamo un'occhiata al codice HTML inviato dal server:
<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>
Si nota che il codice POST del modulo ha come destinazione lo stesso modulo [action="formtest.aspx"], mentre in [formtest.aspx] avevamo inserito il tag HTML server:
L'attributo [runat="server"] del tag <form> ci viene imposto dall'utilizzo dei componenti del server. Se non inseriamo questo attributo, si verifica un errore di compilazione. Quando lo inseriamo, l'attributo [action] del tag <form> viene ignorato. Il server genera sempre un attributo [action] che punta al modulo stesso. Ne consegue che in un’applicazione MVC non è possibile utilizzare moduli creati con il tag <form ... runat="server">. Tuttavia, questo tag è indispensabile per tutti i componenti server ASP che recuperano i dati inseriti dall’utente. In altre parole, non è possibile utilizzare moduli server ASP in un’applicazione MVC. È una grande scoperta. Infatti, uno dei punti di forza del marketing di ASP.NET è proprio la possibilità di costruire un’applicazione web come se fosse un’applicazione Windows. Questo è vero se la nostra applicazione non rispetta l’architettura MVC, ma lo è ancora di più in caso contrario. Tuttavia, l’architettura MVC sembra un concetto fondamentale dello sviluppo web attuale che risulta difficile ignorare.
È possibile utilizzare l’architettura MVC insieme a moduli con componenti ASP per applicazioni con poche viste diverse grazie al seguente espediente:
- l'applicazione consiste in un'unica pagina che funge da controller
- le viste si traducono in questa pagina in contenitori distinti, un contenitore per ogni vista. Per visualizzare una vista, si rende visibile il relativo contenitore e si nascondono gli altri
Si tratta di una soluzione elegante che ora metteremo in pratica in alcuni esempi
7.14. Esempi di applicazioni MVC con componenti server ASP
7.14.1. Esempio 1
In questo primo esempio implementiamo i componenti server che abbiamo presentato. La pagina [form10.aspx] sarà la seguente:
![]() | ![]() |
La schermata a sinistra qui sopra mostra il modulo così come viene presentato al cliente. Quest’ultimo lo compila e lo convalida tramite [Envoyer]. Il server gli restituisce una pagina che mostra un elenco dei valori inseriti (schermata a destra). Un link consente all’utente di tornare al modulo, che ritroverà così come lo ha inviato. Il codice di presentazione di [form10.aspx] è il seguente:
<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 pagina presenta due contenitori, uno per ciascuna vista: [panelform] per la vista modulo, [panelinfo] per la vista informazioni. L'elenco dei componenti del contenitore [panelForm] è il seguente:
nome | tipo | proprietà | ruolo |
Pannello | EnableViewState=true | vista modulo | |
RadioButton | EnableViewState=true GroupName=rdmarie | pulsanti di opzione | |
CheckBox | EnableViewState=true | caselle di selezione | |
TextBox | EnableViewState=true | campo di immissione | |
TextBox | EnableViewState=true | campo di immissione protetto | |
TextBox | EnableViewState=true | campo di immissione multilinea | |
DropDownList | EnableViewState=true | elenco a discesa | |
ListBox | EnableViewState=true SelectionMode=Single | elenco a selezione singola | |
Pulsante | EnableViewState=false | deseleziona tutti gli elementi di lstSimple | |
ListBox | EnableViewState=true SelectionMode=Multiple | elenco a selezione multipla | |
Pulsante | EnableViewState=false | deseleziona tutti gli elementi di lstMultiple | |
Etichetta | EnableViewState=true Visible=false | campo nascosto | |
HTML standard | visualizza un avviso | ||
Pulsante | EnableViewState=false | pulsante [submit] del modulo | |
HTML standard | pulsante [reset] del modulo |
Il ruolo di [VIEWSTATE] per i componenti è qui importante. Tutti i componenti, ad eccezione dei pulsanti, devono avere la proprietà [EnableViewState=true]. Per comprenderne il motivo, è necessario ricordare il funzionamento dell'applicazione. Supponiamo che il campo [txtSaisie] abbia la proprietà [EnableViewState=false]:
- il cliente richiede per la prima volta la pagina [form10.aspx]. Ottiene la vista del modulo
- lo compila e lo invia tramite il pulsante [Envoyer]. I campi di immissione vengono quindi inviati e il server assegna ai componenti server il valore inviato o il loro stato [VIEWSTATE], se ne avevano uno. In questo modo, al campo [txtSaisie] viene assegnato il valore immesso dall’utente. Anche in questa fase, il suo stato [VIEWSTATE] non ha alcuna utilità. Come risultato dell’operazione, viene inviata la vista [informations], che in realtà è sempre la pagina [form10.aspx] ma con un contenitore visualizzato diverso.
- L’utente visualizza questa nuova vista e utilizza il link [Retour au formulaire] per tornare a quella precedente. Viene quindi effettuato un POST verso [form10.aspx]. A questo punto vi è al massimo un solo valore inviato: il valore selezionato dall’utente nell’elenco delle informazioni, informazione che non viene successivamente utilizzata. Resta il fatto che non vi è alcun campo [txtSaisie] inviato.
- Il server riceve il POST e assegna ai componenti del server il valore inviato oppure il loro stato [VIEWSTATE], se ne avevano uno. In questo caso, [txtNom] non ha alcun valore inviato. Se il suo attributo [EnableViewState] è impostato su [false], gli verrà assegnata la stringa vuota. Poiché si desidera che abbia il valore inserito dall’utente, deve avere la proprietà [EnableViewState=true].
Il contenitore [panelinfo] presenta i seguenti controlli:
nome | tipo | proprietà | ruolo |
Pannello | EnableViewState=false | vista informazioni | |
ListBox | EnableViewState=false | elenco di informazioni che riassume i valori inseriti dall'utente | |
LinkButton | EnableViewState=false | link di ritorno al modulo |
Durante i test, osservando il codice HTML generato dal codice di presentazione sopra riportato, si potrebbe rimanere sorpresi dal codice generato per il campo nascosto [lblSecret]:
Il componente [lblSecret] non viene convertito nel codice HTML poiché possiede la proprietà [Visible=false]. Tuttavia, poiché possiede la proprietà [EnableViewState=true], il suo valore verrà comunque conservato nel campo nascosto [__VIEWSATE]. Sarà quindi possibile recuperarlo, come dimostreranno i test.
Non ci resta che scrivere i gestori di eventi. In [Page_Load] inizializzeremo il modulo:
Sub page_Load(sender As Object, e As EventArgs)
' la prima volta, si inizializzano gli elementi
' nelle volte successive, questi recuperano i propri valori tramite il VIEWSTATE
if IsPostBack then return
' inizializzazione del modulo
' pannello informativo non visualizzato
panelinfo.visible=false
' paneform visualizzato
panelform.visible=true
' pulsanti di opzione
rdNon.Checked=true
' caselle di selezione
chk2.Checked=true
' campo di immissione
txtSaisie.Text="qqs mots"
' campo password
txtMdp.Text="ceciestsecret"
' casella di immissione
txtArea.Text="ligne"+ControlChars.CrLf+"ligne2"+ControlChars.CrLf
' menu a discesa
dim i as integer
for i=1 to 4
cmbValeurs.Items.Add(new ListItem("choix"+i.ToString,i.ToString))
next
cmbValeurs.SelectedIndex=1
' elenco a selezione singola
for i=1 to 7
lstSimple.Items.Add(new ListItem("simple"+i.ToString,i.ToString))
next
lstSimple.SelectedIndex=0
' elenco a selezione multipla
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
' campo nascosto
lblSecret.Text="secret"
End Sub
Cliccando sui pulsanti [lstRazSimple] e [lstMultiple]:
Sub btnRazSimple_Click(sender As Object, e As EventArgs)
' azzeramento elenco singolo
lstSimple.SelectedIndex=-1
End Sub
Sub razMultiple_Click(sender As Object, e As EventArgs)
' azzeramento elenco multiplo
lstMultiple.SelectedIndex=-1
End Sub
Cliccando sul pulsante [Envoyer]:
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' il pannello informativo viene reso visibile e il pannello del modulo viene nascosto
panelinfo.Visible=true
panelform.visible=false
' si recuperano i valori inviati via POST e li si inseriscono in lstInfos
' pulsanti di opzione
dim info as string="état marital : "+iif(rdoui.checked,"marié"," non marié")
affiche(info)
' caselle di selezione
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)
' campo di immissione
affiche("champ de saisie : " + txtSaisie.Text.Trim)
' password
affiche("mot de passe : " + txtMdp.Text.Trim)
' casella di immissione
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))
' menu a discesa
affiche("éléments sélectionnés dans combo : "+selection(cmbValeurs))
' elenco semplice
affiche("éléments sélectionnés dans liste simple : "+selection(lstSimple))
' elenco multiplo
affiche("éléments sélectionnés dans liste multiple : "+selection(lstMultiple))
' campo nascosto
affiche ("Champ caché : " + lblSecret.Text)
End Sub
sub affiche(msg as String)
' visualizza messaggio in lstInfos
lstInfos.Items.Add(msg)
end sub
function selection(liste as ListControl) as string
' si scorrono gli elementi dell'elenco
' per individuare quelli selezionati
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
Infine, cliccando sul link [Retour vers le formulaire ]:
Sub LinkButton1_Click(sender As Object, e As EventArgs)
' si visualizza il modulo e si nasconde il pannello informativo
panelform.visible=true
panelinfo.visible=false
End Sub
7.14.2. Esempio 2
Riprendiamo qui un'applicazione già trattata con i moduli standard HTML. L'applicazione consente di effettuare simulazioni di calcolo delle imposte. Si basa su una classe [impot] che non verrà qui riproposta. Questa classe necessita di dati che reperisce in una fonte dati OLEDB. Ai fini dell’esempio, si tratterà di una fonte ACCESS.
7.14.2.1. La struttura MVC dell’applicazione
La struttura MVC dell’applicazione è la seguente:

Le tre viste saranno incorporate nel codice di presentazione del controller [main.aspx] sotto forma di contenitori. Pertanto, questa applicazione ha un'unica pagina [main.aspx].
7.14.2.2. Le viste dell'applicazione web
La vista [formulaire] è il modulo di inserimento dei dati che consente il calcolo dell’imposta di un utente:

L'utente compila il modulo:

Utilizza il pulsante [Envoyer] per richiedere il calcolo dell'imposta. Viene visualizzata la seguente vista [simulations]:

Torna al modulo tramite il link sopra indicato. Lo ritrova nello stato in cui lo aveva compilato. Può commettere errori di inserimento:

Questi gli vengono segnalati dalla vista [erreurs]:

Tornando al modulo tramite il link sopra indicato, lo ritroverà nello stato in cui lo ha compilato. Potrà effettuare nuove simulazioni:

Ottiene quindi la vista [simulations] con una simulazione in più:

Infine, se la fonte dei dati non è disponibile, ciò viene segnalato all’utente nella vista [erreurs]:

7.14.2.3. Il codice di presentazione dell’applicazione
Ricordiamo che la pagina [main.aspx] raggruppa tutte le viste. Si tratta di un unico modulo con tre contenitori:
- [panelform] per la vista [formulaire]
- [panelerreurs] per la vista [erreurs]
- [panelsimulations] per la vista [simulations]
Torniamo alla separazione tra codice di presentazione e codice di controllo in due file distinti. Il primo sarà in [main.aspx] e il secondo in [main.aspx.vb]. Il codice di [main.aspx] è il seguente:
<%@ 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>
Abbiamo delimitato i tre contenitori. Si noti che sono tutti all'interno del tag <form runat="server">. Ciò è obbligatorio, poiché se si desidera sfruttare i vantaggi dei componenti server, questi devono essere inseriti in un tag di questo tipo. Il punto importante da comprendere è che qui abbiamo un unico modulo che verrà scambiato tra il client e il server web. Ci troviamo quindi nella configurazione utilizzata in tutto questo capitolo sui componenti server. Analizziamo in dettaglio i componenti di ciascun contenitore:
Contenitore [panelform]:
nome | tipo | proprietà | ruolo |
Pannello | EnableViewState=true | vista modulo | |
RadioButton | EnableViewState=true GroupName=rdmarie | pulsanti di opzione | |
TextBox | EnableViewState=true | numero di figli | |
TextBox | EnableViewState=true | stipendio annuo | |
Pulsante | pulsante [submit] del modulo - avvia il calcolo dell'imposta | ||
Pulsante | Pulsante [submit] del modulo - azzera il modulo |
Contenitore [panelerreurs]:
nome | tipo | proprietà | ruolo |
Pannello | EnableViewState=true | vista errori | |
LinkButton | EnableViewState=true | link al modulo | |
Letterale | codice HTML dell'elenco degli errori |
Contenitore [panelsimulations]:
nome | tipo | proprietà | ruolo |
Pannello | EnableViewState=true | vista simulazioni | |
LinkButton | EnableViewState=true | link al modulo | |
Letterale | Codice HTML dell'elenco delle simulazioni in una tabella HTML |
7.14.2.4. Il codice di controllo dell'applicazione
Il codice di controllo dell'applicazione è distribuito nei file [global.asax.vb] e [main.aspx.vb]. Il file [global.asax] è definito come segue:
Il file [global.asax.vb] è il seguente:
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)
' si crea un oggetto import
Dim objImpot As impot
Try
objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
' si inserisce l'oggetto nell'applicazione
Application("objImpot") = objImpot
' nessun errore
Application("erreur") = False
Catch ex As Exception
'si è verificato un errore, lo si registra nell'applicazione
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' inizio sessione - si crea un elenco di simulazioni vuoto
Session.Item("simulations") = New ArrayList
End Sub
End Class
All'avvio dell'applicazione (prima richiesta inviata all'applicazione), viene eseguita la procedura [Application_Start]. Essa cerca di creare un oggetto di tipo [impot] prelevando i dati da una fonte OLEDB. Si invita il lettore a consultare il capitolo 5, dove è stata definita questa classe, qualora l'avesse dimenticata. La creazione dell'oggetto [impot] può fallire se la fonte di dati non è disponibile. In tal caso, l’errore viene memorizzato nell’applicazione affinché tutte le richieste successive sappiano che l’oggetto non è stato inizializzato correttamente. Se la creazione va a buon fine, anche l’oggetto [impot] creato viene memorizzato nell’applicazione. Esso verrà utilizzato da tutte le richieste di calcolo delle imposte. Quando un cliente effettua la sua prima richiesta, la procedura [Application_Start] crea una sessione a suo nome. Questa sessione serve a memorizzare le diverse simulazioni di calcolo delle imposte che il cliente effettuerà. Queste saranno memorizzate in un oggetto [ArrayList] associato alla chiave di sessione "simulazioni". All’avvio della sessione, questa chiave viene associata a un oggetto [ArrayList] vuoto. Le informazioni necessarie all’applicazione vengono inserite nel relativo file di configurazione [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 chiave [chaineConnexion] indica la stringa di connessione alla fonte OLEDB. L'altra parte del codice di controllo si trova in [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
' variabili locali
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
' innanzitutto, si verifica lo stato dell'applicazione
If CType(Application("erreur"), Boolean) Then
' l'applicazione non è riuscita a inizializzarsi
' si visualizza la pagina degli errori
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
afficheErreurs(erreurs, "")
Exit Sub
End If
' nessun errore - alla prima richiesta, si presenta il modulo
If Not IsPostBack Then afficheFormulaire()
End Sub
Ricordiamo che quando la procedura [Page_Load] viene eseguita su un client POST, tutti i componenti del modulo hanno un valore: o il valore inviato dal client, se presente, oppure il valore precedente del componente grazie alla procedura [VIEWSTATE]. In questo modulo, tutti i componenti hanno la proprietà [EnableViewState=true]. Prima di iniziare a elaborare la richiesta, ci assicuriamo che l’applicazione sia stata inizializzata correttamente. In caso contrario, visualizziamo la vista [erreurs] con la procedura [afficheErreurs]. Se si tratta della prima richiesta (IsPostBack=false), visualizziamo la vista [formulaire] con [afficheFormulaire].
La procedura che visualizza la vista [erreurs] è la seguente:
Private Sub afficheErreurs(ByRef erreurs As ArrayList, ByRef lien As String)
' viene visualizzato il contenitore degli errori
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
' gli altri contenitori sono nascosti
panelform.Visible = False
panelsimulations.Visible = False
End Sub
La procedura ha due parametri:
- un elenco di messaggi di errore in [erreurs]
- un testo di collegamento in [lien]
Il codice HTML da generare per l'elenco degli errori è inserito nel letterale [erreursHTML]. Il testo del link è invece contenuto nella proprietà [Text] dell’oggetto [LinkButton] della vista.
La procedura che visualizza la vista [formulaire] è la seguente:
Private Sub afficheFormulaire()
' visualizza il modulo
panelform.Visible = True
' gli altri contenitori sono nascosti
panelerreurs.Visible = False
panelsimulations.Visible = False
End Sub
Questa procedura si limita a rendere visibile il contenitore [panelform]. I componenti vengono visualizzati con il valore appena inserito o con quello precedente (VIEWSTATE).
Quando l'utente fa clic sul pulsante [Calculer] nella vista [formulaire], viene eseguito un passaggio da POST a [main.aspx]. Viene eseguita la procedura [Page_Load], seguita dalla procedura [btnCalculer_Click]:
Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
' si verifica la validità dei dati inseriti
Dim erreurs As ArrayList = checkData()
' se ci sono errori, li segnaliamo
If erreurs.Count <> 0 Then
' viene visualizzata la pagina degli errori
afficheErreurs(erreurs, "Retour au formulaire")
Exit Sub
End If
' nessun errore - si calcola l'imposta
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
rdOui.Checked, CType(txtEnfants.Text, Integer), CType(txtSalaire.Text, Long))
' si aggiunge il risultato alle simulazioni esistenti
Dim simulation() As String = New String() {CType(IIf(rdOui.Checked, "oui", "non"), String), _
txtEnfants.Text.Trim, txtSalaire.Text.Trim, impot.ToString}
' si aggiunge il risultato alle simulazioni esistenti
Dim simulations As ArrayList = CType(Session.Item("simulations"), ArrayList)
simulations.Add(simulation)
' si inseriscono le simulazioni nella sessione e nel contesto
Session.Item("simulations") = simulations
' si visualizza la pagina dei risultati
afficheSimulations(simulations, "Retour au formulaire")
End Sub
La procedura inizia verificando la validità dei campi del modulo tramite la procedura [checkData], che restituisce un elenco [ArrayList] di messaggi di errore. Se l'elenco non è vuoto, viene visualizzata la vista [erreurs] e la procedura termina. Se i dati inseriti sono validi, l’importo dell’imposta viene calcolato tramite l’oggetto di tipo [impot] che era stato memorizzato nell’applicazione all’avvio della stessa. Questa nuova simulazione viene aggiunta all’elenco delle simulazioni già effettuate e memorizzata nella sessione.
La funzione [CheckData] verifica la validità dei dati. Restituisce un elenco [ArrayList] di messaggi di errore, vuoto se i dati sono validi:
Private Function checkData() As ArrayList
' all'inizio nessun errore
Dim erreurs As New ArrayList
' numero di figli
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
' stipendio
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
' viene visualizzato l'elenco degli errori
Return erreurs
End Function
Infine, la vista [simulations] viene visualizzata dalla seguente procedura [afficheSimulations]:
Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
' visualizza la vista simulazioni
panelsimulations.Visible = True
' gli altri contenitori sono nascosti
panelerreurs.Visible = False
panelform.Visible = False
' contenuto della vista simulazioni
' ogni simulazione è un array di 4 elementi di tipo string
Dim simulation() As String
Dim i, j As Integer
simulationsHTML.Text = ""
For i = 0 To simulations.Count - 1
simulation = CType(simulations(i), String())
simulationsHTML.Text += "<tr>"
For j = 0 To simulation.Length - 1
simulationsHTML.Text += "<td>" + simulation(j) + "</td>"
Next
simulationsHTML.Text += "</tr>" + ControlChars.CrLf
Next
' link
lnkForm2.Text = lien
End Sub
La procedura ha due parametri:
- un elenco di simulazioni in [simulations]
- un testo di collegamento in [lien]
Il codice HTML da generare per l'elenco delle simulazioni è inserito nel letterale [simulationsHTML]. Il testo del collegamento è invece inserito nella proprietà [Text] dell'oggetto [LinkButton] della vista.
Quando l'utente fa clic sul pulsante [Effacer] della vista [formulaire], viene eseguita la procedura [btnEffacer_click] (sempre dopo [Page_Load]):
Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
' visualizza il modulo vuoto
razForm()
afficheFormulaire()
End Sub
Private Sub razForm()
' svuota il modulo
rdOui.Checked = False
rdNon.Checked = True
txtEnfants.Text = ""
txtSalaire.Text = ""
End Sub
Il codice sopra riportato è sufficientemente semplice da non richiedere commenti. Resta da gestire il clic sui link delle viste [erreurs] e [simulations]:
Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
' visualizza il modulo
afficheFormulaire()
End Sub
Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
' visualizza il modulo
afficheFormulaire()
End Sub
Entrambe le procedure si limitano a visualizzare la vista [formulaire]. Sappiamo che i campi di quest'ultima assumeranno un valore che corrisponde al valore inviato per essi oppure al loro valore precedente. Poiché in questo caso il POST del cliente non invia alcun valore per i campi del modulo, questi riprenderanno il loro valore precedente. Il modulo viene quindi visualizzato con i valori inseriti dall’utente. Ricordiamo che, nella versione senza componenti server, avevamo eseguito noi stessi questa operazione di ripristino.
7.14.2.5. Test
Tutti i file necessari all’applicazione sono collocati in una cartella <application-path>: ![]() | La cartella [bin] contiene la DLL con le classi [impot], [impotsData] e [impotsOLEDB] necessarie all'applicazione: |
Il lettore potrà, se lo desidera, rileggere il capitolo 5 in cui viene spiegato come creare il file [impot.dll] sopra indicato. Fatto ciò, il server Cassini viene avviato con i parametri (<application-path>,/impots5). Si richiede l'URL [http://impots5/main.aspx] con un browser:

Se si rinomina il file ACCESS [impots.mdb] in [impots1.mdb], si otterrà la seguente pagina:

7.14.3. Esempio 3
Con questi due esempi abbiamo dimostrato che è possibile realizzare applicazioni web conformi all’architettura MVC utilizzando componenti server. L’ultimo esempio mostra che la soluzione con componenti server è più semplice rispetto a quella che utilizza i tag standard HTML. I nostri due esempi presentavano una sola pagina con più viste all’interno della stessa pagina. È possibile avere un’architettura MVC con più moduli ASP lato server, purché il fatto che questi invino a se stessi i valori del modulo non costituisca un problema. Questo è molto spesso il caso delle applicazioni con menu. Prendiamo il seguente esempio:

Abbiamo raccolto in un’unica pagina i link alle applicazioni che abbiamo scritto finora. Questo tipo di applicazione si presta bene a un’architettura MVC. Semplicemente, non c’è più un solo controller, ma ce ne sono diversi.

Il controller [main.aspx] funge da controller principale. È lui che viene chiamato dai link presenti nella pagina iniziale dell’applicazione. Esso sarà in grado di eseguire operazioni comuni a tutte le azioni possibili, quindi farà eseguire l’azione specifica associata al link utilizzato. A quel punto passerà il testimone a uno dei controller secondari, quello incaricato di far eseguire l’azione. Da quel momento in poi, le comunicazioni avvengono tra il client e quel particolare controller. Non si passa più attraverso il controller principale [main.aspx]. Non ci troviamo quindi più nel contesto MVC con un unico controller che filtra tutte le richieste. Ciascuno dei controller sopra citati può presentare più viste tramite il meccanismo dei contenitori all’interno di un’unica pagina, come abbiamo illustrato.
Il fatto di non avere più un unico controller che seleziona le viste da inviare al client presenta alcuni svantaggi. Prendiamo ad esempio la gestione degli errori. Ciascuna delle azioni esposte dall’applicazione potrebbe dover visualizzare una vista di errore. Ogni controller [applix.aspx] avrà la propria vista [erreurs], poiché quest’ultima è semplicemente un contenitore specifico della pagina del controller. Non è possibile avere un’unica vista [erreurs] che venga utilizzata da tutte le singole applicazioni. Infatti, una vista di questo tipo presenta generalmente un link di ritorno al modulo errato, che deve essere ripristinato nello stato in cui era stato convalidato per consentire all’utente di correggere i propri errori. Questo ripristino avviene tramite il meccanismo del [VIEWSTATE], che non funziona tra controller diversi. Se le applicazioni vengono sviluppate da persone diverse, si rischia di avere pagine di errore dall’aspetto diverso a seconda dell’azione scelta dall’utente, compromettendo l’omogeneità dell’applicazione nel suo complesso. Vedremo più avanti che ASP.NET offre una soluzione a questo particolare problema della vista condivisa. Questa soluzione può essere implementata tramite un nuovo componente server che realizzeremo noi stessi. È sufficiente utilizzare questo componente nelle diverse applicazioni per garantire l’omogeneità dell’applicazione nel suo complesso. Più difficile da gestire è il problema dell’ordine delle azioni. Quando tutte le richieste passano attraverso un unico controller, quest’ultimo può verificare che l’azione richiesta sia compatibile con quella precedente. Questo codice di controllo si trova in un unico punto. In questo caso, sarà necessario distribuirlo tra i diversi controller, complicando la manutenzione dell’applicazione complessiva.
Torniamo alla nostra applicazione di cui sopra. La pagina iniziale è una classica pagina HTML:
<html>
<head>
<TITLE>Composants ASP Serveur</TITLE>
<meta name="pragma" content="no-cache">
</head>
<frameset rows="130,*" frameborder="0">
<frame name="banner" src="bandeau.htm" scrolling="no">
<frameset cols="200,*">
<frame name="contents" src="options.htm">
<frame name="main" src="main.htm">
</frameset>
<noframes>
<p id="p1">
Ce jeu de frames HTML affiche plusieurs pages Web. Pour afficher ce jeu de
frames, utilisez un navigateur Web qui prend en charge HTML 4.0 et version
ultérieure.
</p>
</noframes>
</frameset>
</html>
Questa pagina iniziale è composta da tre frame denominati banner, contents e main:
![]() |
La pagina [bandeau.htm] inserita nel riquadro [banner] è la seguente:

Il suo codice HTML è il seguente:
<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 pagina [options.htm] è inserita nel banner [contents]. Si tratta di una serie di link:
![]() | |
I vari link puntano tutti al controller principale [main.aspx] con un parametro [action] che indica l'azione da eseguire. Si richiede che la destinazione dei link venga visualizzata nel riquadro [main] (target="main").
La prima pagina visualizzata nel riquadro [main] è [main.htm]:
|
Il controller principale [main.aspx, main.aspx.vb] è il seguente:
[main.aspx]
[main.aspx.vb]
Classe pubblica main
Inherits System.Web.UI.Page
Private Sub Page_Load(ByVal mittente As System.Object, ByVal e As System.EventArgs) Con questo handle MyBase.Load
' si recupera l'azione da eseguire
Dim azione As String
If Request.QueryString("action") Is Nothing Then
action = "label"
Else
azione = Request.QueryString("azione").ToString.ToLower
End If
' si esegue l'azione
Select Case azione
Case "label"
Server.Transfer("form2.aspx")
Case "button"
Server.Transfer("form3.aspx")
Case "textbox1"
Server.Transfer("form4.aspx")
Case "textbox2"
Server.Transfer("form5.aspx")
Caso "dropdownlist"
Server.Transfer("form6.aspx")
Casella "listbox"
Server.Transfer("form7.aspx")
Cella "casella di spunta"
Server.Transfer("form8.aspx")
Caso "elenco caselle di controllo"
Server.Transfer("form8b.aspx")
Caso "pannello"
Server.Transfer("form9.aspx")
Caso Else
Server.Transfer("form2.aspx")
Fine Select
Fine Sub
Fine Classe
Il nostro controller è semplice. A seconda del valore del parametro [action], trasferisce l'elaborazione della richiesta alla pagina appropriata. Non offre alcun valore aggiunto rispetto a una pagina HTML con dei link. Tuttavia, basterebbe aggiungere una pagina di autenticazione per comprenderne l’utilità. Se l’utente dovesse autenticarsi (nome utente, password) per accedere alle applicazioni, il controller [main.aspx] sarebbe il luogo ideale per verificare che tale autenticazione sia stata effettuata.




