Skip to content

2. Una breve introduzione ad ASP.NET

In questa sezione intendiamo introdurre, utilizzando alcuni esempi, i concetti di ASP.NET che saranno utili più avanti in questo documento. Questa introduzione non tratta le complessità della comunicazione client/server in un'applicazione web. A tal fine, è possibile consultare:

Questa introduzione è destinata a coloro che desiderano iniziare rapidamente, accettando, almeno inizialmente, che alcuni punti potenzialmente importanti possano rimanere senza spiegazione. Il resto di questo documento approfondirà tali punti. Chi ha familiarità con ASP.NET può passare direttamente alla Sezione 3.

2.1. Un progetto di esempio

2.1.1. Creazione del progetto

  • In [1], creare un nuovo progetto con Visual Web Developer
  • In [2], selezionare un progetto Web Visual C#
  • In [3], specificare che si desidera creare un'applicazione Web ASP.NET
  • In [4], assegnare un nome all'applicazione. Verrà creata una cartella per il progetto con questo nome.
  • In [5], specificare la cartella principale della cartella del progetto [4]
  • In [6], il progetto creato
  • [Default.aspx] è una pagina Web creata per impostazione predefinita. Contiene tag HTML e tag ASP.NET
  • [Default.aspx.cs] contiene il codice per la gestione degli eventi attivati dall'utente sulla pagina [Default.aspx] visualizzata nel browser
  • [Default.aspx.designer.cs] contiene l'elenco dei componenti ASP.NET per la pagina [Default.aspx]. Ogni componente ASP.NET posizionato sulla pagina [Default.aspx] genera una dichiarazione per quel componente in [Default.aspx.designer.cs].
  • [Web.config] è il file di configurazione del progetto ASP.NET.
  • [Riferimenti] è l'elenco delle DLL utilizzate dal progetto web. Queste DLL sono librerie di classi che il progetto deve utilizzare. In [7] è riportato l'elenco delle DLL incluse per impostazione predefinita nei riferimenti del progetto. La maggior parte di esse non è necessaria. Se il progetto deve utilizzare una DLL non elencata in [7], è possibile aggiungerla tramite [8].

2.1.2. La pagina [Default.aspx]

Se si esegue il progetto utilizzando [Ctrl-F5], la pagina [Default.aspx] viene visualizzata in un browser:

  • in [1], l'URL del progetto web. Visual Web Developer dispone di un server web integrato che si avvia quando si esegue un progetto. Esso è in ascolto su una porta casuale, in questo caso la 1490. La porta di ascolto è solitamente la porta 80. In [1], non viene richiesta alcuna pagina. In questo caso, viene visualizzata la pagina [Default.aspx], da cui il suo nome di pagina predefinita.
  • In [2], la pagina [Default.aspx] è vuota.
  • In Visual Web Developer, la pagina [Default.aspx] [3] può essere creata visivamente (scheda [Design]) o utilizzando i tag (scheda [Source])
  • In [4], la pagina [Default.aspx] in modalità [Design]. Viene creata trascinando i componenti presenti nella casella degli strumenti [5].

La modalità [Source] [6] consente di accedere al codice sorgente della pagina:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
  </div>
  </form>
</body>
</html>
  • La riga 1 è una direttiva ASP.NET che elenca alcune proprietà della pagina
    • La direttiva Page si applica a una pagina web. Esistono altre direttive, come Application, WebService, ecc., che si applicano ad altri oggetti ASP.NET
    • L'attributo CodeBehind specifica il file che gestisce gli eventi della pagina
    • L'attributo Language specifica il linguaggio .NET utilizzato dal file CodeBehind
    • L'attributo Inherits specifica il nome della classe definita all'interno del file CodeBehind
    • L'attributo AutoEventWireUp="true" indica che il collegamento tra un evento in [Default.aspx] e il relativo gestore in [Default.aspx.cs] avviene tramite il nome dell'evento. Pertanto, l'evento Load sulla pagina [Default.aspx] sarà gestito dal metodo Page_Load della classe Intro._Default definita dall'attributo Inherits.
  • Le righe 4-14 descrivono la pagina [Default.aspx] utilizzando i tag:
    • Tag HTML classici come <body> o <div>
    • Tag ASP.NET. Si tratta dei tag con l'attributo runat="server". I tag ASP.NET vengono elaborati dal server web prima che la pagina venga inviata al client. Vengono convertiti in tag HTML. Il browser del client riceve quindi una pagina HTML standard in cui non sono più presenti tag ASP.NET.

La pagina [Default.aspx] può essere modificata direttamente dal suo codice sorgente. A volte questo è più semplice rispetto all'utilizzo della modalità [Design]. Modifichiamo il codice sorgente come segue:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Introduction ASP.NET</title>
</head>
<body>
  <h3>Introduction à ASP.NET</h3>
  <form id="form1" runat="server">
  <div>
  </div>
  </form>
</body>
</html>

Alla riga 6, assegniamo un titolo alla pagina utilizzando il tag HTML <title>. Alla riga 9, inseriamo del testo nel corpo (<body>) della pagina. Se eseguiamo il progetto (Ctrl-F5), otteniamo il seguente risultato nel browser:

 

2.1.3. I file [Default.aspx.designer.cs] e [Default.aspx.cs]

Il file [Default.aspx.designer.cs] dichiara i componenti della pagina [Default.aspx]:


//------------------------------------------------------------------------------
// <auto-generated>
//      This code was generated by a tool.
//      Runtime version :2.0.50727.3603
//
//      Changes made to this file may cause incorrect behavior and will be lost if
//      the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
 
namespace Intro {
    
 
    public partial class _Default {
 
        /// <summary>
        /// Control form1.
        /// </summary>
        /// <remarks>
        /// Automatically generated field.
        /// To modify, move the field declaration from the designer file to the code-behind file.
        /// </remarks>
        protected global::System.Web.UI.HtmlControls.HtmlForm form1;
    }
}

Questo file contiene l'elenco dei componenti ASP.NET presenti nella pagina [Default.aspx] che dispongono di un identificatore. Essi corrispondono ai tag presenti in [Default.aspx] che possiedono l'attributo runat="server" e l'attributo id. Pertanto, il componente alla riga 23 sopra riportato corrisponde al tag


  <form id="form1" runat="server">

in [Default.aspx].

Lo sviluppatore interagisce raramente con il file [Default.aspx.designer.cs]. Tuttavia, questo file è utile per determinare la classe di un componente specifico. Come mostrato di seguito, il componente form1 è di tipo HtmlForm. Lo sviluppatore può quindi esplorare questa classe per conoscere le sue proprietà e i suoi metodi. I componenti nella pagina [Default.aspx] sono utilizzati dalla classe nel file [Default.aspx.cs]:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
 
namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {
 
    }
  }
}

Si noti che la classe definita nei file [Default.aspx.cs] e [Default.aspx.designer.cs] è la stessa (riga 10): Intro._Default. È la parola chiave partial che rende possibile estendere una dichiarazione di classe su più file, in questo caso due.

Nella riga 10 sopra, vediamo che la classe [_Default] estende la classe [Page] ed eredita i suoi eventi. Uno di questi è l'evento Load, che si verifica quando la pagina viene caricata dal server web. Alla riga 12, il metodo Page_Load gestisce l'evento Load della pagina. Questo è generalmente il punto in cui la pagina viene inizializzata prima di essere visualizzata nel browser del client. Qui, il metodo Page_Load non fa nulla.

La classe associata a una pagina web — in questo caso, la classe Intro._Default — viene creata all'inizio della richiesta del client e distrutta una volta che la risposta è stata inviata al client. Non può quindi essere utilizzata per memorizzare informazioni tra una richiesta e l'altra. Per farlo, è necessario ricorrere al concetto di sessione utente.

2.2. Eventi di una pagina Web ASP.NET

