7. Componenti server ASP - 1
7.1. Introduzione
In questo capitolo descriviamo la tecnologia raccomandata in ASP.NET per la creazione dell'interfaccia utente. Sappiamo che ci sono due fasi distinte nell'elaborazione di una pagina .aspx da parte del server web:
- In primo luogo, viene eseguito il controller della pagina. Questo consiste in codice situato all'interno della pagina .aspx stessa (soluzione WebMatrix) o in un file separato (soluzione Visual Studio.NET).
- Successivamente viene eseguito il codice di presentazione della pagina .aspx e convertito in codice HTML inviato al client.

ASP.NET offre tre librerie di tag per la scrittura del codice di presentazione della pagina:
- Tag HTML classici. È ciò che abbiamo utilizzato finora.
- Tag HTML lato server
- Tag Web Forms
Indipendentemente dalla libreria di tag utilizzata, il ruolo del controller della pagina rimane lo stesso. Deve calcolare il valore dei parametri dinamici che compaiono nel codice di presentazione. Finora questi parametri dinamici erano semplici: erano oggetti di tipo [String]. Quindi, se nel codice di presentazione abbiamo un tag <%=name%>:
- il controller della pagina dichiara una variabile [name] di tipo [String] e ne calcola il valore
- quando il controller di pagina ha terminato il suo lavoro e il codice di presentazione viene eseguito per generare la risposta HTML, il tag <%=name%> viene sostituito dal valore calcolato dal codice del controller
Sappiamo che la separazione controller/vista è arbitraria e che il codice del controller e quello della vista possono essere mescolati nella stessa pagina. Abbiamo spiegato perché questo approccio è sconsigliato e continueremo ad attenerci alla separazione controller/vista.
I tag [server HTML] e [WebForms] consentono di includere nel codice di presentazione oggetti più complessi del semplice oggetto [String]. Questo a volte può essere molto utile. Prendiamo l'esempio di un modulo con un elenco. Questo elenco deve essere presentato al client 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à affrontato questo problema e lo abbiamo risolto includendo il tag
Questo tag verrà sostituito dal valore [String] della variabile [uneListeHTML]. Tale valore, calcolato dal controller, deve corrispondere al codice HTML dell'elenco, ovvero "<select name=..>...</select>". Non si tratta di un'operazione particolarmente complessa e sembra una soluzione elegante che evita di inserire il codice di generazione direttamente nel livello di presentazione della pagina. Qui ci sarebbe un ciclo in cui inserire i test, che risulterebbe quindi molto "ingombro". Tuttavia, questo metodo presenta uno svantaggio. La separazione tra controllo e presentazione in una pagina serve anche a delineare due aree di responsabilità:
- quella dello sviluppatore .NET, che gestisce il controller della pagina,
- quella del grafico, che gestisce la parte di presentazione della pagina
Qui vediamo che la generazione del codice HTML per l'elenco è stata spostata nel controller. Il grafico potrebbe voler modificare questo codice HTML per cambiare l'aspetto "visivo" dell'elenco. Sarebbe costretto a lavorare nella sezione [controller] e quindi a uscire dalla propria area di competenza, con il rischio associato di introdurre inavvertitamente errori nel codice.
Le librerie di tag server risolvono questo problema. Forniscono un oggetto che rappresenta un elenco HTML. Ad esempio, la libreria [WebForms] offre il seguente tag:
Questo tag rappresenta un oggetto [ListBox] che può essere manipolato dal controller della pagina. Questo oggetto ha proprietà per rappresentare le varie opzioni nell'elenco HTML e per designare l'opzione selezionata. Il controller della pagina assegnerà quindi i valori appropriati a queste proprietà. Quando viene eseguito il livello di presentazione, il tag
sarà sostituito dal codice HTML che rappresenta l'oggetto [uneListe], ovvero il codice "<select ..>...</select>". Per ora, non vi è alcuna differenza fondamentale rispetto al metodo precedente, se non un approccio di codifica orientato agli oggetti, il che è interessante. Torniamo al nostro grafico che deve modificare l’aspetto della lista. I tag server dispongono di attributi di stile (BackColor, Bordercolor, BorderWidth, ...) che consentono di impostare l’aspetto visivo dell’oggetto HTML corrispondente. Quindi possiamo scrivere:
<asp:ListBox id="ListBox1" runat="server" BackColor="#ffff99"></asp:ListBox></P>
Il vantaggio è che il grafico lavora direttamente all'interno del codice di presentazione per apportare queste modifiche. Questo è un chiaro vantaggio rispetto al metodo precedente. Le librerie di tag lato server semplificano quindi la costruzione del livello di presentazione delle pagine web — ciò a cui abbiamo fatto riferimento nei capitoli precedenti come interfaccia utente. Lo scopo di questo capitolo è quello di introdurle. Vedremo che a volte offrono oggetti complessi come calendari o tabelle collegate a origini dati. Sono estensibili, il che significa che l'utente può creare la propria libreria di tag. Può quindi creare un tag che genera un banner su una pagina. Tutte le pagine che utilizzano questo tag avranno quindi lo stesso banner.
L'HTML generato per un tag si adatta al tipo di browser del client. Quando il browser invia una richiesta al server web, include un'intestazione [User-Agent: xx] tra le sue intestazioni HTTP, dove [xx] identifica il client. Ecco un esempio:
Con queste informazioni, il server web può determinare 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 moderni supportano le versioni più recenti del linguaggio, mentre quelli più vecchi no. In base all'intestazione HTTP [User-Agent:] inviata dal client, il server invierà una versione HTML che il client è in grado di comprendere. Si tratta di un approccio utile e pratico perché significa che lo sviluppatore non deve preoccuparsi del tipo specifico di browser client utilizzato dalla propria applicazione.
Infine, IDE avanzati come Visual Studio.NET, WebMatrix, ecc., consentono una progettazione "in stile Windows" dell'interfaccia web. Sebbene questi strumenti non siano essenziali, forniscono un aiuto significativo allo sviluppatore. Lo sviluppatore progetta l'interfaccia web utilizzando componenti grafici che posiziona sull'interfaccia. Ha accesso diretto alle proprietà di ciascun componente dell'interfaccia, che può configurare a proprio piacimento. Queste proprietà vengono tradotte nel codice di presentazione HTML dell'interfaccia come attributi del tag <asp:> del componente. Il vantaggio per lo sviluppatore è che non deve memorizzare né l'elenco né la sintassi degli attributi di ciascun tag. Questo è un vantaggio significativo quando non si ha piena familiarità con le librerie di tag server offerte da ASP.NET. Una volta padroneggiata questa sintassi, alcuni sviluppatori potrebbero preferire codificare i tag direttamente nel codice di presentazione della pagina senza passare attraverso la fase di progettazione grafica. Un IDE non è quindi più necessario; è sufficiente un semplice editor di testo. A seconda del flusso di lavoro, l'attenzione si concentra quindi sui componenti (utilizzando un IDE) o sui tag (utilizzando un editor di testo). Questi due termini sono equivalenti. Un componente è l'oggetto che verrà manipolato dal codice di controllo della pagina. L'IDE ci consente di accedere alle sue proprietà durante la fase di progettazione. I valori assegnati a queste proprietà vengono immediatamente tradotti negli attributi del tag del componente nel codice di presentazione. Durante la 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à il codice HTML del componente utilizzando, da un lato, gli attributi impostati durante la 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 utilizzando programmi il cui contesto di esecuzione sarà per lo più il seguente:
- l'applicazione web consisterà in una singola pagina P contenente un modulo F,
- il client effettuerà la sua prima richiesta direttamente a questa pagina P. Ciò comporterà la richiesta dell'URL della pagina P utilizzando un browser. Si tratterà quindi di una richiesta GET che verrà effettuata a questo URL P. Il server fornirà la pagina P e quindi il modulo F in essa contenuto,
- l'utente lo compilerà e lo invierà, ovvero eseguirà 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.
- Quindi riprenderanno i passaggi 2 e 3.
Si tratta di un processo di esecuzione molto specifico, al di fuori del quale alcuni concetti discussi di seguito non funzionano più. Non siamo più nel contesto di un'architettura MVC in cui un'applicazione multipagina è controllata da una pagina specifica che abbiamo chiamato "controller dell'applicazione". In questo tipo di architettura, i POST dei moduli hanno come destinazione il controller e non i moduli stessi. Tuttavia, vedremo che la creazione di un modulo con componenti lato server implica che questo modulo venga inviato a se stesso.
7.3. Il componente Label
7.3.1. Utilizzo
Il tag <asp:label> consente di inserire testo dinamico nel codice di presentazione di una pagina. Non fa quindi altro che il tag <%=variable%> utilizzato finora. Lo studio di questo primo tag ci aiuterà a comprendere come funzionano i tag server. Creeremo una pagina con una sezione di controllo [form1.aspx.vb] e una sezione di presentazione [form1.aspx]. L'obiettivo è visualizzare l'ora:

Questo argomento è già stato trattato nel Capitolo 2 e si invita il lettore 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>
Stiamo introducendo il tag <asp:label>. Nelle librerie di tag, l'attributo [runat="server"] è obbligatorio. L'attributo ID identifica il componente. Il controller deve fare riferimento ad 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
' saves the current query in request.txt of the page folder
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
' sets time 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]. Pertanto, possiamo importare sistematicamente questo spazio dei nomi:
Imports System.Web.UI.WebControls
L'oggetto [Label] ha varie proprietà, tra cui la proprietà [Text], che rappresenta il testo che verrà visualizzato dal tag <asp:label> corrispondente. Qui, impostiamo questa proprietà sull'ora corrente. Lo facciamo nella procedura [Form_Load] del controller, che viene sempre eseguita. Nella procedura [Form_Init], che viene 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 l'opportunità di esaminare questo file per comprendere alcuni aspetti del funzionamento delle pagine che utilizzano i tag server.
L'oggetto [Label] ha molte proprietà, metodi ed eventi. Si invita il lettore a consultare la documentazione sulla classe [Label] per esplorarli. Questo vale per tutto il resto del libro. Per ogni tag, presentiamo solo le poche proprietà di cui abbiamo bisogno.
7.3.2. 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>
Possiamo vedere che il tag server
<asp:Label Runat="server" ID="lblHeure" />
è stato convertito 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
Niente di insolito.
7.3.3. Creazione dell'applicazione con WebMatrix
Abbiamo creato manualmente il codice di presentazione per la pagina [form1.aspx]. Questo metodo è utile se si conoscono i tag. È quindi sufficiente un semplice editor di testo per creare l'interfaccia utente. Per iniziare, è spesso necessario uno strumento di progettazione grafica combinato con la generazione automatica di codice, poiché non si conosce la sintassi dei tag necessari. Ora creeremo la stessa applicazione utilizzando lo strumento WebMatrix. Una volta avviato WebMatrix, selezioniamo l'opzione [File/Nuovo file]:

Creiamo una pagina ASP.NET denominata [form2.aspx]. Dopo aver confermato la procedura guidata precedente, vediamo la finestra di progettazione per la pagina [form2.aspx]:


Si noti che WebMatrix inserisce sia il codice di controllo della pagina che il codice di presentazione in un unico file, in questo caso [form2.aspx]. La scheda [All] mostra il contenuto di questo file di testo. Possiamo già vedere che non è vuoto:

Lo svantaggio di questo tipo di strumento è che spesso genera codice superfluo. È il caso qui, dove WebMatrix ha generato un tag HTML <form> anche se non abbiamo intenzione di creare un modulo... Inoltre, possiamo notare che al documento manca un tag <title>. Risolveremo subito entrambi questi problemi per ottenere la seguente versione aggiornata:

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