Stiamo creando la seguente pagina [Default.aspx]:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Introduction ASP.NET</title>
</head>
<body>
  <h3>Introduction à ASP.NET</h3>
  <form id="form1" runat="server">
  <div>
    <table>
      <tr>
        <td>
          Nom</td>
        <td>
          <asp:TextBox ID="TextBoxNom" runat="server"></asp:TextBox>
        </td>
        <td>
          &nbsp;</td>
      </tr>
      <tr>
        <td>
          Age</td>
        <td>
          <asp:TextBox ID="TextBoxAge" runat="server"></asp:TextBox>
        </td>
        <td>
          &nbsp;</td>
      </tr>
    </table>
  </div>
  <asp:Button ID="ButtonValider" runat="server" Text="Valider" />
  <hr />
  <p>
    Evénements traités par le serveur</p>
  <p>
    <asp:ListBox ID="ListBoxEvts" runat="server"></asp:ListBox>
  </p>
  </form>
</body>
</html>

La vista [Design] della pagina è la seguente:

 

Il file [Default.aspx.designer.cs] è il seguente:


namespace Intro {
    public partial class _Default {
        protected global::System.Web.UI.HtmlControls.HtmlForm form1;
        protected global::System.Web.UI.WebControls.TextBox TextBoxNom;
        protected global::System.Web.UI.WebControls.TextBox TextBoxAge;
        protected global::System.Web.UI.WebControls.Button ButtonValider;
        protected global::System.Web.UI.WebControls.ListBox ListBoxEvts;
    }
}

Questo contiene tutti i componenti ASP.NET della pagina [Default.aspx] che dispongono di un identificatore.

Modifichiamo il file [Default.aspx.cs] come segue:


using System;
 
namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
    protected void Page_Init(object sender, EventArgs e)
    {
      // the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Init", DateTime.Now.ToString("hh:mm:ss")));
    }
 
    protected void Page_Load(object sender, EventArgs e)
    {
      // the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
    }
 
    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
    }
  }
}

La classe [_Default] (riga 5) gestisce tre eventi:

  • l'evento Init (riga 7), che si verifica quando la pagina è stata inizializzata
  • l'evento Load (riga 13), che si verifica quando la pagina è stata caricata dal server web. L'evento Init si verifica prima dell'evento Load.
  • l'evento Click sul pulsante ButtonValider (riga 19), che si verifica quando l'utente fa clic sul pulsante [Validate]

La gestione di ciascuno di questi tre eventi comporta l'aggiunta di un messaggio al componente Listbox denominato ListBoxEvts. Questo messaggio visualizza l'ora e il nome dell'evento. Ogni messaggio viene posizionato in cima all'elenco. Pertanto, i messaggi in cima all'elenco sono i più recenti.

Quando il progetto viene eseguito, viene visualizzata la pagina seguente:

Possiamo vedere in [1] che gli eventi Page_Init e Page_Load si sono verificati in quell'ordine. Ricordiamo che l'evento più recente si trova in cima all'elenco. Quando il browser richiede la pagina [Default.aspx] direttamente tramite il suo URL [2], lo fa utilizzando un comando HTTP (HyperText Transfer Protocol) chiamato GET. Una volta che la pagina è stata caricata nel browser, l'utente attiverà degli eventi sulla pagina. Ad esempio, farà clic sul pulsante [Invia] [3]. Gli eventi attivati dall'utente una volta che la pagina è stata caricata nel browser avviano una richiesta alla pagina [Default.aspx], ma questa volta utilizzando un comando HTTP chiamato POST. Riassumendo:

  • il caricamento iniziale di una pagina P in un browser avviene tramite un'operazione HTTP GET
  • gli eventi che si verificano successivamente sulla pagina generano ogni volta una nuova richiesta alla stessa pagina P, ma questa volta utilizzando un comando HTTP POST. Una pagina P può determinare se è stata richiesta utilizzando un comando GET o POST, consentendole di comportarsi in modo diverso se necessario, cosa che di solito avviene.

Richiesta iniziale per una pagina ASPX: GET

  • In [1], il browser richiede la pagina ASPX tramite una richiesta HTTP GET senza parametri.
  • In [2], il server web restituisce l'output HTML della pagina ASPX richiesta.

Gestione di un evento attivato sulla pagina visualizzata dal browser: POST

  • In [1], quando si verifica un evento sulla pagina HTML, il browser richiede la pagina ASPX precedentemente recuperata tramite un'operazione GET, questa volta utilizzando una richiesta HTTP POST accompagnata da parametri. Questi parametri sono i valori dei componenti situati all'interno del tag <form> della pagina HTML visualizzata dal browser. Questi valori sono chiamati valori inviati dal client. Saranno utilizzati dalla pagina ASPX per elaborare la richiesta del client.
  • In [2], il server web rinvia l'output HTML della pagina ASPX inizialmente richiesta tramite POST, o di un'altra pagina se si è verificato un trasferimento o un reindirizzamento.

Torniamo alla nostra pagina di esempio:

  • In [2], la pagina è stata recuperata tramite una richiesta GET.
  • In [1], vediamo i due eventi che si sono verificati durante questa richiesta GET

Se, sopra, l'utente fa clic sul pulsante [Validate] [3], la pagina [Default.aspx] verrà richiesta tramite una richiesta POST. Questa POST sarà accompagnata da parametri che sono i valori di tutti i componenti inclusi nel tag <form> della pagina [Default.aspx]: le due caselle di testo [TextBoxName, TextBoxAge], il pulsante [SubmitButton] e l'elenco [EventListBox]. I valori inviati per i componenti sono i seguenti:

  • TextBox: il valore inserito
  • Pulsante: il testo del pulsante, in questo caso "Convalida"
  • ListBox: il testo del messaggio selezionato nella ListBox

In risposta al POST, otteniamo la pagina [4]. Si tratta ancora una volta della pagina [Default.aspx]. Questo è un comportamento normale, a meno che non vi sia un trasferimento di pagina o un reindirizzamento da parte dei gestori di eventi della pagina. Possiamo notare che si sono verificati due nuovi eventi:

  • l'evento Page_Load, che si verificava al caricamento della pagina
  • l'evento ButtonValider_Click, che si verificava quando si faceva clic sul pulsante [Validate]

Si noti che:

  • l'evento Page_Init non si è verificato durante l'operazione HTTP POST, mentre si è verificato durante l'operazione HTTP GET
  • l'evento Page_Load si verifica ogni volta, sia che si tratti di un GET o di un POST. È in questo metodo che generalmente abbiamo bisogno di sapere se abbiamo a che fare con un GET o un POST.
  • Dopo il POST, la pagina [Default.aspx] è stata rinviata al client con le modifiche apportate dai gestori di eventi. È sempre così. Una volta che gli eventi di una pagina P sono stati elaborati, quella stessa pagina P viene rinviata al client. Esistono due modi per infrangere questa regola. L'ultimo gestore di eventi eseguito può
    • trasferire il flusso di esecuzione a un'altra pagina P2.
    • reindirizzare il browser del client verso un'altra pagina P2.

In entrambi i casi, è la pagina P2 che viene rinviata al browser. I due metodi presentano delle differenze di cui parleremo in seguito.

  • L'evento ButtonValider_Click si è verificato dopo l'evento Page_Load. Pertanto, è questo gestore che può decidere se trasferire o reindirizzare a una pagina P2.
  • L'elenco degli eventi [4] ha conservato i due eventi visualizzati durante il caricamento GET iniziale della pagina [Default.aspx]. Ciò è sorprendente dato che la pagina [Default.aspx] è stata ricreata durante il POST. Dovremmo vedere la pagina [Default.aspx] con i suoi valori di progettazione e quindi una casella di riepilogo vuota. L'esecuzione dei gestori Page_Load e ButtonValider_Click dovrebbe quindi popolarla con due messaggi. Tuttavia, ce ne sono quattro. Ciò è spiegato dal meccanismo VIEWSTATE. Durante la richiesta GET iniziale, il server web invia la pagina [Default.aspx] con un tag HTML <input type="hidden" ...> chiamato campo nascosto (riga 10 sotto).
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>
        Introduction ASP.NET
</title></head>
<body>
  <h3>Introduction à ASP.NET</h3>
  <form name="form1" method="post" action="default.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTMzMTEyNDMxMg9kFgICAw9kFgICBw8QZBAVAhMwNjoxNjozNjogUGFnZV9Mb2FkEzA2OjE2OjM2OiBQYWdlX0luaXQVAhMwNjoxNjozNjogUGFnZV9Mb2FkEzA2OjE2OjM2OiBQYWdlX0luaXQUKwMCZ2dkZGRW1AnTL8f/q7h2MXBLxctKD1UKfg==" />
</div>
..............................

Nel campo ID "__VIEWSTATE", il server web codifica i valori di tutti i componenti della pagina. Lo fa sia per la richiesta GET iniziale che per le successive richieste POST. Quando viene effettuata una richiesta POST alla pagina P:

  • il browser richiede la pagina P inviando i valori di tutti i componenti all'interno del tag <form> nella sua richiesta. Sopra, possiamo vedere che il componente "__VIEWSTATE" si trova all'interno del tag <form>. Il suo valore viene quindi inviato al server durante una richiesta POST.
  • La pagina P viene istanziata e inizializzata con i valori del suo costruttore
  • Il componente "__VIEWSTATE" viene utilizzato per ripristinare i valori che i componenti avevano quando la pagina P era stata precedentemente inviata. È così che, ad esempio, l’elenco degli eventi [4] recupera i primi due messaggi che aveva quando è stato inviato in risposta alla richiesta GET iniziale del browser.
  • I componenti della pagina P assumono quindi i valori inviati dal browser. A questo punto, il modulo sulla pagina P si trova nello stato in cui l'utente lo ha inviato.
  • Viene elaborato l'evento Page_Load. Qui, aggiunge un messaggio all'elenco degli eventi [4].
  • Viene gestito l'evento che ha attivato la richiesta POST. Qui, ButtonValider_Click aggiunge un messaggio all'elenco degli eventi [4].
  • Viene restituita la pagina P. I componenti hanno i seguenti valori:
    • il valore inviato, ovvero il valore che il componente aveva nel modulo quando è stato inviato al server
    • oppure un valore fornito da uno dei gestori di eventi.

Nel nostro esempio,

  • i due componenti TextBox manterranno i loro valori inviati perché i gestori di eventi non li modificano
  • l'elenco degli eventi [4] riacquista il suo valore inviato, ovvero tutti gli eventi già elencati, più due nuovi eventi creati dai metodi Page_Load e ButtonValider_Click.

Il meccanismo VIEWSTATE può essere abilitato o disabilitato a livello di componente. Disabilitiamolo per il componente [ListBoxEvts]:

  • In [1], il VIEWSTATE del componente [ListBoxEvts] è disabilitato. Quello della TextBox [2] è abilitato per impostazione predefinita.
  • In [3], i due eventi restituiti dopo il GET iniziale
  • In [4], abbiamo compilato il modulo e cliccato sul pulsante [Convalida]. Verrà inviata una richiesta POST alla pagina [Default.aspx].
  • In [6], il risultato restituito dopo aver cliccato sul pulsante [Convalida]
  • Il meccanismo del VIEWSTATE abilitato spiega perché le caselle di testo [7] hanno mantenuto i valori inviati in [4]
  • Il meccanismo VIEWSTATE disabilitato spiega perché il componente [ListBoxEvts] [8] non ha mantenuto il suo contenuto [5].

2.3. Gestione dei valori inviati

Qui ci concentreremo sui valori inviati dalle due caselle di testo quando l'utente fa clic sul pulsante [Convalida]. La pagina [Default.aspx] in modalità [Progettazione] cambia come segue:

Il codice sorgente dell'elemento aggiunto in [1] è il seguente:


  <p>
    Eléments postés au serveur :
    <asp:Label ID="LabelPost" runat="server"></asp:Label>
</p>

Useremo il componente [LabelPost] per visualizzare i valori inseriti nelle due caselle di testo [2]. Il codice per il gestore di eventi [Default.aspx.cs] cambia come segue:


using System;
 
namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
    protected void Page_Init(object sender, EventArgs e)
    {
      // the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Init", DateTime.Now.ToString("hh:mm:ss")));
    }
 
    protected void Page_Load(object sender, EventArgs e)
    {
      // the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
    }

    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
      // display name and age
      LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
    }
  }
}

Alla riga 24, aggiorniamo il componente LabelPost:

  • LabelPost è di tipo [System.Web.UI.WebControls.Label] (vedere Default.aspx.designer.cs). La sua proprietà Text rappresenta il testo visualizzato dal componente.
  • TextBoxName e TextBoxAge sono di tipo [System.Web.UI.WebControls.TextBox]. La proprietà Text di un componente TextBox è il testo visualizzato nel campo di immissione.
  • Il metodo Trim() rimuove eventuali spazi che precedono o seguono una stringa

Come spiegato in precedenza, quando viene eseguito il metodo ButtonValider_Click, i componenti della pagina hanno i valori che avevano al momento dell'invio della pagina da parte dell'utente. Le proprietà Text delle due TextBox contengono quindi il testo inserito dall'utente nel browser.

Ecco un esempio:

  • in [1], i valori inviati
  • in [2], la risposta del server.
  • in [3], le caselle di testo hanno riottenuto i valori inviati tramite il meccanismo VIEWSTATE attivato
  • in [4], i messaggi provenienti dal componente ListBoxEvts derivano dai metodi Page_Init, Page_Load e ButtonValider_Click e da un VIEWSTATE disabilitato
  • in [5], il componente LabelPost ha ottenuto il suo valore tramite il metodo ButtonValider_Click. Abbiamo recuperato con successo i due valori inseriti dall'utente nelle due caselle di testo [1].

Come mostrato sopra, il valore inviato per l'età è la stringa "yy", un valore non valido. Aggiungeremo alla pagina dei componenti chiamati validatori. Essi vengono utilizzati per verificare la validità dei dati inviati. Questa validità può essere verificata in due punti:

  • sul lato client. Un'opzione di configurazione del validator consente di scegliere se eseguire o meno i controlli nel browser. Questi vengono quindi eseguiti dal codice JavaScript incorporato nella pagina HTML. Quando l'utente invia i valori inseriti nel modulo, questi vengono prima controllati dal codice JavaScript. Se uno qualsiasi dei controlli fallisce, l'invio non viene eseguito. Ciò evita un round trip al server, rendendo la pagina più reattiva.
  • sul lato server. Mentre la convalida lato client può essere facoltativa, quella lato server è obbligatoria indipendentemente dal fatto che sia stata eseguita o meno la convalida lato client. Questo perché quando una pagina riceve i valori inviati, non ha modo di sapere se sono stati convalidati dal client prima di essere inviati. Sul lato server, lo sviluppatore deve quindi verificare sempre la validità dei dati inviati.