La finestra degli strumenti fornisce l'accesso a due tipi di componenti:
- componenti [WebControls], che si traducono in tag <asp:>
- componenti [HTML Elements], che si traducono in tag HTML standard. Tuttavia, è possibile aggiungere l'attributo [runat="server"] agli attributi di un tag HTML. 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 rappresenta. In precedenza ci siamo riferiti a questi tag come tag HTML lato server.
Fare doppio clic sul componente [Label] nell'elenco [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>
Per prima cosa, noterai che il tag <script> è sparito. È stato generato un tag <asp:label>. Ha un nome [Label1] e un valore [Label]. Torniamo alla scheda [Design] per cambiare questi due valori. Clicca una volta sul componente [Label] per far apparire la finestra delle proprietà nell'angolo in basso a destra:

Il lettore è invitato a esaminare le proprietà dell'oggetto [Label]. Due di esse sono di particolare interesse in questo contesto:
- Testo: è il testo che l'etichetta deve visualizzare; impostiamo la stringa su vuota (cioè nulla)
- ID: questo è il suo identificatore; lo impostiamo su lblHeure
La scheda [Design] ora appare così:

e il codice per [All] diventa:
<%@ Page Language="VB" %>
<html>
<head>
<title>webforms</title>
</head>
<body>
<asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>
Il layout della pagina è completo. Dobbiamo ancora scrivere il codice di controllo responsabile dell'impostazione dell'ora nella proprietà [Text] di [lblHeure]. Aggiungiamo il seguente codice nella scheda [All]:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
' sets time 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, l'oggetto [lblHeure] non è dichiarato come in precedenza:
Protected lblHeure As New System.Web.UI.WebControls.Label
Infatti, tutti i componenti server <asp:> nel livello di presentazione sono dichiarati implicitamente nel codice del controller. Dichiararli esplicitamente causa un errore di compilazione, indicando che l'oggetto è già stato dichiarato. Siamo pronti per eseguire il codice. Selezioniamo l'opzione [View/Start] o premiamo il tasto di scelta rapida [F5]. Cassini si avvia automaticamente con i seguenti parametri:

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

D'ora in poi, useremo principalmente lo strumento WebMatrix per facilitare lo sviluppo e il test dei brevi programmi che scriveremo.
7.4. Il componente letterale
7.4.1. Utilizzo
Il tag <asp:literal> consente di inserire testo dinamico nel codice di presentazione di una pagina, in modo simile al tag <asp:label>. Il suo attributo principale è [Text], che rappresenta il testo che verrà inserito così com'è 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 la formattazione utilizzando attributi quali [BorderColor, BorderWidth, Font, ...], la classe [Literal] non possiede nessuno di questi attributi. Il lettore può riprodurre l’esempio precedente nella sua interezza 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 delle applicazioni Windows. Questo è il punto 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 | testo=Pulsante1 | pulsante di invio | |
2 | Pulsante | testo=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>
Vediamo i componenti [Button] e [Label] utilizzati nel design grafico della pagina all'interno dei tag <asp:>. Notate il tag <form runat="server">, che è stato generato automaticamente. Si tratta di un tag HTML lato server, ovvero un tag HTML standard rappresentato da un oggetto che può essere manipolato dal controller. Il codice HTML per il tag <form> verrà generato in base al valore che il controller assegna a questo oggetto.
Aggiungiamo la procedura [Page_Init] alla sezione del codice relativa al controller, che gestisce l'evento [Init] della pagina. Inseriremo lì il codice che salva la richiesta del client nel file [request.txt]. Ne avremo bisogno per capire come funzionano i pulsanti.
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' saves the current request in request.txt of the page folder
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
</script>
Si noti che non abbiamo incluso la clausola [Handles MyBase.Init] dopo la dichiarazione della procedura [Page_Init]. Questo perché l'evento [Init] dell'oggetto [Page] ha un gestore predefinito denominato [Page_Init]. Se utilizziamo questo nome di gestore, la clausola [Handles Page.Init] diventa superflua. Tuttavia, includerla non causa alcun errore.
7.5.2. Test
Eseguiamo l'applicazione in WebMatrix premendo [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>
Si prega di notare i seguenti punti:
- il tag <form runat="server"> è stato convertito in un 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. Si noti che il modulo verrà inviato all'URL [form3.aspx]. Sono stati impostati anche altri due attributi [name, id]. Nella maggior parte dei casi, questi vengono ignorati. Tuttavia, se la pagina contiene codice JavaScript lato browser, l'attributo [name] del tag <form> risulta utile.
- I tag <asp:button> sono diventati tag HTML [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. Questo 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 modificato il proprio valore e, se necessario, di prendere delle decisioni. Queste decisioni assumeranno la forma di eventi quali "la casella di testo con tale nome ha modificato il proprio valore".
7.5.3. Richieste del client
Una volta caricata la pagina [form3.aspx] nel browser, richiediamola nuovamente ed esaminiamo la richiesta inviata dal browser per recuperarla. Ricordiamo che la nostra applicazione la memorizza nel file [request.txt] all'interno della 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
Questa è una richiesta GET standard. Ora clicchiamo sul pulsante [Button1] nella pagina del browser. Sembra che non succeda nulla. Tuttavia, sappiamo che il modulo è stato inviato. Il codice HTML della pagina lo conferma. 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 client ha inviato una richiesta POST all'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 [Button2], i valori inviati dal browser sono i seguenti:
Il valore del campo nascosto è sempre lo stesso, ma è il valore di [Button2] che è stato inviato. Il server può quindi determinare quale pulsante è stato utilizzato. Lo utilizzerà per attivare un evento che potrà essere gestito dalla pagina una volta caricata.
7.5.4. Gestione dell'evento Click di un oggetto Button
Rivediamo come funziona una pagina .aspx. Si tratta di un oggetto derivato dalla classe [Page]. Chiamiamo la classe derivata [aPage]. Quando il server riceve una richiesta per una pagina di questo tipo, viene istanziato un oggetto di tipo [unePage] tramite l'operazione new unePage(...). Il server genera quindi due eventi chiamati [Init] e [Load] in quest'ordine. L'oggetto [unePage] può gestirli fornendo i gestori di eventi [Page_Init] e [Page_Load]. Altri eventi verranno generati in seguito. Avremo l'opportunità di rivederli. Se la richiesta del client è un POST, il server genererà l'evento [Click] per il pulsante che ha attivato questo POST. Se la classe [unePage] ha fornito un gestore per questo evento, verrà chiamato. Esploriamo questo meccanismo con WebMatrix. Nella scheda [Design] di [form3.aspx], fate doppio clic sul pulsante [Button1]. Verremo quindi reindirizzati automaticamente alla scheda [Code], all'interno del corpo di una procedura chiamata [Button1_Click]. Per comprendere meglio, andiamo 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> per [Button1]. Questo attributo specifica la procedura responsabile della gestione dell'evento [Click] sull'oggetto [Button1], in questo caso la procedura [Button1_Click]. Non resta che scriverla:
Sub Button1_Click(sender As Object, e As EventArgs)
' click on button 1
lblInfo.Text="Vous avez cliqué sur [Bouton1]"
End Sub
La procedura visualizza un messaggio informativo nell'etichetta [lblInfo]. Procediamo allo stesso modo per il pulsante [Button2] 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)
' sauve la requte courante dans request.txt du dossier de la page
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
Sub Button1_Click(sender As Object, e As EventArgs)
' clic sur bouton 1
lblInfo.Text="Vous avez cliqué sur [Bouton1]"
End Sub
Sub Button2_Click(sender As Object, e As EventArgs)
' clic sur bouton 2
lblInfo.Text="Vous avez cliqué sur [Bouton2]"
End Sub
</script>
<html>
<head>
<title>asp:button</title>
</head>
<body>
<form runat="server">
<p>
<asp:Button id="Button1" onclick="Button1_Click" runat="server" Text="Bouton1"></asp:Button>
<asp:Button id="Button2" onclick="Button2_Click" runat="server" Text="Bouton2" BorderStyle="None"></asp:Button>
</p>
<p>
<asp:Label id="lblInfo" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
Premiamo [F5] per eseguire il codice e otteniamo la seguente pagina:

Se osserviamo il codice HTML ricevuto, vedremo che non è cambiato rispetto alla 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 [Button1], 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 vedere che il campo nascosto [__VIEWSTATE] ha cambiato il suo valore. Ciò riflette la modifica del valore del componente [lblInfo].
7.5.5. Eventi nel ciclo di vita di un'applicazione ASP.NET
La documentazione ASP.NET elenca gli eventi generati dal server durante il ciclo di vita di un'applicazione:
- Quando l'applicazione riceve la sua primissima richiesta, viene generato l'evento [Start] sull'oggetto [HttpApplication] dell'applicazione. Questo evento può essere gestito dalla procedura [Application_Start] nel file [global.asax] dell'applicazione.
Successivamente, avremo 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] nel file [global.asax] dell'applicazione.
- Il server genera l'evento [BeginRequest] sull'oggetto [HttpApplication]. Può essere gestito dalla procedura [Application_BeginRequest] nel file [global.asax] dell'applicazione.
- Il server carica la pagina richiesta dalla richiesta. Istanzierà un oggetto [Page] e genererà quindi due eventi su questo oggetto: [Init] e poi [Load]. Questi due eventi possono essere gestiti dalle procedure [Page_Init] e [Page_Load] della pagina.
- In base ai 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 in cui vengono gestiti gli eventi precedenti non è garantito. Pertanto, i gestori non devono fare alcuna ipotesi su questo ordine. Tuttavia, possiamo essere certi che l'evento [Click] del pulsante che ha innescato il 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 che la risposta HTML è stata inviata al client, la pagina verrà scaricata dalla memoria. In questo momento verranno generati due eventi: [Unload] e [Disposed]. La pagina può utilizzare questi eventi per liberare risorse.
L'applicazione può anche ricevere eventi al di fuori di una richiesta del client:
- L'evento [End] su un oggetto [Session] dell'applicazione si verifica quando una sessione termina. Ciò può avvenire sia su richiesta esplicita del codice di una pagina, sia perché la durata della sessione è scaduta. La procedura [Session_End] nel file [global.asax] gestisce questo evento. In genere, essa libera le risorse ottenute in [Session_Start].
- L'evento [End] sull'oggetto [HttpApplication] dell'applicazione si verifica quando l'applicazione termina. Ciò accade, ad esempio, quando il server Web viene arrestato. La procedura [Application_End] nel file [global.asax] gestisce questo evento. In genere viene utilizzata per liberare le risorse acquisite in [Application_Start].
È necessario tenere presente quanto segue:
- Il modello di eventi precedente si basa su scambi HTTP client-server standard. Ciò è chiaramente evidente quando si esaminano le intestazioni HTTP scambiate.
- L'elaborazione degli eventi precedenti avviene sempre sul lato server. Il clic su un pulsante può, ovviamente, essere gestito da uno script JavaScript lato server. Tuttavia, questo non è un evento server e si tratta di una tecnologia indipendente da ASP.NET.
Quando vengono elaborati gli eventi, se sul lato server (eventi relativi ai componenti del server) o sul lato browser tramite script JavaScript?
Prendiamo l'esempio di un elenco a discesa. Quando l'utente modifica l'elemento selezionato al suo interno, l'evento (cambiamento dell'elemento selezionato) può essere elaborato o meno e, se lo è, può essere elaborato in momenti diversi.
- Se si desidera elaborarlo immediatamente, ci sono due soluzioni:
- Può essere gestito dal browser utilizzando uno script JavaScript. In questo caso il server non interviene. Affinché ciò sia possibile, la pagina deve poter essere ricostruita utilizzando i valori presenti sulla pagina.
- Può essere elaborato dal server. Per questo, c'è una sola soluzione: il modulo deve essere inviato al server per l'elaborazione. Abbiamo quindi un'operazione [submit]. Vedremo che in questo caso utilizziamo un componente server chiamato [DropDownList] e impostiamo il suo attributo [AutoPostBack] su [true]. Ciò significa che se l'elemento selezionato nell'elenco a discesa cambia, il modulo deve essere immediatamente inviato al server. In questo caso, il server genera codice HTML per l'oggetto [DropDownList] associato a una funzione JavaScript responsabile dell'esecuzione di un [submit] non appena si verifica l'evento "cambio dell'elemento selezionato". Questo [submit] invierà il modulo al server e nel post saranno inclusi campi nascosti per indicare che il [post] è stato generato da un cambiamento di selezione nell'elenco a discesa. Il server genererà quindi l'evento [SelectedIndexChanged], che la pagina potrà gestire.
- Se si desidera gestire l'operazione ma non immediatamente, impostare l'attributo [AutoPostBack] del componente server [DropDownList] su [false]. In questo caso, il server genera il codice HTML standard per un elenco <select> per l'oggetto [DropDownList], senza una funzione JavaScript associata. Non succede nulla quando l'utente modifica la selezione nell'elenco a discesa. Tuttavia, quando l'utente invia il modulo utilizzando un pulsante [submit], ad esempio, il server sarà in grado di riconoscere che si è verificato un cambiamento di selezione. Abbiamo infatti visto che il modulo inviato al server contiene un campo nascosto [__VIEWSTATE] che rappresenta, in forma codificata, lo stato di tutti gli elementi nel modulo inviato. Quando il server riceve il nuovo modulo inviato dal client, può verificare se l'elemento selezionato nell'elenco a discesa è cambiato. In tal caso, attiverà l'evento [SelectedIndexChanged], che la pagina potrà poi gestire. Per distinguere questo meccanismo da quello precedente, alcuni autori affermano che l'evento "cambiamento di selezione" viene "memorizzato nella cache" quando si verifica nel browser. Verrà elaborato dal server solo quando il browser invia il modulo ad esso, spesso in seguito a un clic su un pulsante [Invia].
- Infine, se non si desidera gestire l'evento, impostare l'attributo [AutoPostBack] del componente server [DropDownList] su [false] e non scrivere il gestore per il suo evento [SelectedIndexChanged].
Una volta compreso il meccanismo di gestione degli eventi, lo sviluppatore non progetterà un'applicazione web come un'applicazione Windows. Infatti, mentre un cambiamento di selezione in una casella combinata all'interno di un'applicazione Windows può essere utilizzato per aggiornare immediatamente l'aspetto del modulo che la contiene, è più probabile che si esiti a gestire immediatamente questo evento in un'applicazione web se comporta un "invio" del modulo al server — e quindi un round-trip tra il client e il server. Questo è il motivo per cui la proprietà [AutoPostBack] dei componenti lato server è impostata su [false] per impostazione predefinita. 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 abilitato l'esecuzione degli script JavaScript. I moduli sono quindi spesso costruiti come segue:
- i componenti lato server del modulo hanno la proprietà [AutoPostBack] impostata su [false]
- il modulo presenta uno o più pulsanti responsabili dell'esecuzione dell'operazione [POST]
- nel codice del controller della pagina, si scrivono gestori solo per gli 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 il seguente layout:

1234Questa pagina, creata con WebMatrix, presenta i seguenti componenti:
N. | Nome | tipo | proprietà | ruolo |
1 | Casella di testo | AutoPostback=true Testo= | campo di immissione | |
2 | Casella di testo | AutoPostback=false Testo= | campo di immissione | |
3 | Etichetta | testo= | Messaggio informativo sul contenuto di [TextBox1] | |
3 | Etichetta | text= | Messaggio informativo sul contenuto di [TextBox2] |
Il codice generato da WebMatrix per questa sezione è 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]. Verrà quindi generato lo scheletro del gestore di eventi [TextChanged] per questo oggetto (nella 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 specificare il gestore dell'evento [TextChanged] su [TextBox1]. Ora scriviamo la procedura [TextBox1_Changed].
Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
' text change
lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
End Sub
Nella procedura, scriviamo un messaggio nell'etichetta [lblInfo1] che indica l'evento e mostra il contenuto di [TextBox1]. Facciamo lo stesso per [TextBox2]. Includiamo anche l'ora per monitorare meglio la gestione degli eventi. Il codice finale per [form4.aspx] è il seguente:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' sauve la requte courante dans request.txt du dossier de la page
Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
Me.Request.SaveAs(requestFileName, True)
End Sub
Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
' changement de texte
lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
End Sub
Sub TextBox2_TextChanged(sender As Object, e As EventArgs)
' changement de texte
lblInfo2.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox2]. Texte 2=["+textbox2.Text+"]"
End Sub
</script>
<html>
<head>
<title>asp:textbox</title>
</head>
<body>
<form runat="server">
<p>
Texte 1 :
<asp:TextBox id="TextBox1" runat="server" AutoPostBack="True" OnTextChanged="TextBox1_TextChanged"></asp:TextBox>
</p>
<p>
Texte 2 :
<asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged"></asp:TextBox>
</p>
<p>
<asp:Label id="lblInfo1" runat="server"></asp:Label>
</p>
<p>
<asp:Label id="lblInfo2" runat="server"></asp:Label>
</p>
</form>
</body>
</html>
Abbiamo aggiunto la procedura [Page_Init] per memorizzare la richiesta del client, come nell'esempio precedente.
7.6.2. Test
Avviamo l'applicazione in WebMatrix premendo [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]. Gli ultimi due campi sono 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" ...> corrispondenti 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 specifica che se il contenuto di [TextBox1] cambia, deve essere eseguita la funzione JavaScript [_doPostBack(...)]. È 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. Il modulo viene quindi inviato al server. Questo è l'effetto [AutoPostBack]. L'evento "change" del browser attiva l'esecuzione del codice "__doPostBack('TextBox1','')". Possiamo dedurre che nel modulo inviato, il campo nascosto [__EventTarget] avrà il valore 'TextBox1' e il campo nascosto [__EventArgument] avrà il valore ''. Ciò consente al server di identificare il componente che ha attivato il POST.
- Il tag server <asp:textbox id="TextBox2"...> ha generato un tag standard <input type="text" ...> poiché il suo attributo [AutoPostBack] non era impostato su [true].
- Il tag <form> indica che il modulo verrà inviato a [form4.aspx]:
Eseguiamo il nostro primo test. Digita del testo nel primo campo di immissione:

quindi spostare il cursore sul secondo campo di immissione. Immediatamente, appare una nuova pagina:

Cosa è successo? Quando il cursore ha lasciato il primo campo di immissione, il browser ha verificato se il suo contenuto fosse cambiato. Era cambiato. Quindi, dal lato del browser, ha attivato 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 riconoscere che il contenuto del tag [TextBox1] lato server 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] è stato rispedito al browser. Ecco perché ora abbiamo del testo in [lblInfo1]. Detto questo, può sembrare sorprendente che ci sia qualcosa nel campo di immissione [TextBox1]. In effetti, nessuna procedura 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. Per farlo, 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 renderizzati come tag HTML <span>. Ricordiamo che il modulo ha un campo nascosto [__VIEWSTATE] che rappresenta lo stato del modulo quando viene inviato al client. Questo stato è la somma degli stati di tutti i componenti del modulo, inclusi eventuali componenti <asp:label>. Poiché il campo nascosto [__VIEWSTATE] viene inviato dal browser del client, il server è in grado di ripristinare lo stato precedente di tutti i componenti del modulo. Non resta che modificare quelli i cui valori sono stati modificati dalla richiesta POST.
Diamo ora un'occhiata in [request.txt] alla 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=
Possiamo vedere chiaramente la richiesta POST e i parametri inviati. Torniamo al nostro browser e inseriamo del 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 ha la proprietà [AutoPostBack] impostata su [true], quindi il tag <input type="text"...> generato per esso non gestisce l'evento [Change], come mostrato nel suo codice HTML:
Pertanto, non si verifica 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. Ciò si riflette nella richiesta 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 ripristinando i componenti ai loro valori precedenti utilizzando il campo nascosto [__VIEWSTATE] che il client gli ha inviato. Utilizzando i campi inviati [TextBox1] e [TextBox2], assegna i valori inviati ai componenti [TextBox1] e [TextBox2]. È attraverso questo meccanismo che il client recupera il modulo così come è stato inviato. Quindi, utilizzando nuovamente i campi inviati [__VIEWSTATE], [TextBox1] e [TextBox2], il server rileva che i valori dei campi di immissione [TextBox1] e [TextBox2] sono cambiati. Attiva quindi gli eventi [TextChanged] per questi due oggetti. Verranno eseguite le procedure [TextBox1_TextChanged] e [TextBox2_TextChanged] e le etichette [labelInfo1] e [labelInfo2] riceveranno nuovi valori. Quindi la pagina [form4.aspx] modificata viene rinviata al client.
Ora modifichiamo nuovamente il campo di immissione n. 1:

Quando spostiamo il cursore fuori dal campo 1, nel browser si verifica l'evento [Change]. Quindi ha luogo la sequenza di eventi già spiegata (invio dal browser al server, ..., invio della risposta del server). Otteniamo la seguente risposta:

Grazie al timestamp visualizzato per ogni messaggio, possiamo vedere che sul server è stata eseguita solo la procedura [TextBox1_Changed]. La procedura [TextBox2_TextChanged] non è stata eseguita perché il valore di [TextBox2] non è cambiato. Infine, inseriamo del nuovo testo nel campo n. 2:

Quindi posizioniamo il cursore sul campo n. 1 e lo spostiamo nuovamente sul campo n. 2. La pagina non cambia. Perché? Poiché non stiamo modificando il valore del campo n. 1, l'evento [Change] del browser non si verifica quando usciamo da quel campo. Di conseguenza, il modulo non viene inviato al server. Pertanto, nulla cambia sulla pagina. È il fatto che il contenuto di [lblInfo2] non cambi a dimostrarci che non c'è alcun POST. Se ce ne fosse uno, il server rileverebbe che il contenuto di [TextBox2] è cambiato e dovrebbe riflettere questo cambiamento in [lblInfo2].
La lezione da trarre da questo esempio è che non ha senso impostare la proprietà [AutoPostBack] di un [TextBox] su [true]. Ciò causa il più delle volte un inutile round trip client-server.
7.6.3. Il ruolo del campo __VIEWSTATE
Abbiamo visto che il server inserisce sistematicamente un campo nascosto chiamato __VIEWSTATE nel modulo che genera. Abbiamo notato che questo campo rappresenta lo stato del modulo e che, se questo campo nascosto viene restituito al server, esso può ricostruire il valore precedente del modulo. Lo stato di un modulo è la somma degli stati dei suoi componenti. Ogni componente ha una proprietà [EnableViewState] con un valore booleano che indica se lo stato del componente debba essere inserito o meno nel campo nascosto [__VIEWSTATE]. Per impostazione predefinita, questa proprietà è impostata su [true], il che significa che lo stato di tutti i componenti in un modulo viene inserito in [__VIEWSTATE]. A volte, ciò non è auspicabile.
Eseguiamo alcuni test 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>
...
Ora eseguiamo l'applicazione e digitiamo del testo nel campo n. 1 prima di passare al campo n. 2. Viene quindi inviata una richiesta POST al server e otteniamo la seguente risposta:

Digitiamo del testo nel campo n. 2, modifichiamo il testo nel campo n. 1, quindi torniamo al campo n. 2 (in quest'ordine). Viene inviata una nuova richiesta POST al server e riceviamo 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 perché il valore di [lblInfo2] è stato modificato. Tuttavia, non c'è stata alcuna modifica effettiva. Ciò è dovuto alla proprietà [EnableViewState=false] di [TextBox2]. Questo fa sì che il server non memorizzi lo stato precedente di [TextBox2] nel campo [__VIEWSTATE] del modulo. Ciò equivale ad assegnare la stringa vuota come stato precedente. Quando si è verificato il POST innescato dalla modifica del contenuto di [TextBox1], il server ha confrontato il valore corrente di [TextBox2], che era [two], con il suo valore precedente (la stringa vuota). Ha concluso che [TextBox2] aveva cambiato il suo valore e ha generato l'evento [TextChanged] per [TextBox2]. Possiamo verificare questo comportamento inserendo una stringa vuota in [TextBox2]. In base a quanto appena spiegato, il server non dovrebbe generare l'evento [TextChanged] per [TextBox2]. Proviamo:

È esattamente ciò che è accaduto. L'ora e il contenuto di [lblInfo2] mostrano che la procedura [TextBox2_TextChanged] non è stata eseguita. Tenendo presente questo, esaminiamo la proprietà [EnableViewState] dei quattro componenti del modulo:
Vogliamo preservare lo stato di questo componente in modo che il server sappia se è cambiato o meno | |
idem | |
Non vogliamo conservare lo stato di questo componente. Vogliamo che il testo venga ricalcolato ad ogni nuovo POST. Se non viene ricalcolato, deve essere vuoto. Tutto questo si ottiene con [EnableViewState=false] | |
stesso |
La nostra home page ora appare così:
...
<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>
...
Eseguiamo la stessa serie di test di prima. Dove abbiamo ottenuto la schermata
versione 1

, ora otteniamo:
versione 2

In questo passaggio, abbiamo modificato il contenuto del campo 1 senza modificare quello del campo 2, assicurandoci che la procedura [TextBox2_TextChanged] non venisse eseguita sul lato server, il che significa che il campo [lblInfo2] non ha ricevuto un nuovo valore. Viene quindi visualizzato con il suo valore precedente. Nella versione 1 [EnableViewState=true], questo valore precedente era il valore inserito. Nella versione 2 [EnableViewState=false], questo 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 mantenga il proprio stato. Ciò avviene nella direttiva [Page] del codice di presentazione:
In questo caso, indipendentemente dal valore della proprietà [EnableViewState], lo stato di un componente non viene memorizzato nel campo nascosto [__VIEWSTATE]. Tutto si comporta quindi come se lo stato precedente fosse la stringa vuota.
Utilizziamo ora il client [curl] per evidenziare altri meccanismi. Innanzitutto, richiediamo 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 il codice HTML per [form4.aspx] dal server. Non è diverso da quello ricevuto dal browser. Ricordiamo qui la richiesta effettuata dal browser quando ha inviato il 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=
Eseguiamo lo stesso POST ma senza inviare il campo [__VIEWSTATE]:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=premier+texte --data TextBox2=
...................
<p>
Texte 1 :
<input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1">19:57:48: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
..............
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 impedito ciò. In sua assenza, si presume che il valore precedente di un campo di immissione sia la stringa vuota.
- È stato in grado di ripristinare il testo inviato per [TextBox1] nell'attributo [value] del tag [TextBox1], in modo che il campo [TextBox1] ricompaia con il valore inserito. Per farlo, non ha bisogno di [__VIEWSTATE], ma solo del valore inviato per [TextBox1]
Ora, effettuiamo nuovamente la stessa richiesta senza modificare nulla. Otteniamo la seguente nuova risposta:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=premier+texte --data TextBox2=
<p>
Texte 1 :
<input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1">20:05:47: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
In assenza di [__VIEWSTATE], il server non poteva vedere che il valore del campo [TextBox1] non era cambiato. Agisce quindi come se il valore precedente fosse la stringa vuota. Di conseguenza, ha generato l'evento [TextChanged] su [TextBox1] in questo caso. Eseguiamo nuovamente la stessa richiesta, 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=second+texte --data TextBox1=
......
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" value="second texte" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2">20:11:54: evt [TextChanged] sur [TextBox2]. Texte 2=[second texte]</span>
</p>
......
In assenza di [__VIEWSTATE], il valore precedente di [TextBox1] è stato trattato come stringa vuota. Poiché anche il valore inviato di [TextBox1] era una stringa vuota, l'evento [TextChanged] su [TextBox1] non è stato generato. La procedura [TextBox1_TextChanged] non è stata eseguita e, di conseguenza, il campo [lblInfo1] non ha ricevuto un nuovo valore. Sappiamo che in questo caso il componente mantiene il suo vecchio valore. Tuttavia, qui non è così; [lblInfo1] ha perso il suo valore precedente. Questo perché tale valore viene recuperato da [__VIEWSTATE]. Poiché questo campo manca, a [lblInfo1] è stata assegnata la stringa vuota. Per [TextBox2], il server ha confrontato il suo valore inviato [secondo testo] con il suo valore precedente. In assenza di [__VIEWSTATE], questo valore precedente è uguale alla stringa vuota. Poiché il valore inviato da [TextBox2] è diverso dalla stringa vuota, è stato attivato l'evento [TextChanged] su [TextBox2]. È stata eseguita la procedura [TextBox2_TextChanged] e il campo [lblInfo2] ha ricevuto un nuovo valore.
Ci si potrebbe chiedere se i parametri [__EVENTTARGET] e [__EVENTARGUMENT] siano effettivamente utili. Non inviando questi parametri, il server non saprà quale evento ha attivato il [submit]. Proviamo:
dos>curl --include --url http://localhost/form4.aspx --data TextBox2=second+texte --data TextBox1=premier+texte
..............................
<p>
Texte 1 :
<input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" id="TextBox2" />
</p>
<p>
<span id="lblInfo1"></span>
</p>
<p>
<span id="lblInfo2"></span>
</p>
</form>
.....................
Possiamo vedere che nessun evento [TextChanged] è stato gestito. Inoltre, i campi inviati [TextBox1] e [TextBox2] non recuperano i valori inviati. Infatti, tutto si comporta come se fosse stata effettuata una richiesta GET. Tutto torna alla normalità se il campo [__EVENTTARGET] è incluso nei campi inviati, anche se non ha alcun valore:
dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET= --data TextBox2=second+texte --data TextBox1=premier+texte
.......
<p>
Texte 1 :
<input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
</p>
<p>
Texte 2 :
<input name="TextBox2" type="text" value="second texte" id="TextBox2" />
</p>
<p>
<span id="lblInfo1">20:34:14: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
</p>
<p>
<span id="lblInfo2">20:34:14: evt [TextChanged] sur [TextBox2]. Texte 2=[second texte]</span>
</p>
........
7.6.4. Altre proprietà del componente TextBox
Il componente server [TextBox] consente inoltre di generare i tag HTML <input type="password"..> e <textarea>..</textarea>, ovvero i tag corrispondenti rispettivamente a un campo password e a un campo di immissione multilinea. Questa generazione è controllata dalla proprietà [TextMode] del componente [TextBox]. Essa ha tre possibili valori:
valore | Tag HTML generato |
<input type="text" ...> | |
<textarea>...</textarea> | |
<input type="password ...> |
Esamineremo l'uso di queste proprietà con il seguente esempio [form5.aspx]:

No. | nome | tipo | proprietà | ruolo |
1 | Pulsante | Pulsante [submit] - utilizzato per aggiungere il contenuto di [TextBox1] a quello di [TextBox2] | ||
2 | Casella di testo | ModalitàTesto=Password Testo= | campo di immissione protetto da password | |
3 | TextBox | ModalitàTesto=Multiriga Testo= | unisce le voci inserite in [TextBox1] |
La proprietà [EnableViewState] della pagina è impostata su [false]. Sul lato server, gestiamo l'evento clic sul pulsante [btnAjouter]:
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' the contents of [textBox1] are added to those of [TextBox2]
textbox2.text=textbox2.text + textbox1.text+controlchars.crlf
End Sub
Per comprendere questo codice, è necessario ricordare come viene elaborata la richiesta POST di un modulo. Le procedure [Page_Init] e [Page_Load] vengono eseguite per prime. Seguono tutte le procedure degli eventi memorizzate nella cache. Infine, viene eseguita la procedura che gestisce l'evento che ha attivato il [POST] — in questo caso, la procedura [btnAjouter_Click]. Quando vengono eseguiti i gestori di eventi, tutti i componenti della pagina che hanno un valore nella richiesta POST hanno assunto quel valore. Gli altri sono tornati al loro valore precedente se la loro proprietà [EnableViewState] era impostata su [true], oppure al loro valore di progettazione se la loro proprietà [EnableViewState] era impostata su [false]. In questo caso, i valori dei campi [TextBox1] e [TextBox2] faranno parte del POST inviato dal client. Pertanto, nel codice precedente, [textbox1.text] avrà il valore inviato dal client, e lo stesso vale per [textbox2.text]. La procedura [btnAjouter_Click] imposta il valore del campo [TextBox2] sul valore inviato per [TextBox2] sommato al valore inviato per [TextBox1], più il carattere di interruzione di riga [ControlChars.CrLf] definito nello spazio dei nomi [Microsoft.VisualBasic]. Non è necessario importare questo spazio dei nomi, poiché il server web lo importa per impostazione predefinita.
Il codice finale per [form5.aspx] è il seguente:
<%@ Page Language="VB" EnableViewState="False" %>
<script runat="server">
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' on ajoute le contenu de [textBox1] à celui de [TextBox2]
textbox2.text+=textbox1.text+controlchars.crlf
End Sub
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:Button id="btnAjouter" onclick="btnAjouter_Click" runat="server" Text="Ajouter" EnableViewState="False"></asp:Button>
<asp:TextBox id="TextBox1" runat="server" TextMode="Password" Width="353px" EnableViewState="False"></asp:TextBox>
</p>
<p>
<asp:TextBox id="TextBox2" runat="server" TextMode="MultiLine" Width="419px" Height="121px" EnableViewState="False"></asp:TextBox>
</p>
</form>
</body>
</html>
Poco fa abbiamo fornito uno screenshot di un'esecuzione.
7.7. Il componente DropDownList
Il tag <asp:DropDownList> consente di inserire un elenco a discesa nel codice di presentazione di una pagina. Creiamo una pagina [form6.aspx] per ottenere il seguente layout:

No. | Nome | Tipo | proprietà | ruolo |
1 | Elenco a discesa | 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>
Per ora, l'elenco a discesa non contiene elementi. Lo popoleremo nella procedura [Page_Load]. Per farlo, dobbiamo conoscere alcune delle proprietà e dei metodi della classe [DropDownList]:
Una raccolta di tipo [ListItemCollection] contenente gli elementi dell'elenco a discesa. I membri di questa raccolta sono di tipo [ListItem]. | |
Il numero di elementi nella raccolta [Items] | |
Elemento numero i nell'elenco - di tipo [ListItem] | |
per aggiungere un nuovo elemento [ListItem] alla collezione [Items] | |
per rimuovere tutti gli elementi dalla raccolta [Items] | |
per rimuovere l'elemento numero i dalla raccolta [Items] | |
Il primo [ListItem] nella collezione [Items] la cui proprietà [Selected] è true | |
Indice dell'elemento [SelectedItem] nella raccolta [Items] |
Gli elementi nella raccolta [Items] della classe [DropDownList] sono di tipo [ListItem]. Ogni [ListItem] genera un tag HTML <option>:
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 <option value="V">T</option>. La classe [ListItem] ci permette quindi di descrivere gli elementi di una lista HTML | |
booleano. Se vero, l'opzione corrispondente nell'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 scrivere il codice per popolare l'elenco a discesa [DropDownList1] nella procedura [Page_Load] della pagina:
Sub Page_Load(sender As Object, e As EventArgs)
' fill in the combo if it's the 1st time you've been called
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], il suo codice HTML sarà il 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 caricata la pagina [form6.aspx]. Viene chiamata la prima volta tramite una richiesta GET, e poi tramite una richiesta POST ogni volta che l'utente seleziona un nuovo elemento dall'elenco a discesa. Il codice per popolare questo elenco deve essere eseguito ogni volta in [Page_Load]? La risposta dipende dall'attributo [EnableViewState] del componente [DropDownList1]. Se questo attributo è impostato su true, sappiamo che lo stato del componente [DropDownList1] verrà conservato tra le richieste nel campo nascosto [__VIEWSTATE]. Questo stato include due elementi:
- l'elenco di tutti i valori presenti nell'elenco a discesa
- il valore dell'elemento selezionato in questo elenco
Potrebbe sembrare allettante impostare la proprietà [EnableViewState] del componente [DropDownList1] su [true] in modo da non dover ricalcolare i valori da inserire nell'elenco. Il problema, tuttavia, è 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, ha un attributo [IsPostBack] con un valore booleano. Se questo attributo è vero, significa che la pagina è stata richiesta tramite un POST. Se è falso, significa che la pagina è stata richiesta tramite un GET. Nel nostro sistema client-server a doppio passaggio, il client richiede sempre la stessa pagina [form6.aspx] dal server. La prima volta la richiede con un GET; le volte successive con un POST. Concludiamo che la proprietà [IsPostBack] può essere utilizzata per rilevare la prima richiesta GET del client. Generiamo i valori dell'elenco a discesa solo durante questa prima richiesta. Per le richieste successive, questi valori saranno generati dal meccanismo [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, imposteremo l'attributo [EnableViewState] dell'elenco su [false] per evitare un doppio calcolo non necessario del contenuto dell'elenco, a meno che non sia necessario conoscere gli elementi precedentemente selezionati nell'elenco, poiché queste informazioni sono memorizzate nel [VIEWSTATE].
L'attributo [AutoPostBack] dell'elenco [DropDownList1] è stato impostato su true. Ciò significa che il browser invierà il modulo non appena rileverà l'evento "elemento selezionato modificato" nell'elenco a discesa. Il server, a sua volta, rileverà, utilizzando [VIEWSTATE] e i valori inviati, che l'elemento selezionato nel componente [DropDownList1] è cambiato. Attiverà quindi l'evento [SelectedIndexChanged] su questo componente. Lo gestiremo con la seguente procedura:
Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
' selection change
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 [ListItem] tramite [VIEWSTATE]. Inoltre, uno di questi elementi [ListItem] ha l'attributo [Selected] impostato su true, ovvero quello il cui valore è stato inviato dal browser. Esistono diversi modi per accedere a questo elemento:
è il primo [ListItem] nell'elenco con l'attributo [Selected] impostato su true | |
corrisponde alla parte [text] del tag HTML per l'elemento <option value="...">text</option> selezionato dall'utente | |
corrisponde alla parte [value] del tag HTML per l'elemento <option value="...">text</option> selezionato dall'utente | |
L'indice nella raccolta [DropDownList1.Items] del primo elemento [ListItem] il cui attributo [Selected] è true |
Il codice finale per [form6.aspx] è il seguente:
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
' on remplit le combo si c'est la 1ère fois qu'on est appelé
if not IsPostBack then
dim valeurs() as String = {"1","2","3","4"}
dim textes() as String = {"un","deux","trois","quatre"}
dim i as integer
for i=0 to valeurs.length-1
DropDownList1.Items.Add(new ListItem(textes(i),valeurs(i)))
next
end if
end sub
Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
' changement de sélection
lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
" valeur=" + dropdownlist1.selecteditem.value + _
" index="+ dropdownlist1.selectedindex.tostring
End Sub
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:DropDownList id="DropDownList1" runat="server" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
</p>
<p>
<asp:Label id="lblInfo" runat="server" enableviewstate="False"></asp:Label>
</p>
</form>
</body>
</html>
7.8. Il componente ListBox
Il tag <asp:ListBox> consente di inserire un elenco nel codice di presentazione di una pagina. Creiamo [form7.aspx] per ottenere il seguente layout:

N. | nome | tipo | proprietà | ruolo |
1 | Casella di testo | EnableViewState=false | campo di immissione | |
2 | Pulsante | Pulsante [submit] che trasferisce il contenuto di txtSaisie a List 1 se non è vuoto. | ||
3 | Casella di riepilogo | EnableViewState=true ModalitàSelezione=Singola | elenco di valori per la selezione singola | |
4 | ListBox | EnableViewState=true ModalitàSelezione=Multipla | elenco di valori per la selezione multipla | |
5 | Pulsante | Pulsante [Invia] che trasferisce l'elemento selezionato da [elenco 1] a [elenco 2] | ||
6 | Pulsante | Pulsante [Invia] che trasferisce gli elementi selezionati da [elenco 2] a [elenco 1] |
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] discussa in precedenza. Include tutte le proprietà e i metodi visti per [DropDownList] poiché in realtà appartenevano a [ListControl]. Appare una nuova proprietà:
imposta la modalità di selezione dell'elenco HTML <select> che verrà generato dal componente. Se SelectionMode=Single, è possibile selezionare un solo elemento. Se SelectionMode=Multiple, è possibile selezionare più elementi. A tal fine, nell'elemento <select> dell'elenco HTML verrà generato l'attributo [multiple="multiple"]. |
Gestiamo gli eventi. Un clic sul pulsante [Add] verrà gestito dalla seguente procedura [btnAdd_Click]:
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' added to list 1
dim texte as string=txtSaisie.text.trim
if texte<> "" then ListBox1.Items.Add(New ListItem(texte))
' raz txtSaisie
txtSaisie.text=""
End Sub
Se il testo inserito in [txtSaisie] non è una stringa vuota o nulla, viene aggiunto un nuovo elemento all'elenco [ListBox1]. Sappiamo che dobbiamo aggiungere un elemento di tipo [ListItem]. In precedenza, abbiamo utilizzato il costruttore [ListItem(T as String, V as String)] per eseguire un'operazione simile. Tale elemento genera il tag HTML [<option value="V">T</option>]. Qui utilizziamo il costruttore [ListItem(T as String)], che genera il tag HTML [<option value="T">T</option>], ovvero il testo [T] dell'opzione viene utilizzato per formare il valore dell'opzione. Una volta che il contenuto di [txtSaisie] è stato aggiunto all'elenco [ListBox1], il campo [txtSaisie] viene cancellato.
I clic sui pulsanti [Clear] saranno gestiti dalle seguenti procedure:
Sub btnRaz1_Click(sender As Object, e As EventArgs)
' raz list 1
ListBox1.Items.Clear
End Sub
Sub btnRaz2_Click(sender As Object, e As EventArgs)
' raz list 2
ListBox2.Items.Clear
End Sub
I clic sui pulsanti per il trasferimento tra gli elenchi sono gestiti dalle seguenti procedure:
Sub btn1vers2_Click(sender As Object, e As EventArgs)
' transfer the item selected in list 1 to list 2
transfert(ListBox1,ListBox2)
End Sub
Sub btn2vers1_Click(sender As Object, e As EventArgs)
' transfer of item selected in list 2 to list 1
transfert(ListBox2,ListBox1)
End Sub
sub transfert(l1 as listbox, l2 as listbox)
' transfer items selected in l1 to l2
' anything to do?
if l1.selectedindex=-1 then return
dim i as integer
' we start at the end
for i=l1.items.count-1 to 0 step -1
' selected?
if l1.items(i).selected then
' more selected
l1.items(i).selected=false
' transfer to l2
l2.items.add(l1.items(i))
' deletion in l1
l1.items.removeAt(i)
end if
next
end sub
Poiché entrambi i pulsanti eseguono la stessa operazione — trasferire elementi da un elenco all'altro — possiamo ridurla 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
Per prima cosa, controlliamo se c'è almeno un elemento selezionato nell'elenco l1; in caso contrario, non c'è nulla da fare. Per farlo, esaminiamo la proprietà [l1.selectedindex], che rappresenta l'indice del primo elemento selezionato nell'elenco. Se non ce ne sono, il suo valore è -1. Se c'è almeno un elemento selezionato in l1, lo trasferiamo a l2. Per farlo, percorriamo l'intero elenco di elementi in l1 e verifichiamo per ciascuno se il suo attributo [selected] è vero. In tal caso, il suo attributo [selected] viene impostato su [false], quindi viene copiato nell'elenco l2 e infine rimosso dall'elenco l1. Questa rimozione comporta la rinumerazione degli elementi nell'elenco l1. Questo è il motivo per cui l'elenco degli elementi in l1 viene attraversato in ordine inverso. Se lo percorriamo in avanti e rimuoviamo l'elemento n. 10, l'elemento n. 11 diventa n. 10 e il n. 12 diventa n. 11. Dopo aver elaborato l'elemento n. 10, il nostro ciclo in avanti elaborerà l'elemento n. 11, che, come abbiamo appena spiegato, è l'ex n. 12. L'elemento che era n. 11 e ora è n. 10 viene ignorato. Percorrendo gli elementi dell'elenco l1 nella direzione opposta, evitiamo questo problema.
7.9. I componenti CheckBox e 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 il seguente layout:

No. | nome | tipo | proprietà | ruolo |
1 | Pulsante di opzione | RadioButton1.Checked = true RadioButton1.Text = 1 RadioButton2.Selezionato = false RadioButton2.Text = 2 RadioButton3.Selezionato = false RadioButton3.Text = 3 Per tutti e 3 i pulsanti: GroupName = radio | pulsanti di opzione | |
2 | Casella di controllo | Selezionato=false per tutti Casella di selezioneA.Testo = A Casella di controllo B.Testo = B Casella di controllo C.Testo = C | caselle di controllo | |
3 | Pulsante | Pulsante [Invia] | ||
4 | Casella di riepilogo | elenco informazioni |
Per garantire che il browser tratti i tre pulsanti di opzione come mutuamente esclusivi, è necessario raggrupparli in un gruppo di pulsanti di opzione. Ciò si ottiene utilizzando l'attributo [GroupName] della classe [RadioButton]. In questa applicazione non è necessario mantenere lo stato della pagina. Pertanto, impostiamo l'attributo [EnableViewState="false"] sulla pagina. 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 è determinato dal suo attributo [Checked], che è vero se la casella è selezionata e falso in caso contrario. Quindi dobbiamo semplicemente scrivere il valore dell'attributo [Checked] per i sei pulsanti di opzione e caselle di controllo nell'elenco [lstInfos]. Poiché non vi è alcuna difficoltà particolare nel farlo, proviamo qualcosa di leggermente diverso:
<script runat="server">
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' place information in the listbox
for each c as control in FindControl("frmControls").controls
' is the control derived from 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 visualizzata come una struttura ad albero di controlli. Nel nostro esempio, la pagina contiene testo e controlli server. Il testo viene trattato come un controllo speciale denominato [LiteralControl]. Qualsiasi testo crea questo controllo, anche una sequenza di spazi tra due controlli. Ogni controllo ha un attributo ID che lo identifica. È l'attributo ID che appare nei tag:
Se ignoriamo i controlli [LiteralControl], la pagina in questione presenta i seguenti controlli:
- [HtmlForm], che è il modulo [ID=frmControls]. Questo, a sua volta, è un contenitore per i 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 ha le seguenti proprietà:
restituisce la raccolta dei controlli figli di [Control], se presenti | |
restituisce il controllo identificato dall'ID situato alla radice dell'albero dei controlli figli di [Control]. Nell'esempio sopra: Page.FindControl("frmControls") si riferisce al contenitore [HtmlForm]. Per accedere al pulsante di opzione [RadioButton1], è necessario scrivere Page.FindControl("frmControls").FindControl("RadioButton1") | |
[Control] identificatore |
Torniamo al codice della procedura [btnEnvoyer_Click]:
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' place information in the listbox
for each c as control in FindControl("frmControls").controls
' is the control derived from CheckBox?
if TypeOf(c) is CheckBox then
lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
end if
next
End Sub
Vogliamo visualizzare lo stato dei pulsanti di opzione e delle caselle di controllo nel modulo. Eseguiamo un'iterazione su tutti i controlli presenti nel modulo. Se il controllo corrente è di un tipo derivato da [CheckBox], visualizziamo la sua proprietà [Checked]. Poiché la classe [RadioButton] deriva dalla classe [CheckBox], il test si applica a entrambi i tipi di controlli. La schermata sopra riportata mostra un esempio dell'output.
7.10. I componenti CheckBoxList e RadioButtonList
A volte, vogliamo consentire all'utente di scegliere tra valori che sono sconosciuti al momento della progettazione della pagina. Queste scelte provengono da un file di configurazione, un database, ecc. e sono note solo in fase di esecuzione. Esistono soluzioni a questo problema e le abbiamo incontrate. L'elenco a selezione singola è adatto quando l'utente può effettuare una sola scelta, mentre l'elenco a selezione multipla è indicato quando può effettuarne diverse. Da un punto di vista estetico, e se il numero di scelte non è elevato, potresti 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 [CheckBoxList] e [RadioButtonList].
Le classi [CheckBoxList] e [RadioButtonList] derivano dalla stessa classe [ListControl] delle classi [DropDownList] e [ListBox] discusse in precedenza. Pertanto, troveremo alcune delle proprietà e dei metodi visti per quelle classi, in particolare quelli che appartengono effettivamente a [ListControl].
Una raccolta di tipo [ListItemCollection] contenente gli elementi dell'elenco a discesa. I membri di questa raccolta sono di tipo [ListItem]. | |
Il numero di elementi nella raccolta [Items] | |
Elemento numero i nell'elenco - di tipo [ListItem] | |
per aggiungere un nuovo elemento [ListItem] alla collezione [Items] | |
per rimuovere tutti gli elementi dalla raccolta [Items] | |
per rimuovere l'elemento numero i dalla raccolta [Items] | |
Il primo [ListItem] nella collezione [Items] la cui proprietà [Selected] è true | |
Indice dell'elemento precedente nella raccolta [Items] |
Alcune proprietà sono specifiche delle classi [CheckBoxList] e [RadioButtonList]:
[horizontal] o [vertical] per elenchi orizzontali o verticali. |
Gli elementi nella raccolta [Items] sono di tipo [ListItem]. Ogni elemento [ListItem] genererà un tag diverso a seconda che si tratti di un oggetto [CheckBoxList] o [RadioButtonList]:
Oppure
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. | |
Boolean. Se true, l'opzione corrispondente nell'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 |
Proponiamo di creare la seguente pagina [form8b.aspx]:

N. | nome | tipo | proprietà | ruolo |
1 | RadioButtonList | EnableViewState=true DirezioneRipetizione=orizzontale | elenco di pulsanti di opzione | |
2 | CheckBoxList | EnableViewState=true DirezioneRipetizione=orizzontale | elenco caselle di controllo | |
3 | Pulsante | Pulsante [Invia] che visualizza in [4] l'elenco degli elementi selezionati da entrambe le liste | ||
4 | Casella di riepilogo | EnableViewState=false | elenco di valori |
Il codice del layout 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 del controllo è il seguente:
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs) handles MyBase.Load
' fill in the lists if it's the 1st time you're called
if not IsPostBack then
' texts for the RadioButton list
dim textesRadio() as String = {"1","2","3","4"}
' texts for the CheckBox list
dim textesCheckBox() as String = {"un","deux","trois","quatre"}
' radio list filling
dim i as integer
for i=0 to textesRadio.length-1
RadioButtonList1.Items.Add(new ListItem(textesRadio(i)))
next
' selection item no. 1
RadioButtonList1.SelectedIndex=1
' checkbox list filling
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)
' place information in the lstinfos listbox
affiche(RadioButtonList1)
affiche(CheckBoxList1)
End Sub
sub affiche(l1 as ListControl)
' displays the values of selected elements of l1
' anything to do?
if l1.selectedindex=-1 then return
dim i as integer
' we start at the end
for i= 0 to l1.items.count-1
' selected?
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 la pagina viene caricata, vengono inizializzati i due elenchi. Per evitare che vengano inizializzati ogni volta, utilizziamo la proprietà [IsPostBack] per farlo solo la prima volta. Nei caricamenti successivi, gli elenchi verranno rigenerati automaticamente dal meccanismo [VIEWSTATE]. Una volta visualizzata la pagina, l'utente seleziona alcune caselle e fa clic sul pulsante [Submit]. I valori del modulo vengono quindi inviati al modulo stesso. Dopo l'esecuzione di [Page_Load], viene eseguita la procedura [btnEnvoyer_Click]. Questa procedura chiama la procedura [affiche] per popolare l'elenco [lstInfos]. Questa procedura riceve un oggetto [ListControl] come parametro, il che le consente di accettare sia un oggetto [RadioButtonList] che un oggetto [CheckBoxList] — classi derivate da [ListControl]. L'attributo [EnableViewState] dell'elenco [lstInfos] può essere impostato su [false] poiché non è necessario mantenere il suo stato tra una richiesta e l'altra.
7.11. I componenti Panel e 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. Questo è il caso della sua proprietà [Visible]. Questa proprietà esiste per ogni controllo lato server. Se un contenitore ha la proprietà [Visible=false], ciascuno dei suoi controlli sarà gestito dalla propria proprietà [Visible]. Se ha la proprietà [Visible=false], allora il contenitore e tutto ciò che contiene non vengono visualizzati. Questo può essere più semplice che gestire la proprietà [Visible] di ciascuno dei controlli del contenitore.
Il tag <asp:LinkButton> consente di inserire un collegamento nel codice di presentazione di una pagina. Ha una funzione simile al controllo [Button]. Attiva un POST lato client utilizzando una funzione JavaScript associata. Creiamo una pagina [form9.aspx] per produrre il seguente layout:

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

No. | nome | tipo | proprietà | ruolo |
4 | LinkButton | EnableViewState=false | link per visualizzare il contenitore |
Il codice del layout 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 di eventi [Click] per i 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)
' displays container 1
panel1.Visible=true
' changing links
lnkVoir.visible=false
lnkCacher.visible=true
End Sub
Sub lnkCacher_Click(sender As Object, e As EventArgs)
' hides container 1
panel1.Visible=false
' changing links
lnkVoir.visible=true
lnkCacher.visible=false
End Sub
</script>
Useremo la procedura [Page_Load] per inizializzare il modulo. Lo faremo alla prima richiesta (IsPostBack=false):
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
' the 1st time
if not IsPostBack then
' the container is shown
lnkVoir_Click(nothing,nothing)
end if
end sub
.....
</script>
7.12. Continua...
I paragrafi precedenti hanno introdotto una serie di componenti lato server. In ogni sezione sono state trattate solo alcune delle loro proprietà. Per approfondire la conoscenza di questi componenti, il lettore può procedere in diversi modi:
- esplorare le proprietà di un componente utilizzando un IDE come WebMatrix. Questo strumento visualizza le proprietà principali dei componenti utilizzati in un modulo
- consultare la documentazione .NET per esplorare tutte le classi corrispondenti a ciascun componente server. Questo è il metodo preferito per acquisire la piena padronanza del componente. Lì troverete la gerarchia delle classi che porta ai componenti, nonché le proprietà, i metodi, i costruttori e gli eventi di ciascuno. Inoltre, la documentazione a volte fornisce degli esempi.
In questo capitolo abbiamo utilizzato l'approccio [WebMatrix] all-in-one, ovvero abbiamo inserito il codice di presentazione e il codice di controllo di una pagina nello stesso file. In generale, non raccomandiamo questo metodo, ma piuttosto l'approccio [codebehind] utilizzato in precedenza, che colloca questi due tipi di codice in due file separati. Vale la pena notare che il vantaggio di questa separazione risiede nel fatto che il codice di controllo può essere compilato senza dover eseguire l'applicazione web. Inoltre, come abbiamo spiegato all'inizio di questo capitolo, i nostri esempi avevano una struttura molto specifica: consistevano in una singola pagina che fungeva da modulo scambiato tra il client e il server in cicli successivi di richiesta-risposta, con la prima richiesta del client che era una richiesta GET e quelle successive che erano richieste POST.
7.13. Componenti del server e controller dell'applicazione
Nei capitoli precedenti abbiamo realizzato diverse applicazioni web. Sono state tutte sviluppate utilizzando l'architettura MVC (Model-View-Controller), che suddivide l'applicazione in blocchi distinti e ne facilita la manutenzione. In precedenza abbiamo realizzato le nostre interfacce utente utilizzando tag HTML standard. Alla luce di quanto appena visto, è naturale voler ora utilizzare componenti server. Riprendiamo un problema che abbiamo già studiato approfonditamente: il calcolo delle imposte. La sua architettura MVC era la seguente:

L'applicazione ha due viste: [form.aspx] e [errors.aspx]. La vista [form.aspx] viene visualizzata quando l'URL [main.aspx] viene richiesto per la prima volta:

L'utente compila il modulo:

e fa clic sul pulsante [Calcola] per ottenere la seguente risposta:

In un'applicazione MVC, ogni richiesta deve passare attraverso il controller, in questo caso [main.aspx]. Ciò significa che una volta che il modulo [form.aspx] è stato compilato dall'utente, deve essere inviato a [main.aspx] e non a [form.aspx]. Questo semplicemente non è possibile se costruiamo l'interfaccia utente [form.aspx] utilizzando i componenti server ASP. Per verificarlo, creiamo 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 visualizzata mostra solo un 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>
Possiamo notare che la richiesta POST del modulo è indirizzata al modulo stesso [action="formtest.aspx"], mentre avevamo scritto il tag HTML lato server in [formtest.aspx]:
L'attributo [runat="server"] del tag <form> è necessario quando si utilizzano componenti lato server. Se non includiamo questo attributo, si verifica un errore di compilazione. Quando lo includiamo, l'attributo [action] del tag <form> viene ignorato. Il server genera sempre un attributo [action] che punta al modulo stesso. Possiamo concludere che in un'applicazione MVC non è possibile utilizzare moduli creati con il tag <form ... runat="server">. Tuttavia, questo tag è essenziale per tutti i componenti server ASP che recuperano gli input dell'utente. In altre parole, non è possibile utilizzare i moduli server ASP in un'applicazione MVC. Si tratta di una rivelazione importante. Infatti, uno dei principali punti di forza di ASP.NET è la possibilità di creare un'applicazione web simile a un'applicazione Windows. Ciò è vero se l'applicazione non segue l'architettura MVC, ma lo è meno in caso contrario. Tuttavia, l'architettura MVC sembra essere un concetto fondamentale nello sviluppo web moderno, difficile da ignorare.
È possibile utilizzare l'architettura MVC in combinazione con i moduli dei componenti ASP per applicazioni con poche viste diverse utilizzando la seguente soluzione alternativa:
- l'applicazione è costituita da una singola pagina che funge da controller
- le viste sono rappresentate su questa pagina da diversi contenitori, un contenitore per ogni vista. Per visualizzare una vista, rendiamo visibile il suo contenitore e nascondiamo gli altri
Si tratta di una soluzione elegante che ora implementeremo 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] avrà il seguente aspetto:
![]() | ![]() |
La schermata in alto a sinistra mostra il modulo così come appare all'utente. L'utente lo compila e lo invia cliccando su [Invia]. Il server restituisce una vista che mostra un elenco dei valori inseriti (schermata a destra). Un link permette all'utente di tornare al modulo. Lo vedrà esattamente come lo ha inviato. Il codice di presentazione per [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 del modulo, [panelinfo] per la vista delle informazioni. L'elenco dei componenti nel contenitore [panelForm] è il seguente:
nome | tipo | proprietà | ruolo |
Pannello | EnableViewState=true | vista del modulo | |
Pulsante di opzione | EnableViewState=true GroupName=rdmarie | pulsanti di opzione | |
Casella di controllo | EnableViewState=true | caselle di controllo | |
Casella di testo | EnableViewState=true | campo di immissione | |
TextBox | EnableViewState=true | campo di immissione protetto | |
Casella di testo | EnableViewState=true | casella di testo multilinea | |
Elenco a discesa | EnableViewState=true | elenco a discesa | |
ListBox | EnableViewState=true ModalitàSelezione=Singola | elenco a selezione singola | |
Pulsante | EnableViewState=false | deseleziona tutti gli elementi in lstSimple | |
Casella di riepilogo | EnableViewState=true SelectionMode=Multiple | elenco a selezione multipla | |
Pulsante | EnableViewState=false | Deseleziona tutti gli elementi in lstMultiple | |
Etichetta | EnableViewState=true Visible=false | campo nascosto | |
HTML standard | visualizza un messaggio di avviso | ||
Pulsante | EnableViewState=false | Pulsante [submit] nel modulo | |
HTML standard | Pulsante [reset] del modulo |
Il ruolo di [VIEWSTATE] per i componenti è importante in questo caso. Tutti i componenti, eccetto i pulsanti, devono avere la proprietà [EnableViewState=true]. Per capire il motivo, dobbiamo ricordare come funziona l'applicazione. Supponiamo che il campo [txtSaisie] abbia la proprietà [EnableViewState=false]:
- Il client richiede la pagina [form10.aspx] per la prima volta. Riceve la vista del modulo
- , la compila e la invia utilizzando il pulsante [Submit]. I campi di input vengono quindi inviati e il server assegna il valore inviato o il loro [VIEWSTATE] ai componenti lato server, se ne possiedono uno. Pertanto, al campo [txtSaisie] viene assegnato il valore inserito dall'utente. Pertanto, in questa fase, il suo [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 di visualizzazione diverso.
- L'utente visualizza questa nuova vista e utilizza il link [Torna al modulo] per ritornarvi. Viene quindi inviata una richiesta POST a [form10.aspx]. Al massimo, viene inviato un solo valore: quello selezionato dall'utente dall'elenco delle informazioni, che non verrà utilizzato in seguito. In ogni caso, non viene inviato alcun valore dal campo [txtSaisie].
- Il server riceve il POST e assegna il valore inviato ai componenti del server o al loro [VIEWSTATE], se ne hanno 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é vogliamo che abbia il valore inserito dall'utente, deve avere la proprietà [EnableViewState=true].
Il contenitore [panelinfo] presenta i seguenti controlli:
name | type | proprietà | ruolo |
Pannello | EnableViewState=false | visualizza informazioni | |
Casella di riepilogo | EnableViewState=false | Elenco di informazioni che riassumono i valori inseriti dall'utente | |
LinkButton | EnableViewState=false | link di ritorno al modulo |
Durante il test, se osserviamo il codice HTML generato dal codice di presentazione sopra riportato, potremmo rimanere sorpresi dal codice generato per il campo nascosto [lblSecret]:
Il componente [lblSecret] non viene visualizzato come HTML perché ha la proprietà [Visible=false]. Tuttavia, poiché ha la proprietà [EnableViewState=true], il suo valore verrà comunque memorizzato nel campo nascosto [__VIEWSATE]. Pertanto, saremo in grado di recuperarlo, come dimostreranno i test.
Dobbiamo ancora scrivere i gestori di eventi. In [Page_Load], inizializzeremo il modulo:
Sub page_Load(sender As Object, e As EventArgs)
' the 1st time, we initialize the elements
' subsequent times, they are reset to their values by VIEWSTATE
if IsPostBack then return
' init form
' panelinfo not displayed
panelinfo.visible=false
' paneform displayed
panelform.visible=true
' radio buttons
rdNon.Checked=true
' checkboxes
chk2.Checked=true
' input field
txtSaisie.Text="qqs mots"
' password field
txtMdp.Text="ceciestsecret"
' input box
txtArea.Text="ligne"+ControlChars.CrLf+"ligne2"+ControlChars.CrLf
' combo
dim i as integer
for i=1 to 4
cmbValeurs.Items.Add(new ListItem("choix"+i.ToString,i.ToString))
next
cmbValeurs.SelectedIndex=1
' simple selection list
for i=1 to 7
lstSimple.Items.Add(new ListItem("simple"+i.ToString,i.ToString))
next
lstSimple.SelectedIndex=0
' multiple selection list
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
' hidden field
lblSecret.Text="secret"
End Sub
Facendo clic sui pulsanti [lstRazSimple] e [lstMultiple]:
Sub btnRazSimple_Click(sender As Object, e As EventArgs)
' raz single list
lstSimple.SelectedIndex=-1
End Sub
Sub razMultiple_Click(sender As Object, e As EventArgs)
' raz multiple list
lstMultiple.SelectedIndex=-1
End Sub
Facendo clic sul pulsante [Invia]:
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' the info panel is made visible and the form panel is hidden
panelinfo.Visible=true
panelform.visible=false
' we retrieve the posted values and put them in lstInfos
' radio buttons
dim info as string="état marital : "+iif(rdoui.checked,"marié"," non marié")
affiche(info)
' checkboxes
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)
' input field
affiche("champ de saisie : " + txtSaisie.Text.Trim)
' password
affiche("mot de passe : " + txtMdp.Text.Trim)
' input box
dim lignes() as String
lignes=new Regex("\r\n").Split(txtArea.Text.Trim)
dim i as integer
for i=0 to lignes.length-1
lignes(i)="["+lignes(i).Trim+"]"
next
affiche("Boîte de saisie : " + String.Join(",",lignes))
' combo
affiche("éléments sélectionnés dans combo : "+selection(cmbValeurs))
' simple list
affiche("éléments sélectionnés dans liste simple : "+selection(lstSimple))
' multiple list
affiche("éléments sélectionnés dans liste multiple : "+selection(lstMultiple))
' hidden field
affiche ("Champ caché : " + lblSecret.Text)
End Sub
sub affiche(msg as String)
' displays msg in lstInfos
lstInfos.Items.Add(msg)
end sub
function selection(liste as ListControl) as string
' browse list elements
' to find those selected
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 [Torna al modulo]:
Sub LinkButton1_Click(sender As Object, e As EventArgs)
' display the form and hide the info panel
panelform.visible=true
panelinfo.visible=false
End Sub
7.14.2. Esempio 2
Qui, stiamo riesaminando un'applicazione trattata in precedenza che utilizza moduli HTML standard. L'applicazione consente agli utenti di eseguire simulazioni di calcolo delle imposte. Si basa su una classe [impot], che non riprenderemo qui. Questa classe richiede dati che recupera da un'origine dati OLEDB. Per questo esempio, useremo un'origine dati 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] come contenitori. Pertanto, questa applicazione ha una singola pagina [main.aspx].
7.14.2.2. Le viste dell'applicazione web
La vista [form] è il modulo per l'inserimento delle informazioni utilizzate per calcolare l'imposta di un utente:

L'utente compila il modulo:

Utilizza il pulsante [Invia] per richiedere il calcolo delle imposte. Viene visualizzata la seguente schermata [simulazioni]:

Torna al modulo tramite il link in alto. Lo trova nello stato in cui lo ha compilato. Potrebbe commettere errori nell'inserimento dei dati:

Questi vengono segnalati dalla schermata [errori]:

Torna al modulo tramite il link in alto. Lo trova nello stato in cui lo ha inserito. Può eseguire nuove simulazioni:

Vede quindi la vista [simulazioni] con una simulazione aggiuntiva:

Infine, se la fonte dei dati non è disponibile, l'utente viene avvisato nella vista [errori]:

7.14.2.3. Il codice di presentazione dell'applicazione
Ricordate che la pagina [main.aspx] riunisce tutte le viste. Si tratta di un unico modulo con tre contenitori:
- [panelform] per la vista [form]
- [panelerrors] per la vista [errori]
- [panelsimulations] per la vista [simulations]
Torniamo a separare il codice di presentazione e il codice di controllo in due file distinti. Il primo sarà in [main.aspx] e il secondo in [main.aspx.vb]. Il codice per [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 definito i tre contenitori. Si noti che sono tutti all'interno del tag <form runat="server">. Questo è obbligatorio, perché per sfruttare i componenti server, questi devono essere inseriti all'interno di tale tag. Il punto chiave da comprendere è che qui abbiamo un unico modulo che verrà scambiato tra il client e il server web. Stiamo quindi utilizzando la configurazione descritta in questo capitolo sui componenti server. Analizziamo i componenti di ciascun contenitore:
Contenitore [panelform]:
nome | tipo | proprietà | ruolo |
Pannello | EnableViewState=true | vista modulo | |
Pulsante di opzione | EnableViewState=true NomeGruppo=rdmarie | pulsanti di opzione | |
Casella di testo | EnableViewState=true | numero di figli | |
Casella di testo | EnableViewState=true | stipendio annuale | |
Pulsante | Pulsante [Invia] nel modulo - avvia il calcolo delle imposte | ||
Pulsante | Pulsante [Invia] del modulo - cancella il modulo |
Contenitore [errorPanel]:
nome | tipo | proprietà | ruolo |
Pannello | EnableViewState=true | vista errori | |
LinkButton | EnableViewState=true | link al modulo | |
Letterale | Codice HTML per l'elenco degli errori |
Contenitore [panelsimulations]:
nome | tipo | proprietà | ruolo |
Pannello | EnableViewState=true | vista simulazione | |
LinkButton | EnableViewState=true | collegamento al modulo | |
Literal | Codice HTML per l'elenco delle simulazioni in una tabella HTML |
7.14.2.4. Il codice di controllo dell'applicazione
Il codice di controllo dell'applicazione è distribuito tra i 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)
' create an impot object
Dim objImpot As impot
Try
objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
' put the object in the application
Application("objImpot") = objImpot
' no error
Application("erreur") = False
Catch ex As Exception
'there has been an error, we note it in the application
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' start of session - create a list of empty simulations
Session.Item("simulations") = New ArrayList
End Sub
End Class
All'avvio dell'applicazione (prima richiesta effettuata all'applicazione), viene eseguita la procedura [Application_Start]. Essa tenta di creare un oggetto di tipo [impot] recuperando i suoi 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] potrebbe fallire se la fonte dei dati non è disponibile. In questo caso, l'errore viene memorizzato nell'applicazione in modo che tutte le richieste successive sappiano che non è stato possibile inizializzarlo correttamente. Se la creazione ha esito positivo, anche l'oggetto [impot] creato viene memorizzato nell'applicazione. Verrà utilizzato da tutte le richieste di calcolo delle imposte. Quando un client effettua la sua prima richiesta, la procedura [Application_Start] crea una sessione per lui. Questa sessione ha lo scopo di memorizzare le varie simulazioni di calcolo delle imposte che il client effettuerà. Queste saranno memorizzate in un oggetto [ArrayList] associato alla chiave di sessione "simulations". All'avvio della sessione, questa chiave è associata a un oggetto [ArrayList] vuoto. Le informazioni richieste dall'applicazione sono inserite nel suo 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 [connectionString] specifica la stringa di connessione alla fonte OLEDB. Il resto 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
' local variables
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
' first, we look at the state of the application
If CType(Application("erreur"), Boolean) Then
' the application failed to initialize
' the error view is displayed
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
afficheErreurs(erreurs, "")
Exit Sub
End If
' no errors - on the 1st request, the form is presented
If Not IsPostBack Then afficheFormulaire()
End Sub
Ricordiamo che quando la procedura [Page_Load] viene eseguita su un POST del client, tutti i componenti del modulo hanno un valore: o il valore inviato dal client, se presente, oppure il valore precedente del componente tramite [VIEWSTATE]. In questo modulo, tutti i componenti hanno la proprietà [EnableViewState=true]. Prima di elaborare la richiesta, ci assicuriamo che l'applicazione sia stata inizializzata correttamente. In caso contrario, visualizziamo la vista [errors] utilizzando la procedura [displayErrors]. Se questa è la prima richiesta (IsPostBack=false), visualizziamo la vista [form] utilizzando [displayForm].
La procedura che visualizza la vista [errors] è la seguente:
Private Sub afficheErreurs(ByRef erreurs As ArrayList, ByRef lien As String)
' displays the error container
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
' the other containers are hidden
panelform.Visible = False
panelsimulations.Visible = False
End Sub
La procedura ha due parametri:
- un elenco di messaggi di errore in [errors]
- un testo di collegamento in [link]
Il codice HTML da generare per l'elenco degli errori è inserito nel letterale [errorsHTML]. Il testo del collegamento è inserito nella proprietà [Text] dell'oggetto [LinkButton] nella vista.
La procedura che visualizza la vista [form] è la seguente:
Private Sub afficheFormulaire()
' displays the form
panelform.Visible = True
' the other containers are hidden
panelerreurs.Visible = False
panelsimulations.Visible = False
End Sub
Questa procedura rende semplicemente visibile il contenitore [panelform]. I componenti vengono visualizzati con il loro valore inviato o precedente (VIEWSTATE).
Quando l'utente fa clic sul pulsante [Calculate] nella vista [form], viene inviata una richiesta POST a [main.aspx]. Viene eseguita la procedura [Page_Load], seguita dalla procedura [btnCalculate_Click]:
Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
' check the validity of the data entered
Dim erreurs As ArrayList = checkData()
' if there are errors, we report them
If erreurs.Count <> 0 Then
' the error page is displayed
afficheErreurs(erreurs, "Retour au formulaire")
Exit Sub
End If
' no errors - tax is calculated
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
rdOui.Checked, CType(txtEnfants.Text, Integer), CType(txtSalaire.Text, Long))
' the result is added to existing simulations
Dim simulation() As String = New String() {CType(IIf(rdOui.Checked, "oui", "non"), String), _
txtEnfants.Text.Trim, txtSalaire.Text.Trim, impot.ToString}
' the result is added to existing simulations
Dim simulations As ArrayList = CType(Session.Item("simulations"), ArrayList)
simulations.Add(simulation)
' put simulations in session and context
Session.Item("simulations") = simulations
' the result page is displayed
afficheSimulations(simulations, "Retour au formulaire")
End Sub
La procedura inizia convalidando i campi del modulo utilizzando la procedura [checkData], che restituisce un [ArrayList] di messaggi di errore. Se l'elenco non è vuoto, viene visualizzata la vista [errors] e la procedura termina. Se i dati inseriti sono validi, l'importo dell'imposta viene calcolato utilizzando l'oggetto [tax] che è stato memorizzato nell'applicazione all'avvio. Questa nuova simulazione viene aggiunta all'elenco delle simulazioni già eseguite e memorizzate nella sessione.
La funzione [CheckData] verifica la validità dei dati. Restituisce un [ArrayList] di messaggi di errore, che è vuoto se i dati sono validi:
Private Function checkData() As ArrayList
' initially no errors
Dim erreurs As New ArrayList
' no. of children
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
' salary
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
' return the list of errors
Return erreurs
End Function
Infine, la vista [simulations] viene visualizzata dalla seguente procedura [displaySimulations]:
Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
' displays the simulations view
panelsimulations.Visible = True
' the other containers are hidden
panelerreurs.Visible = False
panelform.Visible = False
' contents of simulations view
' each simulation is an array of 4 string elements
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 [link]
Il codice HTML da generare per l'elenco delle simulazioni è memorizzato nel letterale [simulationsHTML]. Il testo del link è memorizzato nella proprietà [Text] dell'oggetto [LinkButton] nella vista.
Quando l'utente fa clic sul pulsante [Clear] nella vista [form], viene eseguita la procedura [btnClear_click] (sempre dopo [Page_Load]):
Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
' displays the empty form
razForm()
afficheFormulaire()
End Sub
Private Sub razForm()
' empty the form
rdOui.Checked = False
rdNon.Checked = True
txtEnfants.Text = ""
txtSalaire.Text = ""
End Sub
Il codice sopra riportato è talmente semplice che non necessita di commenti. Dobbiamo ancora gestire i clic sui collegamenti delle viste [errori] e [simulazioni]:
Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
' displays the form
afficheFormulaire()
End Sub
Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
' displays the form
afficheFormulaire()
End Sub
Entrambe le procedure visualizzano semplicemente la vista [form]. Sappiamo che i campi del modulo riceveranno un valore che è o il valore inviato per essi o il loro valore precedente. Poiché, in questo caso, la richiesta POST del client non invia alcun valore per i campi del modulo, questi torneranno ai loro valori precedenti. Il modulo viene quindi visualizzato con i valori inseriti dall'utente. Ricordiamo che, nella versione senza componenti server, eseguivamo noi stessi questo ripristino.
7.14.2.5. Test
Tutti i file richiesti dall'applicazione sono collocati in una cartella denominata <application-path>: ![]() | La cartella [bin] contiene la DLL con le classi [impot], [impotsData] e [impotsOLEDB] richieste dall'applicazione: |
Il lettore può, se lo desidera, tornare al Capitolo 5, che spiega come creare il file [impot.dll] menzionato sopra. Una volta fatto ciò, il server Cassini viene avviato con i parametri (<percorso-applicazione>,/impots5). Richiediamo l'URL [http://impots5/main.aspx] utilizzando un browser:

Se rinominiamo il file ACCESS [impots.mdb] in [impots1.mdb], vedremo la seguente pagina:

7.14.3. Esempio 3
In questi due esempi abbiamo dimostrato che è possibile creare applicazioni web seguendo l'architettura MVC utilizzando componenti lato server. L'ultimo esempio dimostra che la soluzione con componenti lato server è più semplice rispetto a quella che utilizza tag HTML standard. I nostri due esempi avevano 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 moduli inviano i propri valori a se stessi non costituisca un problema. Questo è molto spesso il caso delle applicazioni che dispongono di 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. L'unica differenza è che non c'è più un solo controller, ma diversi.

Il controller [main.aspx] funge da controller principale. È quello chiamato dai link presenti nella home page dell'applicazione. Eseguirà le operazioni comuni a tutte le possibili azioni e poi eseguirà l'azione specifica associata al link utilizzato. Passerà quindi il testimone a uno dei controller secondari, quello responsabile dell'esecuzione dell'azione. Da questo punto in poi, la comunicazione avviene tra il client e questo specifico controller. Non passiamo più attraverso il controller principale [main.aspx]. Non siamo quindi più nel framework MVC con un unico controller che filtra tutte le richieste. Ciascuno dei controller sopra citati può presentare più viste utilizzando il meccanismo del contenitore all'interno di una singola pagina, come abbiamo descritto.
Il fatto di non avere più un unico controller che sceglie quali viste inviare al client presenta alcuni svantaggi. Prendiamo ad esempio la gestione degli errori. Ogni azione esposta dall'applicazione potrebbe dover visualizzare una vista di errore. Ogni controller [applix.aspx] avrà la propria vista [errors] perché si tratta semplicemente di un contenitore specifico all'interno della pagina del controller. Non c'è modo di avere un'unica vista [errors] che possa essere utilizzata da tutte le singole applicazioni. Infatti, una vista di questo tipo include tipicamente un link di ritorno al modulo con gli errori, e quel modulo deve essere riportato allo stato in cui era stato convalidato per consentire all'utente di correggere i propri errori. Questo ripristino viene eseguito tramite il meccanismo [VIEWSTATE], che non funziona tra controller diversi. Se le applicazioni sono sviluppate da persone diverse, c'è il rischio di avere pagine di errore che appaiono diverse a seconda dell'azione scelta dall'utente, il che compromette la coerenza dell'applicazione nel suo complesso. Vedremo poco più avanti che ASP.NET offre una soluzione a questo specifico problema della condivisione delle viste. Questa può essere implementata come un nuovo componente server che costruiamo noi stessi. Il semplice utilizzo di questo componente nelle varie applicazioni garantisce la coerenza dell'applicazione nel suo complesso. Più difficile da gestire è la questione dell'ordine delle azioni. Quando tutte le richieste passano attraverso un unico controller, questo può verificare che l'azione richiesta sia compatibile con quella precedente. Questo codice di convalida si trova in un unico posto. In questo caso, dovrà essere distribuito tra i vari controller, complicando la manutenzione dell'applicazione nel suo complesso.
Torniamo alla nostra applicazione di cui sopra. La sua pagina di ingresso è una pagina HTML standard [home.htm]:
<html>
<head>
<TITLE>Composants ASP Serveur</TITLE>
<meta name="pragma" content="no-cache">
</head>
<frameset rows="130,*" frameborder="0">
<frame name="banner" src="bandeau.htm" scrolling="no">
<frameset cols="200,*">
<frame name="contents" src="options.htm">
<frame name="main" src="main.htm">
</frameset>
<noframes>
<p id="p1">
Ce jeu de frames HTML affiche plusieurs pages Web. Pour afficher ce jeu de
frames, utilisez un navigateur Web qui prend en charge HTML 4.0 et version
ultérieure.
</p>
</noframes>
</frameset>
</html>
Questa home page è composta da tre frame denominati banner, contents e main:
![]() |
La pagina [bandeau.htm] inserita nel frame [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] si trova 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. Specifichiamo che la destinazione del link debba essere visualizzata nel frame [main] (target="main").
La prima pagina visualizzata nel frame [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 sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' recupera l'azione da eseguire
Dim azione As String
Se Request.QueryString("action") è nulla, allora
azione = "label"
Else
azione = Request.QueryString("azione").ToString.ToLower
Fine Se
' esegui l'azione
Select Case azione
Case "label"
Server.Transfer("form2.aspx")
Case "pulsante"
Server.Transfer("form3.aspx")
Case "casella di testo1"
Server.Transfer("form4.aspx")
Campo "textbox2"
Server.Transfer("form5.aspx")
Case "dropdownlist"
Server.Transfer("form6.aspx")
Caso "listbox"
Server.Transfer("form7.aspx")
Case "checkbox"
Server.Transfer("form8.aspx")
Case "checkboxlist"
Server.Transfer("form8b.aspx")
Case "pannello"
Server.Transfer("form9.aspx")
Caso Altro
Server.Transfer("form2.aspx")
Fine selezione
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 collegamenti. Tuttavia, la semplice aggiunta di una pagina di autenticazione ne dimostrerebbe l'utilità. Se l'utente dovesse autenticarsi (nome utente, password) per accedere alle applicazioni, il controller [main.aspx] sarebbe un buon punto in cui verificare che tale autenticazione sia stata completata.