La pagina [Default.aspx] si evolve come segue:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Introduction ASP.NET</title>
</head>
<body>
  <h3>Introduction à ASP.NET</h3>
  <form id="form1" runat="server">
  <div>
    <table>
      <tr>
        <td>
          Nom</td>
        <td>
          <asp:TextBox ID="TextBoxNom" runat="server"></asp:TextBox>
        </td>
        <td>
          <asp:RequiredFieldValidator ID="RequiredFieldValidatorNom" runat="server" 
            ControlToValidate="TextBoxNom" Display="Dynamic" 
            ErrorMessage="Donnée obligatoire !"></asp:RequiredFieldValidator>
        </td>
      </tr>
      <tr>
        <td>
          Age</td>
        <td>
          <asp:TextBox ID="TextBoxAge" runat="server"></asp:TextBox>
        </td>
        <td>
          <asp:RequiredFieldValidator ID="RequiredFieldValidatorAge" runat="server" 
            ControlToValidate="TextBoxAge" Display="Dynamic" 
            ErrorMessage="Donnée obligatoire !"></asp:RequiredFieldValidator>
          <asp:RangeValidator ID="RangeValidatorAge" runat="server" 
            ControlToValidate="TextBoxAge" Display="Dynamic" 
            ErrorMessage="Tapez un nombre entre 1 et 150 !" MaximumValue="150" 
            MinimumValue="1" Type="Integer"></asp:RangeValidator>
        </td>
      </tr>
    </table>
  </div>
  <asp:Button ID="ButtonValider" runat="server" onclick="ButtonValider_Click" 
    Text="Valider" CausesValidation="False"/>
  <hr />
  <p>
    Evénements traités par le serveur</p>
  <p>
    <asp:ListBox ID="ListBoxEvts" runat="server" EnableViewState="False">
    </asp:ListBox>
  </p>
  <p>
    Eléments postés au serveur :
    <asp:Label ID="LabelPost" runat="server"></asp:Label>
  </p>
  <p>
    Eléments validés par le serveur :
    <asp:Label ID="LabelValidation" runat="server"></asp:Label>
  </p>
  <asp:Label ID="LabelErreursSaisie" runat="server" ForeColor="Red"></asp:Label>
  </form>
</body>
</html>

Sono stati aggiunti dei validatori alle righe 20, 32 e 35. Alla riga 58, viene utilizzato un controllo Label per visualizzare i valori validi inviati. Alla riga 60, viene utilizzato un controllo Label per visualizzare un messaggio di errore in caso di errori di immissione.

La pagina [Default.aspx] in modalità [Design] ha questo aspetto:

  • I componenti [1] e [2] sono di tipo RequiredFieldValidator. Questo validatore verifica che un campo di immissione non sia vuoto.
  • Il componente [3] è un RangeValidator. Questo validatore verifica che un campo di immissione contenga un valore compreso tra due limiti.
  • In [4], le proprietà del validatore [1].

Dimostreremo i due tipi di validatori utilizzando i loro tag nel codice della pagina [Default.aspx]:


          <asp:RequiredFieldValidator ID="RequiredFieldValidatorNom" runat="server" 
            ControlToValidate="TextBoxNom" Display="Dynamic" 
ErrorMessage="Donnée obligatoire !"></asp:RequiredFieldValidator>
  • ID: l'identificatore del componente
  • ControlToValidate: il nome del componente il cui valore viene convalidato. In questo caso, vogliamo assicurarci che il componente TextBoxNom non abbia un valore vuoto (stringa vuota o una sequenza di spazi)
  • ErrorMessage: messaggio di errore da visualizzare nel validatore se i dati non sono validi.
  • EnableClientScript: un valore booleano che indica se il validatore deve essere eseguito anche sul lato client. Questo attributo ha un valore predefinito di True quando non è esplicitamente impostato come mostrato sopra.
  • Display: modalità di visualizzazione del validatore. Esistono due modalità:
    • static (predefinita): il validatore occupa spazio sulla pagina anche se non visualizza un messaggio di errore
    • dinamico: il validatore non occupa spazio sulla pagina se non visualizza un messaggio di errore.

          <asp:RangeValidator ID="RangeValidatorAge" runat="server" 
            ControlToValidate="TextBoxAge" Display="Dynamic" 
            ErrorMessage="Tapez un nombre entre 1 et 150 !" MaximumValue="150" 
            MinimumValue="1" Type="Integer"></asp:RangeValidator>
  • Type: il tipo dei dati da convalidare. In questo caso, l'età è un numero intero.
  • MinimumValue, MaximumValue: i limiti entro i quali deve rientrare il valore convalidato

La configurazione del componente che attiva il POST influisce sulla modalità di convalida. In questo caso, tale componente è il pulsante [Convalida]:


  <asp:Button ID="ButtonValider" runat="server" onclick="ButtonValider_Click"  Text="Valider" CausesValidation="True" />
  • CausesValidation: imposta la modalità automatica o il nome delle validazioni lato server. Questo attributo ha il valore predefinito "True" se non specificato esplicitamente. In questo caso,
    • sul lato client vengono eseguiti i validatori con EnableClientScript impostato su True. La richiesta POST viene inviata solo se tutti i validatori lato client hanno esito positivo.
    • Sul lato server, tutti i validatori presenti nella pagina vengono eseguiti automaticamente prima che venga elaborato l'evento che ha attivato il POST. In questo caso, verrebbero eseguiti prima della chiamata al metodo ButtonValider_Click. All'interno di questo metodo, è possibile verificare se tutte le validazioni hanno avuto esito positivo o meno. Page.IsValid è "True" se tutte hanno avuto esito positivo, "False" in caso contrario. In quest'ultimo caso, è possibile interrompere l'elaborazione dell'evento che ha attivato il POST. La pagina inviata viene restituita esattamente come è stata inviata. I validatori che hanno fallito visualizzano quindi il loro messaggio di errore (attributo ErrorMessage).

Se CausesValidation è impostato su False, allora

  • sul lato client non viene eseguito alcun validatore
  • sul lato server, spetta allo sviluppatore richiedere l’esecuzione dei validatori della pagina. Ciò avviene utilizzando il metodo Page.Validate(). A seconda dei risultati della validazione, questo metodo imposta la proprietà Page.IsValid su "True" o "False".

In [Default.aspx.cs], il codice per l'evento ButtonValider_Click si presenta come segue:


protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
      // display name and age
      LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
      // is the page valid?
      Page.Validate();
      if (!Page.IsValid)
      {
        // global error msg
        LabelErreursSaisie.Text = "Veuillez corriger les erreurs de saisie...";
        LabelErreursSaisie.Visible = true;
        return;
      }
      // hide error msg
      LabelErreursSaisie.Visible = false;
      // displays validated name and age
      LabelValidation.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
}

Se il pulsante [Validate] ha l'attributo CausesValidation impostato su True e i validatori hanno l'attributo EnableClientScript impostato su True, il metodo ButtonValider_Click viene eseguito solo quando i valori inviati sono validi. Ci si potrebbe quindi chiedere quale sia lo scopo del codice che inizia alla riga 8. È importante ricordare che è sempre possibile scrivere uno script lato client che invii valori non verificati alla pagina [Default.aspx]. Pertanto, questa pagina deve sempre rieseguire i controlli di validità.

  • Riga 8: avvia l’esecuzione di tutti i validatori presenti nella pagina. Se l’attributo CausesValidation del pulsante [Validate] è impostato su True, ciò avviene automaticamente e non è necessario ripeterlo. In questo caso si ha una ridondanza.
  • Righe 9–15: caso in cui uno dei validatori ha fallito
  • Righe 16–19: caso in cui tutti i validatori hanno superato il controllo

Ecco due esempi di esecuzione:

  • in [1], un esempio di esecuzione nel caso in cui:
    • il pulsante [Validate] ha la proprietà CausesValidation impostata su True
    • I validatori hanno la proprietà EnableClientScript impostata su True

I messaggi di errore [2] sono stati visualizzati dai validatori eseguiti sul lato client dal codice JavaScript della pagina. Non è stata inviata alcuna richiesta POST al server, come mostrato dall'etichetta degli elementi inviati [3].

  • In [4], un esempio di esecuzione nel caso in cui:
    • il pulsante [Validate] ha la proprietà CausesValidation impostata su False
    • i validatori hanno la proprietà EnableClientScript impostata su False

I messaggi di errore [5] sono stati visualizzati dai validatori eseguiti sul lato server. Come mostrato in [6], è stata effettivamente inviata una richiesta POST al server. In [7], il messaggio di errore visualizzato dal metodo [ButtonValider_Click] in caso di errori di input.

  • In [8], un esempio ottenuto con dati validi. [9,10] mostrano che gli elementi inviati sono stati convalidati. Quando si eseguono test ripetuti, impostare la proprietà EnableViewState dell'etichetta [LabelValidation] su False in modo che il messaggio di convalida non rimanga visualizzato tra un'esecuzione e l'altra.

2.4. Gestione dei dati a livello di applicazione

Rivediamo l'architettura di esecuzione di una pagina ASPX:

La classe della pagina ASPX viene istanziata all'inizio della richiesta del client e distrutta al termine della stessa. Pertanto, non può essere utilizzata per memorizzare dati tra una richiesta e l'altra. Potresti voler memorizzare due tipi di dati:

  • dati condivisi da tutti gli utenti dell'applicazione web. Si tratta generalmente di dati di sola lettura. Per implementare questa condivisione dei dati vengono utilizzati tre file:
    • [Web.Config]: il file di configurazione dell'applicazione
    • [Global.asax, Global.asax.cs]: consentono di definire una classe, denominata classe dell'applicazione globale, la cui durata è quella dell'applicazione, nonché gestori per determinati eventi della stessa applicazione.

La classe di applicazione globale consente di definire i dati che saranno disponibili per tutte le richieste provenienti da tutti gli utenti.

  • dati condivisi tra le richieste provenienti dallo stesso client. Questi dati sono memorizzati in un oggetto chiamato Sessione. Ci riferiamo a questo come alla sessione del client per indicare la memoria del client. Tutte le richieste provenienti da un client hanno accesso a questa sessione. Possono memorizzare e leggere informazioni al suo interno

Sopra, mostriamo i tipi di memoria a cui una pagina ASPX ha accesso:

  • la memoria dell'applicazione, che contiene principalmente dati di sola lettura ed è accessibile a tutti gli utenti.
  • la memoria di un utente specifico, o sessione, che contiene dati in lettura/scrittura ed è accessibile alle richieste successive dello stesso utente.
  • Non mostrata sopra, esiste una memoria di richiesta, o contesto di richiesta. La richiesta di un utente può essere elaborata da diverse pagine ASPX successive. Il contesto di richiesta consente alla Pagina 1 di passare informazioni alla Pagina 2.

Qui ci interessano i dati a livello di applicazione, che sono condivisi da tutti gli utenti. La classe globale dell'applicazione può essere creata come segue:

  • In [1], aggiungere un nuovo elemento al progetto
  • In [2], aggiungere la classe globale dell'applicazione
  • in [3], mantenere il nome predefinito [Global.asax] per il nuovo elemento
  • in [4], sono stati aggiunti due nuovi file al progetto
  • in [5], visualizza il markup per [Global.asax]

<%@ Application Codebehind="Global.asax.cs" Inherits="Intro.Global" Language="C#" %>
  • Il tag Application sostituisce il tag Page che avevamo per [Default.aspx]. Identifica la classe dell'applicazione globale
  • Codebehind: definisce il file in cui è definita la classe dell'applicazione globale
  • Inherits: definisce il nome di questa classe

La classe Intro.Global generata è la seguente:


using System;
 
namespace Intro
{
  public class Global : System.Web.HttpApplication
  {
 
    protected void Application_Start(object sender, EventArgs e)
    {
 
    }
 
    protected void Session_Start(object sender, EventArgs e)
    {
 
    }
 
    protected void Application_BeginRequest(object sender, EventArgs e)
    {
 
    }
 
    protected void Application_AuthenticateRequest(object sender, EventArgs e)
    {
 
    }
 
    protected void Application_Error(object sender, EventArgs e)
    {
 
    }
 
    protected void Session_End(object sender, EventArgs e)
    {
 
    }
 
    protected void Application_End(object sender, EventArgs e)
    {
 
    }
  }
}
  • Riga 5: La classe dell'applicazione globale deriva dalla classe HttpApplication

La classe viene generata con modelli di gestori di eventi dell'applicazione:

  • righe 8, 38: gestione degli eventi Application_Start (avvio dell'applicazione) e Application_End (chiusura dell'applicazione all'arresto del server web o quando l'amministratore discarica l'applicazione)
  • righe 13, 33: gestiscono l'evento Session_Start (inizio di una nuova sessione client all'arrivo di un nuovo client o alla scadenza di una sessione esistente) e l'evento Session_End (fine di una sessione client, sia esplicitamente tramite programmazione sia implicitamente per superamento della durata consentita della sessione).
  • Riga 28: gestisce l'evento Application_Error (verificarsi di un'eccezione non gestita dal codice dell'applicazione e propagata fino al server)
  • Riga 18: gestisce l'evento Application_BeginRequest (arrivo di una nuova richiesta).
  • Riga 23: Gestisce l'evento Application_AuthenticateRequest (si verifica quando un utente ha effettuato l'autenticazione).

Il metodo [Application_Start] viene spesso utilizzato per inizializzare l'applicazione in base alle informazioni contenute in [Web.Config]. Quello generato al momento della creazione iniziale del progetto ha questo aspetto:


<?xml version="1.0" encoding="utf-8"?>
 
<configuration>
    <configSections>
...
    </configSections>  
 
    <appSettings/>
    <connectionStrings/>
 
    <system.web>
...
    </system.web>
 
    <system.codedom>
....
    </system.codedom>
 
    <!-- 
        La section system.webServer est requise pour exécuter ASP.NET AJAX sur Internet
        Information Services 7.0.  Elle n'est pas nécessaire pour les versions précédentes d'IIS.
    -->
    <system.webServer>
...
    </system.webServer>
 
    <runtime>
....
    </runtime>
 
</configuration>

Per la nostra applicazione attuale, questo file non è necessario. Se lo eliminiamo o lo rinominiamo, l'applicazione continua a funzionare normalmente. Ci concentreremo sui tag nelle righe 8 e 9:

  • <appsettings> consente di definire un dizionario di informazioni
  • <connectionStrings> consente di definire stringhe di connessione ai database

Si consideri il seguente file [Web.config]:


<?xml version="1.0" encoding="utf-8"?>
 
<configuration>
    <configSections>
...
    </configSections>  
 
  <appSettings>
    <add key="cle1" value="valeur1"/>
    <add key="cle2" value="valeur2"/>
  </appSettings>
  <connectionStrings>
    <add connectionString="connectionString1" name="conn1"/>
  </connectionStrings>
 
    <system.web>
...
 

Questo file può essere utilizzato dalla seguente classe di applicazione globale:


using System;
using System.Configuration;
 
namespace Intro
{
  public class Global : System.Web.HttpApplication
  {
    public static string Param1 { get; set; }
    public static string Param2 { get; set; }
    public static string ConnString1 { get; set; }
    public static string Erreur { get; set; }
 
    protected void Application_Start(object sender, EventArgs e)
    {
      try
      {
        Param1 = ConfigurationManager.AppSettings["cle1"];
        Param2 = ConfigurationManager.AppSettings["cle2"];
        ConnString1 = ConfigurationManager.ConnectionStrings["conn1"].ConnectionString;
      }
      catch (Exception ex)
      {
        Erreur = string.Format("Erreur de configuration : {0}", ex.Message);
      }
    }
 
    protected void Session_Start(object sender, EventArgs e)
    {
 
    }
 
  }
}
  • righe 8–11: quattro proprietà statiche P. Poiché la classe Global ha la stessa durata dell'applicazione, qualsiasi richiesta effettuata all'applicazione avrà accesso a queste proprietà P tramite la sintassi Global.P.
  • righe 17-19: il file [Web.config] è accessibile tramite la classe [System.Configuration.ConfigurationManager]
  • righe 17-18: recupera gli elementi del tag <appSettings> dal file [Web.config] tramite l'attributo key.
  • Riga 19: recupera gli elementi del tag <connectionStrings> dal file [Web.config] tramite l'attributo name.

Gli attributi statici nelle righe 8–11 sono accessibili da qualsiasi gestore di eventi nelle pagine ASPX caricate. Li utilizziamo nel gestore [Page_Load] della pagina [Default.aspx]:


    protected void Page_Load(object sender, EventArgs e)
    {
      // the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
      // retrieve information from the global application class
      LabelGlobal.Text = string.Format("Param1={0},Param2={1},ConnString1={2},Erreur={3}", Global.Param1, Global.Param2, Global.ConnString1, Global.Erreur);
}
  • Riga 6: Le quattro proprietà statiche della classe globale dell'applicazione vengono utilizzate per popolare una nuova etichetta nella pagina [Default.aspx]

In fase di esecuzione, otteniamo il seguente risultato:

Come si può vedere sopra, i parametri da [web.config] sono stati recuperati correttamente. La classe globale dell'applicazione è il luogo ideale in cui memorizzare le informazioni condivise da tutti gli utenti.

2.5. Gestione dei dati nell'ambito della sessione

In questo caso, ci interessa capire come memorizzare le informazioni tra le richieste di un determinato utente:

Ogni utente dispone di una propria memoria, nota come sessione.

Abbiamo visto che la classe globale dell'applicazione dispone di due gestori per la gestione degli eventi:

  • Session_Start: inizio di una sessione
  • Session_end: fine di una sessione

Il meccanismo di sessione funziona come segue:

  • Quando un utente effettua la sua prima richiesta, il server web crea un token di sessione e lo assegna all'utente. Questo token è una stringa di caratteri univoca per ogni utente. Viene inviato dal server nella risposta alla prima richiesta dell'utente.
  • Nelle richieste successive, l'utente (il browser web) include il token di sessione assegnato nella propria richiesta. Ciò consente al server web di riconoscerlo.
  • Una sessione ha un periodo di timeout. Quando il server web riceve una richiesta da un utente, calcola il tempo trascorso dalla richiesta precedente. Se questo tempo supera il timeout della sessione, viene creata una nuova sessione per l'utente. I dati della sessione precedente vanno persi. Con il server web IIS (Internet Information Server) di Microsoft, le sessioni hanno una durata predefinita di 20 minuti. Questo valore può essere modificato dall'amministratore del server web.
  • Il server web sa che sta gestendo la prima richiesta di un utente perché tale richiesta non contiene un token di sessione. È l'unica.

Qualsiasi pagina ASP.NET ha accesso alla sessione dell'utente tramite la proprietà Session della pagina, di tipo [System.Web.SessionState.HttpSessionState]. Utilizzeremo le seguenti proprietà P e metodi M della classe HttpSessionState:

Name
Tipo
Ruolo
Item[String key]
P
La sessione può essere strutturata come un dizionario. Item[key] è l'elemento della sessione identificato da key. Invece di scrivere [HttpSessionState].Item[key], è possibile scrivere anche [HttpSessionState].[key].
Cancella
M
cancella il dizionario della sessione
Abbandona
M
termina la sessione. La sessione non è più valida. Una nuova sessione inizierà con la richiesta successiva dell'utente.

Come esempio di stato dell'utente, conteremo il numero di volte in cui un utente fa clic sul pulsante [Invia]. Per ottenere questo risultato, dobbiamo mantenere un contatore nella sessione dell'utente.

La pagina [Default.aspx] cambia come segue:

La classe di applicazione globale [Global.asax.cs] viene modificata come segue:


using System;
using System.Configuration;
 
namespace Intro
{
  public class Global : System.Web.HttpApplication
  {
    public static string Param1 { get; set; }
...
 
    protected void Application_Start(object sender, EventArgs e)
    {
...
    }
 
    protected void Session_Start(object sender, EventArgs e)
    {
      // query counter
      Session["nbRequêtes"] = 0;
    }
 
  }
}

Alla riga 19, utilizziamo la sessione dell'utente per memorizzare un contatore delle richieste identificato dalla chiave "nbRequests". Questo contatore viene aggiornato dal gestore [ButtonValider_Click] nella pagina [Default.aspx]:


using System;
 
namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
....
 
    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
      // post name and age are displayed
      LabelPost.Text = string.Format("nom={0}, age={1}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim());
      // number of requests
      Session["nbRequêtes"] = (int)Session["nbRequêtes"] + 1;
      LabelNbRequetes.Text = Session["nbRequêtes"].ToString();
      // is the page valid?
      Page.Validate();
      if (!Page.IsValid)
      {
...
      }
...
    }
  }
}
  • riga 16: incrementare il contatore delle richieste
  • riga 17: il contatore viene visualizzato sulla pagina

Ecco un esempio di esecuzione:

2.6. Gestione delle richieste GET e POST durante il caricamento della pagina

Abbiamo accennato al fatto che esistono due tipi di richieste a una pagina ASPX:

  • la richiesta iniziale dal browser effettuata con un comando HTTP GET. Il server risponde inviando la pagina richiesta. Supponiamo che questa pagina sia un modulo, ovvero che la pagina ASPX inviata contenga un tag <form runat="server">.
  • Richieste successive effettuate dal browser in risposta a determinate azioni dell’utente sul modulo. Il browser effettua quindi una richiesta HTTP POST.

Che si tratti di una richiesta GET o di una richiesta POST, viene eseguito il metodo [Page_Load]. Durante una richiesta GET, questo metodo viene tipicamente utilizzato per inizializzare la pagina inviata al browser del client. Successivamente, attraverso il meccanismo VIEWSTATE, la pagina rimane inizializzata e viene modificata solo dai gestori di eventi che attivano le richieste POST. Non è necessario reinizializzare la pagina in Page_Load. Da qui la necessità di questo metodo per determinare se la richiesta del client è di tipo GET o POST.

Esaminiamo il seguente esempio. Aggiungiamo un elenco a discesa alla pagina [Default.aspx]. Il contenuto di questo elenco verrà definito nel gestore Page_Load per la richiesta GET:

L'elenco a discesa è dichiarato in [Default.aspx.designer.cs] come segue:


        protected global::System.Web.UI.WebControls.DropDownList DropDownListNoms;

Utilizzeremo i seguenti metodi M e proprietà P della classe [DropDownList]:

Nome
Tipo
Ruolo
Elementi
P
la ListItemCollection degli elementi ListItem nell'elenco a discesa
SelectedIndex
P
l'indice, a partire da 0, della voce selezionata nell'elenco a discesa al momento dell'invio del modulo
SelectedItem
P
l'elemento ListItem selezionato nell'elenco a discesa al momento dell'invio del modulo
SelectedValue
P
il valore stringa dell'elemento ListItem selezionato nell'elenco a discesa al momento dell'invio del modulo. Definiremo questo concetto di valore tra poco.

La classe ListItem per gli elementi di un elenco a discesa viene utilizzata per generare i tag <option> all'interno del tag HTML <select>:

1
2
3
4
5
<select ....>
    <option value="val1">texte1</option>
    <option value="val2">texte2</option>
....
</select>

Nel tag <option>

  • text1 è il testo visualizzato nell'elenco a discesa
  • vali è il valore inviato dal browser se texti è il testo selezionato nell'elenco a discesa

Ogni opzione può essere generata da un oggetto ListItem creato utilizzando il costruttore ListItem(string text, string value).

In [Default.aspx.cs], il codice per il gestore [Page_Load] cambia come segue:


    protected void Page_Load(object sender, EventArgs e)
    {
      // the event
      ...
      // retrieve information from the global application class
      ...
      // initialization of name combo only during initial GET
      if (!IsPostBack)
      {
        for (int i = 0; i < 3; i++)
        {
          DropDownListNoms.Items.Add(new ListItem("nom"+i,i.ToString()));
        }
      }
}
  • Riga 8: La classe Page ha una proprietà booleana IsPostBack. In sostanza, ciò significa che la richiesta dell'utente è di tipo POST. Le righe 10–13 vengono quindi eseguite solo sulla richiesta GET iniziale del client.
  • Riga 12: Aggiungiamo un elemento di tipo ListItem(string text, string value) all'elenco [DropDownListNames]. Il testo visualizzato per l'elemento (i+1) sarà "names", e il valore inviato per questo elemento, se selezionato, sarà i.

Il gestore [ButtonValider_Click] viene modificato per visualizzare il valore inviato dall'elenco a discesa:


    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // the event
...
      // display posted values
      LabelPost.Text = string.Format("nom={0}, age={1}, combo={2}", TextBoxNom.Text.Trim(), TextBoxAge.Text.Trim(), DropDownListNoms.SelectedValue);
      // number of requests
...
}

Riga 6: Il valore inviato per l'elenco [DropDownListNames] viene ottenuto utilizzando la proprietà SelectedValue dell'elenco. Ecco un esempio di esecuzione:

  • in [1], il contenuto dell'elenco a discesa dopo il GET iniziale e appena prima del primo POST
  • in [2], la pagina dopo il primo POST.
  • in [3], il valore inviato per l'elenco a discesa. Ciò corrisponde all'attributo "value" dell'elemento ListItem selezionato nell'elenco.
  • in [4], l'elenco a discesa. Contiene gli stessi elementi presenti dopo la richiesta GET iniziale. Ciò è dovuto al meccanismo VIEWSTATE.

Per comprendere l'interazione tra il VIEWSTATE dell'elenco DropDownListNames e il test if (!IsPostBack) nel gestore Page_Load di [Default.aspx], il lettore è invitato a ripetere il test precedente con le seguenti configurazioni:

Caso
DropDownListNames.EnableViewState
Test if(! IsPostBack) in Page_Load di [Default.aspx]
1
vero
presente
2
false
presente
3
vero
assente
4
false
assente

I vari test danno i seguenti risultati:

  1. Questo è il caso descritto sopra
  2. L'elenco viene popolato durante il GET iniziale ma non durante i POST successivi. Poiché EnableViewState è impostato su false, l'elenco è vuoto dopo ogni POST
  3. L'elenco viene popolato sia dopo il GET iniziale che durante le successive richieste POST. Poiché EnableViewState è impostato su true, ci sono 3 nomi dopo il GET iniziale, 6 nomi dopo il primo POST, 9 nomi dopo il secondo POST, ...
  4. L'elenco viene popolato sia dopo il GET iniziale che durante i POST successivi. Poiché EnableViewState è impostato su false, l'elenco viene popolato con solo 3 nomi per ogni richiesta, sia che si tratti del GET iniziale che dei POST successivi. Osserviamo lo stesso comportamento del Caso 1. Esistono quindi due modi per ottenere lo stesso risultato.

2.7. Gestione del VIEWSTATE degli elementi in una pagina ASPX

Per impostazione predefinita, tutti gli elementi di una pagina ASPX hanno la proprietà EnableViewState impostata su True. Ogni volta che la pagina ASPX viene inviata al browser del client, contiene il campo nascosto __VIEWSTATE, il cui valore è una stringa che codifica tutti i valori dei componenti con la proprietà EnableViewState impostata su True. Per ridurre al minimo la dimensione di questa stringa, possiamo provare a ridurre il numero di componenti con la proprietà EnableViewState impostata su True.

Vediamo come i componenti di una pagina ASPX ottengono i propri valori a seguito di una richiesta POST:

  1. La pagina ASPX viene istanziata. I componenti vengono inizializzati con i loro valori di progettazione.
  2. Il valore __VIEWSTATE inviato dal browser viene utilizzato per assegnare ai componenti i valori che avevano quando la pagina ASPX è stata inviata al browser in precedenza.
  3. I valori inviati dal browser vengono assegnati ai componenti.
  4. Vengono eseguiti i gestori di eventi. Questi possono modificare i valori di determinati componenti.

Da questa sequenza, possiamo dedurre che i componenti che:

  • hanno i propri valori inviati
  • hanno i propri valori modificati da un gestore di eventi

potrebbero avere la proprietà EnableViewState impostata su False poiché il loro valore VIEWSTATE (passaggio 2) verrà modificato dal passaggio 3 o 4.

L'elenco dei componenti presenti nella nostra pagina è disponibile in [Default.aspx.designer.cs]:


namespace Intro {
    public partial class _Default {
        protected global::System.Web.UI.HtmlControls.HtmlForm form1;
        protected global::System.Web.UI.WebControls.TextBox TextBoxNom;
        protected global::System.Web.UI.WebControls.RequiredFieldValidator RequiredFieldValidatorNom;
        protected global::System.Web.UI.WebControls.TextBox TextBoxAge;
        protected global::System.Web.UI.WebControls.RequiredFieldValidator RequiredFieldValidatorAge;
        protected global::System.Web.UI.WebControls.RangeValidator RangeValidatorAge;
        protected global::System.Web.UI.WebControls.DropDownList DropDownListNoms;
        protected global::System.Web.UI.WebControls.Button ButtonValider;
        protected global::System.Web.UI.WebControls.ListBox ListBoxEvts;
        protected global::System.Web.UI.WebControls.Label LabelPost;
        protected global::System.Web.UI.WebControls.Label LabelValidation;
        protected global::System.Web.UI.WebControls.Label LabelErreursSaisie;
        protected global::System.Web.UI.WebControls.Label LabelGlobal;
        protected global::System.Web.UI.WebControls.Label LabelNbRequetes;
    }
}

Il valore della proprietà EnableViewState per questi componenti potrebbe essere il seguente:

Componente
Valore impostato
EnableViewState
Motivo
NomeTextBox
Valore inserito nella casella di testo
False
il valore del componente viene inviato
TextBoxAge
uguale
  
NomeValidatoreCampoObbligatorio
nessuno
Falso
Nessun concetto di valore del componente
RequiredFieldValidatorAge
uguale
  
RangeValidatorAge
uguale
  
EtichettaPost
nessuno
False
ottiene il proprio valore da un gestore di eventi
ValidazioneEtichetta
uguale
  
InputErrorLabel
stesso
  
Etichetta globale
stesso
  
Numero di richieste dell'etichetta
uguale
  
NomiElencoAScorrere
"valore" dell'elemento selezionato
True
Vogliamo mantenere il contenuto dell'elenco tra una richiesta e l'altra senza doverlo rigenerare
ListBoxEvts
"valore" dell'elemento selezionato
False
Il contenuto dell'elenco viene generato da un gestore di eventi
ButtonValidate
Etichetta del pulsante
False
Il componente mantiene il proprio valore di progettazione

2.8. Inoltro da una pagina all'altra

Fino ad ora, le richieste GET e POST hanno sempre restituito la stessa pagina [Default.aspx]. Prenderemo in esame il caso in cui una richiesta venga elaborata da due pagine ASPX successive, [Default.aspx] e [Page1.aspx], e in cui quest'ultima venga restituita al client. Inoltre, vedremo come la pagina [Default.aspx] possa trasmettere informazioni alla pagina [Page1.aspx] tramite una memoria che chiameremo memoria di richiesta.

Creiamo la pagina [Page1.aspx]:

  • In [1], aggiungiamo un nuovo elemento al progetto
  • In [2], aggiungiamo un elemento [Web Form] denominato [Page1.aspx] [3]
  • in [4], la pagina aggiunta
  • in [5], la pagina una volta creata

Il codice sorgente di [Page1.aspx] è il seguente:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Page1.aspx.cs" Inherits="Intro.Page1" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>Page1</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h1>
      Page 1</h1>
    <asp:Label ID="Label1" runat="server"></asp:Label>
    <br />
    <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/Default.aspx">Retour 
      vers page [Default]</asp:HyperLink>
  </div>
  </form>
</body>
</html>
  • Riga 13: un'etichetta che verrà utilizzata per visualizzare le informazioni inviate dalla pagina [Default.aspx]
  • Riga 15: un collegamento HTML alla pagina [Default.aspx]. Quando l'utente fa clic su questo collegamento, il browser richiede la pagina [Default.aspx] utilizzando un'operazione GET. La pagina [Default.aspx] viene quindi caricata come se l'utente avesse digitato direttamente il suo URL nel browser.

La pagina [Default.aspx] è stata arricchita con un nuovo componente LinkButton:

Il codice sorgente di questo nuovo componente è il seguente:


  <asp:LinkButton ID="LinkButtonToPage1" runat="server" CausesValidation="False" 
    EnableViewState="False" onclick="LinkButtonToPage1_Click">Forward vers Page1</asp:LinkButton>
  • CausesValidation="False": facendo clic sul collegamento verrà inviata una richiesta POST a [Default.aspx]. Il componente [LinkButton] si comporta come il componente [Button]. In questo caso, non vogliamo che facendo clic sul collegamento venga avviata l'esecuzione dei validatori.
  • EnableViewState="False": non è necessario conservare lo stato del collegamento tra una richiesta e l'altra. Mantiene i valori di progettazione.
  • onclick="LinkButtonToPage1_Click": nome del metodo che, in [Defaul.aspx.cs], gestisce l'evento Click sul componente LinkButtonToPage1.

Il codice per il gestore LinkButtonToPage1_Click è il seguente:


  // to Page1
  protected void LinkButtonToPage1_Click(object sender, EventArgs e)
  {
    // we put information in context
    Context.Items["msg1"] = "Message de Default.aspx pour Page1";
    // pass the request to Page1
    Server.Transfer("Page1.aspx",true);
}

Riga 7: La richiesta viene passata alla pagina [Page1.aspx] utilizzando il metodo [Server.Transfer]. Il secondo parametro del metodo, impostato su true, indica che tutte le informazioni inviate a [Default.aspx] durante la richiesta POST devono essere passate a [Page1.aspx]. Ciò consente a [Page1.aspx], ad esempio, di accedere ai valori inviati tramite una raccolta denominata Request.Form. La riga 5 utilizza quello che è noto come contesto della richiesta. È accessibile tramite la proprietà Context della classe Page. Questo contesto può fungere da memoria condivisa tra le diverse pagine che gestiscono la stessa richiesta, in questo caso [Default.aspx] e [Page1.aspx]. A questo scopo viene utilizzato il dizionario Items.

Quando [Page1.aspx] viene caricata dall'operazione Server.Transfer("Page1.aspx", true), tutto avviene come se [Page1.aspx] fosse stata chiamata da una richiesta GET da un browser. Il gestore Page_Load di [Page1.aspx] viene eseguito normalmente. Lo useremo per visualizzare il messaggio inserito da [Default.aspx] nel contesto della richiesta:


using System;
 
namespace Intro
{
  public partial class Page1 : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      Label1.Text = Context.Items["msg1"] as string;
    }
  }
}

Riga 9: Il messaggio impostato da [Default.aspx] nel contesto della richiesta viene visualizzato in Label1.

Ecco un esempio di esecuzione:

  • Nella pagina [Default.aspx] [1], clicca sul link [2] che ti porta alla pagina Page1
  • in [3], viene visualizzata la pagina Page1
  • In [4], il messaggio creato in [Default.aspx] e visualizzato da [Page1.aspx]
  • In [5], l'URL visualizzato nel browser è quello della pagina [Default.aspx]

2.9. Reindirizzamento da una pagina all'altra

Qui presentiamo un'altra tecnica che è funzionalmente simile alla precedente: quando l'utente richiede la pagina [Default.aspx] tramite una richiesta POST, riceve in risposta un'altra pagina [Page2.aspx]. Nel metodo precedente, la richiesta dell'utente veniva elaborata in successione da due pagine: [Default.aspx] e [Page1.aspx]. Nel metodo di reindirizzamento delle pagine che stiamo presentando ora, ci sono due richieste separate dal browser:

  • In [1], il browser invia una richiesta POST alla pagina [Default.aspx]. Questa pagina elabora la richiesta e invia al browser una cosiddetta risposta di reindirizzamento. Questa risposta è un semplice flusso HTTP (righe di testo) che istruisce il browser a reindirizzarsi a un altro URL [Page2.aspx]. [Default.aspx] non invia alcun contenuto HTML in questa prima risposta.
  • In [2], il browser effettua una richiesta GET alla pagina [Page2.aspx]. Questa pagina viene quindi inviata come risposta al browser.
  • Se la pagina [Default.aspx] vuole passare informazioni alla pagina [Page2.aspx], può farlo tramite la sessione dell'utente. A differenza del metodo precedente, qui non è possibile utilizzare il contesto della richiesta, poiché ci sono due richieste separate e quindi due contesti separati. Dobbiamo quindi utilizzare la sessione dell'utente per consentire alle pagine di comunicare tra loro.

Come fatto per [Page1.aspx], aggiungiamo la pagina [Page2.aspx] al progetto:

  • in [1], [Page2.aspx] è stata aggiunta al progetto
  • in [2], l'aspetto visivo di [Page2.aspx]
  • in [3], aggiungiamo un componente LinkButton [4] alla pagina [Default.aspx], che reindirizzerà l'utente a [Page2.aspx].

Il codice sorgente di [Page2.aspx] è simile a quello di [Page1.aspx]:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Page2.aspx.cs" Inherits="Intro.Page2" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>Page2</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h1>
      Page 2</h1>
    <asp:Label ID="Label1" runat="server"></asp:Label>
    <br />
    <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/Default.aspx">Retour 
      vers page [Default]</asp:HyperLink>
  </div>
  </form>
</body>
</html>

In [Default.aspx], l'aggiunta del componente LinkButton ha generato il seguente codice sorgente:


<asp:LinkButton ID="LinkButtonToPage2" runat="server" 
    onclick="LinkButtonToPage2_Click">Redirection vers Page2</asp:LinkButton>

Il gestore [LinkButtonToPage2_Click] gestisce il reindirizzamento a [Page2.aspx]. Il suo codice in [Default.aspx.cs] è il seguente:


    protected void LinkButtonToPage2_Click(object sender, EventArgs e)
    {
      // put a msg in the session
      Session["msg2"] = "Message de [Default.aspx] pour [Page2.aspx]";
      // the client is redirected to [Page2.aspx]
      Response.Redirect("Page2.aspx");
}
  • Riga 4: Impostiamo un messaggio nella sessione dell'utente
  • riga 5: L'oggetto Response è una proprietà di ogni pagina ASPX. Rappresenta la risposta inviata al client. Dispone di un metodo Redirect che fa sì che la risposta inviata al client sia una richiesta di reindirizzamento HTTP.

Quando il browser riceve il comando di reindirizzamento a [Page2.aspx], invierà una richiesta GET a quella pagina. Su quella pagina verrà eseguito il metodo [Page_Load]. Lo useremo per recuperare il messaggio memorizzato da [Default.aspx] nella sessione e visualizzarlo. Il codice per [Page2.aspx.cs] è il seguente:


using System;

namespace Intro
{
  public partial class Page2 : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      // displays the msg set in the session by [Default.aspx]
      Label1.Text = Session["msg2"] as string;
    }
  }
}

All'esecuzione, si ottengono i seguenti risultati:

  • In [1], si fa clic sul link di reindirizzamento su [Default.aspx]. Viene inviata una richiesta POST alla pagina [Default.aspx]
  • In [2], il browser è stato reindirizzato a [Page2.aspx]. Ciò è visibile nell'URL visualizzato dal browser. Nel metodo precedente, questo URL era quello di [Default.aspx] poiché l'unica richiesta effettuata dal browser era diretta a quell'URL. Qui, c'è una prima richiesta POST a [Default.aspx], seguita da una seconda richiesta GET a [Page2.aspx] all'insaputa dell'utente.
  • In [3], vediamo che [Page2.aspx] ha recuperato correttamente il messaggio inserito da [Default.aspx] nella sessione.

2.10. Conclusione

Abbiamo introdotto, utilizzando alcuni esempi, i concetti di ASP.NET che ci saranno utili nel resto di questo documento. Questa introduzione non copre le sottigliezze della comunicazione client/server in un'applicazione web. Per questo, potete leggere: