5. La vista e il relativo modello
5.1. Introduction
Torniamo all’architettura di un’applicazione ASP.NET MVC:
![]() |
Nel capitolo precedente, abbiamo visto come ASP.NET MVC presentasse le informazioni della richiesta [1] a un'azione [2a] sotto forma di un modello che poteva contenere vincoli di validazione. Questo modello veniva fornito come input all’azione e lo abbiamo chiamato modello dell’azione. Ci occupiamo ora del risultato più comune di un'azione, il tipo [ViewResult] che corrisponde a una vista V [3] accompagnata dal suo modello M [2c]. Questo modello sarà denominato modello della vista V, da non confondere con il modello dell’azione che abbiamo appena esaminato. Uno costituisce l’input dell’azione, l’altro ne è l’output.
Iniziamo creando un nuovo progetto [Exemple-03] [1] sempre all’interno della stessa soluzione, di tipo base ASP.NET MVC:
![]() |
Creiamo un controller denominato [First] [2]. Il codice generato per questo controller è il seguente:
using System.Web.Mvc;
namespace Exemple_03.Controllers
{
public class FirstController : Controller
{
public ActionResult Index()
{
return View();
}
}
}
- righe 7-10: è stata creata un'azione [Index]. Il tipo del risultato del metodo [Index] è quello della classe [ActionResult], da cui derivano la maggior parte dei possibili risultati di un'azione;
- riga 9: il metodo [View] della classe [Controller] (riga 5) restituisce un tipo [ViewResult] che deriva da [ActionResult]. Questo metodo ammette numerose sovraccarichi. Ne vedremo alcune. La principale è la seguente:
![]() |
- il primo parametro è il nome della vista. Se è assente, la vista utilizzata è quella che porta lo stesso nome dell’azione che produce il [ViewResult] e che verrà cercata nella cartella [/Views/{controller}], dove {controller} è il nome del controller;
- il secondo è il modello della vista. Se è assente, la vista non ha un modello.
Il metodo [Index] riportato di seguito:
public ActionResult Index()
{
return View();
}
richiede alla vista [/Views/First/Index.cshtml] di visualizzarsi. Non le trasmette alcun modello. Creiamo [1] nella cartella [/Views/First]:
![]() |
poi creiamo al suo interno la vista [Index] [2]:
![]() |
![]() |
Si specifica il nome della vista come [3]. Questa viene creata come [4]. Il codice generato è il seguente:
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
</div>
</body>
</html>
Si tratta di un classico HTML, tranne che per le righe 1-3 che sono codice C#. Il programma che gestisce le viste è chiamato motore delle viste. Si occupa di gestire tutto ciò che non è HTML per trasformarlo in HTML. Alla fine, infatti, è questo che verrà inviato al client. Il motore di visualizzazione in questo caso si chiama [Razor]. Consente di includere codice C# in una vista. [Razor] interpreterà questo codice C# e genererà da esso codice HTML. Ecco alcune regole di base per l’inclusione di codice C# in una vista:
- il passaggio da HTML a C# avviene all’incontro con il carattere @ (riga 1). Se questo carattere introduce un blocco di codice, si inseriranno le parentesi graffe (righe 1 e 3). Se introduce una variabile di cui si desidera recuperare il valore, si scriverà semplicemente @variabile;
- il passaggio da C# a HTML avviene all'incontro con il carattere < (riga 5). A volte è necessario forzare questa conversione, in particolare quando si include nella pagina del testo in chiaro senza il tag HTML. In tal caso, si utilizzerà il tag <text> per inserire il testo: <text>qui del testo in chiaro</text>.
La riga 2 sopra indica che la vista [Index] non ha una pagina master.
Modifichiamo la vista come segue:
@{
Layout = null;
string vue = "Index";
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
<h3>Vue @vue</h3>
</div>
</body>
</html>
- riga 3: definisce una variabile C#;
- riga 15: visualizza il valore di questa variabile.
Ora richiamiamo la vista URL [/First/Index]:
![]() |
Il codice HTML ricevuto è il seguente:
Si tratta di un documento HTML puro. Tutto il codice C# è scomparso.
5.2. Utilizzare il codice [ViewBag] per passare informazioni alla vista
Creiamo una nuova azione denominata [Action01] associata alla vista [Action01.cshtml]:
![]() |
L'azione [Action01] è la seguente:
// Azione01
public ViewResult Action01()
{
ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
return View();
}
- riga 4: si utilizza la proprietà [ViewBag] del controller. Si tratta di un oggetto dinamico a cui è possibile aggiungere proprietà, come avviene alla riga 4. Questo oggetto ha la particolarità di essere accessibile anche alla vista. Si tratta quindi di un modo per trasmettergli informazioni;
- riga 5: viene richiesta la vista predefinita dell’azione. Si tratta della vista [/First/Action01.cshtml]. Non le viene trasmesso alcun modello.
La vista [Action01.cshtml] è la seguente:
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action01</title>
</head>
<body>
<div>
<h4>@ViewBag.info</h4>
</div>
</body>
</html>
- riga 14: viene visualizzata la proprietà [ViewBag.info].
Proviamo. Richiediamo URL [/First/Action01]:
![]() |
5.3. Utilizzare un modello fortemente tipizzato per passare informazioni alla vista
Il metodo precedente presenta lo svantaggio di non consentire il rilevamento degli errori prima dell’esecuzione. Pertanto, se la vista [Action01.cshtml] utilizza il codice
<h4>@ViewBag.Info</h4>
si verificherà un errore poiché la proprietà [Info] non esiste. Quella creata dall’azione [Action01] si chiama [info]. È quindi possibile utilizzare un modello fortemente tipizzato per evitare questo inconveniente.
In uno degli esempi esaminati in precedenza, l’azione era la seguente:
// Azione10
public ContentResult Action10(ActionModel03 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("email={0}, jour={1}, info1={2}, info2={3}, info3={4}, erreurs={5}",
modèle.Email, modèle.Jour, modèle.Info1, modèle.Info2, modèle.Info3, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
L'azione [Action10] trasmetteva al proprio client sei informazioni (Email, Giorno, Info1, Info2, Info3, errori) sotto forma di stringa di caratteri. Trasmetteremo queste informazioni in un modello di vista [ViewModel01]. Poiché questo modello riprende informazioni da [ActionModel03], lo faremo derivare da questa classe.
Iniziamo copiando [ActionModel03] dal progetto [Exemple-02] nel progetto [Exemple-03] attuale:
![]() |
e ne modifichiamo lo spazio dei nomi in modo che corrisponda a quello del progetto [Exemple-03]:
using System.ComponentModel.DataAnnotations;
namespace Exemple_03.Models
{
public class ActionModel03
{
[Required(ErrorMessage = "Le paramètre email est requis")]
[EmailAddress(ErrorMessage = "Le paramètre email n'a pas un format valide")]
public string Email { get; set; }
[Required(ErrorMessage = "Le paramètre jour est requis")]
[RegularExpression(@"^\d{1,2}$", ErrorMessage = "Le paramètre jour doit avoir 1 ou 2 chiffres")]
public string Jour { get; set; }
[Required(ErrorMessage = "Le paramètre info1 est requis")]
[MaxLength(4, ErrorMessage = "Le paramètre info1 ne peut avoir plus de 4 caractères")]
public string Info1 { get; set; }
[Required(ErrorMessage = "Le paramètre info2 est requis")]
[MinLength(2, ErrorMessage = "Le paramètre info2 ne peut avoir moins de 2 caractères")]
public string Info2 { get; set; }
[Required(ErrorMessage = "Le paramètre info3 est requis")]
[MinLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
[MaxLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
public string Info3 { get; set; }
}
}
- riga 2: il nuovo spazio dei nomi;
Quindi creiamo la classe [ViewModel01]:
![]() |
Il codice di [ViewModel01] è il seguente:
namespace Exemple_03.Models
{
public class ViewModel01 : ActionModel03
{
public string Erreurs { get; set; }
}
}
- riga 3: la classe eredita da [ActionModel03] e quindi dalle proprietà di [Email, Jour, Info1, Info2, Info3];
- riga 5: le viene aggiunta la proprietà [Erreurs].
Ora scriviamo l’azione [Action02] che:
- accetta in ingresso il modello di azione [ActionModel03];
- e restituisce in uscita il modello di vista [ViewModel01].
Il suo codice è il seguente:
// Azione02
public ViewResult Action02(ActionModel03 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
return View(new ViewModel01(){Email=modèle.Email, Jour=modèle.Jour, Info1=modèle.Info1, Info2=modèle.Info2, Info3=modèle.Info3, Erreurs=erreurs});
}
- riga 1: [Action02] riceve il modello di azione [ActionModel03]. Restituisce un risultato di tipo [ViewResult];
- riga 4: gli errori relativi al modello di azione [ActionModel03] vengono aggregati nella stringa [erreurs]. Il metodo [getErrorMessagesFor] è stato descritto a pagina 65 ed è stato incluso nel controller [First] del nuovo progetto;
- riga 5: il metodo [View] viene chiamato con un parametro. Si tratta del modello della vista. Quest’ultima non è specificata. Verrà quindi utilizzata la vista predefinita [/Views/First/Action02]. Il modello della vista [ViewModel01] viene istanziato e inizializzato con le cinque informazioni del modello dell’azione [ActionModel03] e l’informazione [erreurs] costruita alla riga 4.
Ora costruiamo la vista [/First/Action02.cshtml]:
![]() |
Il suo codice è il seguente:
@model Exemple_03.Models.ViewModel01
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action02</title>
</head>
<body>
<h3>Informations du modèle de vue</h3>
<ul>
<li>Email : @Model.Email</li>
<li>Jour : @Model.Jour</li>
<li>Info1 : @Model.Info1</li>
<li>Info2 : @Model.Info2</li>
<li>Info3 : @Model.Info3</li>
<li>Erreurs : @Model.Erreurs</li>
</ul>
</body>
</html>
- la novità si trova nella riga 1. La notazione [@model] definisce il tipo di modello della vista. Questo modello viene poi richiamato dalla notazione [@Model] (righe 16-21);
- righe 15-22: le informazioni del modello vengono visualizzate in un elenco.
Vediamo alcuni esempi di esecuzione dell’azione [Action02].
Innanzitutto senza parametri:
![]() |
poi con parametri errati:
![]() |
e infine con parametri corretti:
![]() |
In questo esempio, il modello della vista [ViewModel01] riprende le informazioni del modello di azione [ActionModel03]. Questo accade spesso. È quindi possibile utilizzare un unico modello che fungerà sia da modello di azione che da modello di vista. Creiamo un nuovo modello [ActionModel04]:
![]() |
che sarà il seguente:
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Exemple_03.Models
{
[Bind(Exclude="Erreurs")]
public class ActionModel04
{
// ---------------------- Azione --------------------------------
[Required(ErrorMessage = "Le paramètre email est requis")]
[EmailAddress(ErrorMessage = "Le paramètre email n'a pas un format valide")]
public string Email { get; set; }
[Required(ErrorMessage = "Le paramètre jour est requis")]
[RegularExpression(@"^\d{1,2}$", ErrorMessage = "Le paramètre jour doit avoir 1 ou 2 chiffres")]
public string Jour { get; set; }
[Required(ErrorMessage = "Le paramètre info1 est requis")]
[MaxLength(4, ErrorMessage = "Le paramètre info1 ne peut avoir plus de 4 caractères")]
public string Info1 { get; set; }
[Required(ErrorMessage = "Le paramètre info2 est requis")]
[MinLength(2, ErrorMessage = "Le paramètre info2 ne peut avoir moins de 2 caractères")]
public string Info2 { get; set; }
[Required(ErrorMessage = "Le paramètre info3 est requis")]
[MinLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
[MaxLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
public string Info3 { get; set; }
// ---------------------- vista --------------------------------
public string Erreurs { get; set; }
}
}
- righe 8-28: il modello dell’azione con i relativi vincoli di integrità. Questi campi faranno parte anche della vista;
- riga 31: una proprietà specifica del modello della vista. È stata esclusa dal modello dell'azione tramite l'annotazione alla riga 5.
Creiamo la seguente nuova azione [Action03]:
// Azione03
public ViewResult Action03(ActionModel04 modèle)
{
modèle.Erreurs = getErrorMessagesFor(ModelState);
return View(modèle);
}
- riga 2: [Action03] riceve il modello di azione di tipo [ActionModel04];
- riga 5: e restituisce come modello di vista proprio questo modello;
- riga 4: integrata con l’informazione [Erreurs];
Non ci resta che creare la vista [/First/Action03.cshtml]:
![]() |
- in [1]: clic destro sul codice di [Action03], quindi su [Ajouter une vue];
- in [2]: il nome della vista proposto di default;
- in [3]: specificare che si sta creando una vista fortemente tipizzata;
- in [4]: selezionare dall'elenco a discesa la classe corretta, in questo caso la classe [ActionModel04];
- in [5]: la vista creata.
Assegniamo alla vista [Action03] lo stesso codice della vista [Action02]. Cambiano solo il modello di vista (riga 1) e il titolo della pagina (riga 11):
@model Exemple_03.Models.ActionModel04
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action03</title>
</head>
<body>
<h3>Informations du modèle de vue</h3>
<ul>
<li>Email : @Model.Email</li>
<li>Jour : @Model.Jour</li>
<li>Info1 : @Model.Info1</li>
<li>Info2 : @Model.Info2</li>
<li>Info3 : @Model.Info3</li>
<li>Erreurs : @Model.Erreurs</li>
</ul>
</body>
</html>
Ora eseguiamo l’azione [Action03] senza parametri:
![]() |
I risultati sono gli stessi di prima. È frequente utilizzare lo stesso modello sia per l’azione che per la vista, poiché il modello della vista spesso riprende informazioni dal modello dell’azione. Si utilizza quindi un modello più ampio, utilizzabile sia dall’azione che dalla vista che essa genera. Si dovrà prestare attenzione a escludere dal binding dei dati le informazioni che non appartengono al modello dell’azione. In caso contrario, un utente ben informato potrebbe inizializzare parti del modello della vista a nostra insaputa.
5.4. [Razor] – primi passi
Presenteremo ora alcuni elementi delle viste [Razor], in particolare le istruzioni foreach e if.
Supponiamo di voler presentare un elenco di persone in una tabella HTML. Il modello della vista potrebbe essere il seguente [ViewModel02]:
![]() |
namespace Exemple_03.Models
{
public class ViewModel02
{
public Personne[] Personnes { get; set; }
public ViewModel02()
{
Personnes = new Personne[] { new Personne { Nom = "Pierre", Age = 44 }, new Personne { Nom = "Pauline", Age = 12 } };
}
}
public class Personne
{
public string Nom { get; set; }
public int Age { get; set; }
}
}
- la vista del modello è la classe [ViewModel02], righe 3-10;
- riga 5: il modello possiede un array di persone di tipo [Personne] definito alle righe 12-16;
- righe 6-10: il costruttore del modello inizializza la proprietà [Personnes] della riga 5 con un array di due persone.
L'azione che genera questo modello in uscita sarà la seguente [Action04]:
// Azione04
public ViewResult Action04()
{
return View(new ViewModel02());
}
- riga 2: l'azione non ha alcun modello in ingresso;
- riga 4: passa alla sua vista predefinita, un'istanza del modello [ViewModel02] che abbiamo appena definito.
La vista [Action04.cshtml] visualizzerà il modello [ViewModel02]:
![]() |
Il codice della vista [Action04.cshtml] è il seguente:
@model Exemple_03.Models.ViewModel02
@using Exemple_03.Models
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action04</title>
</head>
<body>
<table border="1">
<thead>
<tr>
<th>Nom</th>
<th>Age</th>
</tr>
</thead>
<tbody>
@foreach (Personne p in Model.Personnes)
{
<tr>
<td>@p.Nom</td>
<td>@p.Age</td>
</tr>
}
</tbody>
</table>
</body>
</html>
- riga 1: il modello della vista;
- riga 2: l'importazione dello spazio dei nomi della classe [Personne] utilizzata alla riga 24;
- righe 16-32: l'array HTML che visualizza le persone del modello;
- riga 24: l'inizio del codice C# è segnalato dal carattere @. L'istruzione [foreach] eseguirà un ciclo su tutte le persone del modello;
- righe 26-27: il carattere < interrompe il codice C# e avvia il codice HTML. Poi, nuovamente, il carattere @ per passare al codice C# e scrivere il nome della persona. Infine, ancora il carattere < che fa tornare alla modalità HTML;
- riga 28: si scrive l’età della persona.
L'esecuzione dell'azione [Action04] fornisce il seguente risultato:
![]() |
Altri elementi di una vista possono essere alimentati da una collezione: elenchi, a tendina o meno, pulsanti di opzione, caselle di controllo. Consideriamo il seguente nuovo esempio che visualizza un elenco a tendina.
Il modello [ViewModel05] sarà il seguente:
![]() |
namespace Exemple_03.Models
{
public class ViewModel05
{
public Personne2[] Personnes { get; set; }
public int SelectedId { get; set; }
public ViewModel05()
{
Personnes = new Personne2[] {
new Personne2 { Id = 1, Prénom = "Pierre", Nom = "Martino" },
new Personne2 { Id = 2, Prénom = "Pauline", Nom = "Pereiro" },
new Personne2 { Id = 3, Prénom = "Jacques", Nom = "Alfonso" } };
SelectedId = 2;
}
}
public class Personne2
{
public int Id { get; set; }
public string Nom { get; set; }
public string Prénom { get; set; }
}
}
- riga 18: una classe [Personne2] con tre proprietà;
- riga 3: il modello [ViewModel05] della vista;
- riga 5: l'elenco delle persone da visualizzare nel menu a tendina nella forma [Prénom Nom];
- riga 6: l'[Id] della persona da selezionare nell'elenco a discesa;
- righe 8-16: il costruttore che crea un array di tre persone (righe 10-13) e imposta il codice [Id] della persona che deve apparire selezionata.
La vista [Action05.cshtml] visualizzerà questo modello:
![]() |
Il suo codice è il seguente:
@model Exemple_03.Models.ViewModel05
@using Exemple_03.Models
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action05</title>
</head>
<body>
<select>
@foreach (Personne2 p in Model.Personnes)
{
string selected = "";
if (p.Id == Model.SelectedId)
{
selected = "selected=\"selected\"";
}
<option value="@p.Id" @selected>@p.Prénom @p.Nom</option>
}
</select>
</body>
</html>
Le caratteristiche del menu a tendina HTML sono state illustrate nel paragrafo 2.5.2.6. Ricordiamole:
Combo | <select size="1" name="cmbValeurs"> <option value="1">scelta1</option> <option selected="selected" value="2">opzione2</option> <option value="3">scelta3</option> </select> |
tag HTML | <select size=".." name=".."> <option [selected="selected"] value=”v”>...</option> ... </select> visualizza in un elenco i testi compresi tra i tag <option>...</option> |
attributi | name="cmbValeurs": nome del controllo. size="1": numero di elementi dell'elenco visibili. size="1" rende l'elenco equivalente a una casella combinata. selected="selected": se questa parola chiave è presente per un elemento dell'elenco, quest'ultimo appare selezionato nell'elenco. Nel nostro esempio sopra riportato, l'elemento dell'elenco choix2 appare come l'elemento selezionato della casella combinata quando questa viene visualizzata per la prima volta. value=”v”: se l’elemento viene selezionato dall’utente, è questo valore ([v]) che viene inviato al server. In assenza di questo attributo, è il testo visualizzato e selezionato che viene inviato al server. |
Il codice delle righe 17-25 genera i tag <option> che vengono inseriti all’interno del tag <select> della riga 16.
- riga 17: si scorre l’elenco delle persone del modello;
- riga 20: si verifica se la persona corrente è quella che deve essere selezionata. In caso affermativo, si prepara il testo selected="selected" da inserire nel tag <option>;
- riga 24: viene scritto il tag <option>.
Richiediamo l'azione [Action05]:
![]() |
- in [1,2], le persone vengono visualizzate nella forma [Prénom Nom];
- In [1,2], la persona selezionata è quella per cui [Id] è uguale a 2.
Esaminiamo ora il codice sorgente HTML della pagina sopra riportata:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action05</title>
</head>
<body>
<select>
<option value="1" >Pierre Martino</option>
<option value="2" selected="selected">Pauline Pereiro</option>
<option value="3" >Jacques Alfonso</option>
</select>
</body>
</html>
- righe 10-12: i tre tag <option> generati dal codice [Razor];
- riga 11: è stata effettivamente selezionata la persona di [Id]=2.
I due esempi sopra riportati ci bastano. Quando si scrive una vista [Razor], bisogna resistere alla tentazione di inserirvi della logica. Il codice C# ce lo consentirebbe. Tuttavia, nel modello MVC, la logica deve trovarsi nell’azione o nei livelli inferiori [Metier, DAO], ma non nella vista. Anche rispettando il modello MVC, ci si può ritrovare con molta logica nella vista per calcolare valori intermedi. Ciò potrebbe significare che il modello utilizzato non è sufficientemente dettagliato. Esso deve contenere i valori finali di cui la vista ha bisogno, in modo che non debba calcolarli autonomamente. Una buona vista è una vista in cui la logica è ridotta al minimo e in cui la struttura HTML della vista rimane chiara. Se si inserisce troppo codice C#, la struttura HTML può diventare illeggibile.
Nell’esempio sopra riportato, il menu a tendina potrebbe essere utilizzato da un utente e si vorrebbe quindi sapere quale persona ha selezionato. A tal fine è necessario un modulo.
5.5. Modulo – primi passi
Il modulo presentato all’utente sarà il seguente:
![]() |
Il modello della vista sarà il modello [ViewModel05] già utilizzato in precedenza. L’azione che visualizzerà questa vista sarà la seguente:
// Azione06-GET
[HttpGet]
public ViewResult Action06()
{
return View("Action06Get",new ViewModel05());
}
- riga 2: l'azione può essere richiesta solo tramite un comando HTTP GET;
- riga 5: la vista [/First/Action06Get.cshtml] verrà visualizzata utilizzando come modello un'istanza di tipo [ViewModel05].
La vista [/First/Action06Get.cshtml] sarà la seguente:
![]() |
@model Exemple_03.Models.ViewModel05
@using Exemple_03.Models
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action06-GET</title>
</head>
<body>
<h3>Action06 - GET</h3>
<p>Choisissez une personne</p>
<form method="post" action="/First/Action06">
<select name="personneId">
@foreach (Personne2 p in Model.Personnes)
{
string selected = "";
if (p.Id == Model.SelectedId)
{
selected = "selected=\"selected\"";
}
<option value="@p.Id" @selected>@p.Prénom @p.Nom</option>
}
</select>
<input name="valider" type="submit" value="Valider" />
</form>
</body>
</html>
Le principali novità sono le seguenti:
- riga 18: affinché il browser possa trasmettere le informazioni inserite da un utente, è necessario un modulo. È il tag <form> delle righe 18 e 31 a delimitarlo.
Il tag HTML <form> è stato presentato nel paragrafo 2.5.2.1. Ricordiamone le caratteristiche:
modulo | |
tag HTML | <form name="..." method="..." action="...">...</form> |
attributi | name="frmexemple": nome del modulo - facoltativo method="...": metodo utilizzato dal browser per inviare al server Web i valori raccolti nel modulo action="...": URL a cui verranno inviati i valori raccolti nel modulo. Un modulo Web è racchiuso tra i tag <form>...</form>. Il modulo può avere un nome (name="xx"). Questo vale per tutti i controlli presenti in un modulo. Lo scopo di un modulo è quello di raccogliere le informazioni fornite dall’utente tramite tastiera o mouse e di inviarle a un URL del server web. Quale? Quello indicato nell’attributo action="URL". Se questo attributo è assente, le informazioni verranno inviate al URL del documento in cui si trova il modulo. Un client Web può utilizzare due metodi diversi, denominati POST e GET, per inviare dati a un server Web. L'attributo method="méthode", con method uguale a GET o POST, del tag <form> indica al browser il metodo da utilizzare per inviare le informazioni raccolte nel modulo all'URL specificato dall'attributo action="URL". Quando l’attributo method non è specificato, viene utilizzato per impostazione predefinita il metodo GET. |
- riga 18: si vede che i valori del modulo saranno inviati a URL [/First/Action06] tramite un comando HTTP POST;
- riga 30: un modulo deve avere un pulsante di tipo [submit]. È questo pulsante che attiva l’invio dei valori inseriti a URL, specificato dall’attributo [action] del tag <form>.
Cosa trasmetterà esattamente il browser quando l’utente cliccherà sul pulsante [Valider]? Questo è stato spiegato nel paragrafo 2.5.3.1. Ricordiamo quanto detto:
controllo HTML | visivo | valore/i restituito/i |
<input type="radio" value="Sì" name="R1"/>Sì <input type="radio" name="R1" value="no" checked="checked"/>No | R1=Sì - il valore dell'attributo value del pulsante di opzione selezionato dall'utente. | |
<input type="checkbox" name="C1" value="uno"/>1 <input type="checkbox" name="C2" value="due" checked="checked"/>2 <input type="checkbox" name="C3" value="tre"/>3 | C1=uno C2=due - valori degli attributi value delle caselle selezionate dall'utente | |
<input type="text" name="txtSaisie" size="20" value="alcune parole"/> | txtInserimento=programmazione+Web - testo digitato dall'utente nel campo di immissione. Gli spazi sono stati sostituiti dal segno + | |
<input type="password" name="txtMdp" size="20" value="unMotDePasse"/> | txtMdp=questoèsegreto - testo digitato dall'utente nel campo di immissione | |
<textarea rows="2" name="areaSaisie" cols="20"> riga1 riga 2 riga3 </textarea> | areaInserimento=le+basi+della%0D%0A programmazione+Web - testo digitato dall'utente nel campo di immissione. %OD%OA è il carattere di fine riga. Gli spazi sono stati sostituiti dal segno + | |
<select size="1" name="cmbValeurs"> <option value='1'>scelta1</option> <option selected="selected" value='2'>opzione2</option> <option value='3'>opzione3</option> </select> | cmbValori=3 - attributo [value] dell'elemento selezionato dall'utente | |
<select size="3" name="lst1"> <option selected="selected" value='1'>lista1</option> <option value='2'>lista2</option> <option value='3'>lista3</option> <option value='4'>lista4</option> <option value='5'>lista5</option> </select> | ![]() | lst1=3 - attributo [value] dell'elemento selezionato dall'utente |
<select size="3" name="lst2" multiple="multiple"> <option selected="selected" value='1'>lista1</option> <option value='2'>lista2</option> <option selected="selected" value='3'>lista3</option> <option value='4'>lista4</option> <option value='5'>lista5</option> </select> | lst2=1 lst2=3 - attributi [value] degli elementi selezionati dall'utente | |
<input type="submit" value="Invia" name="cmdRenvoyer"/> | cmdInvia=Invia - nome e attributo value del pulsante utilizzato per inviare i dati del modulo al server | |
<input type="hidden" name="secret" value="uneValeur"/> | secret=unValore - attributo value del campo nascosto |
Nel nostro modulo sono presenti due tag in grado di inviare un valore:
<select name="personneId">
...
</select>
e
<input name="valider" type="submit" value="Valider" />
Se l'utente seleziona la persona n. 2, i valori inviati assumeranno la forma:
I nomi dei parametri corrispondono agli attributi [name] dei tag interessati dal POST. Senza questo attributo, i tag non inviano alcun valore. Pertanto, nell’esempio sopra riportato, si potrebbe omettere l’attributo name="valider" del pulsante [submit]. Il valore inviato è l’attributo [value] del pulsante. In questo caso, tale informazione non ci interessa. A volte i moduli presentano più pulsanti del tipo [submit]. In tal caso è importante sapere quale pulsante è stato cliccato. Si assegnerà quindi l’attributo [name] ai diversi pulsanti.
Il tag <select> è composto da una serie di tag <option>:
<select name="personneId">
<option value="1" >Pierre Martino</option>
<option value="2" selected="selected">Pauline Pereiro</option>
<option value="3" >Jacques Alfonso</option>
</select>
Viene inviato il valore dell’attributo [value] dell’opzione selezionata. In assenza di tale attributo, viene inviato il testo visualizzato dall’opzione, ad esempio [Pierre Martino].
La stringa
verrà pubblicata nella seguente URL [/First/Action06]:
// Azione06-POST
[HttpPost]
public ViewResult Action06(ActionModel06 modèle)
{
return View("Action06Post",modèle);
}
Forse ricorderete che avevamo già un’azione [Action06]:
// Azione06-GET
[HttpGet]
public ViewResult Action06()
{
return View("Action06Get",new ViewModel05());
}
È possibile avere due azioni con lo stesso nome, a condizione che non elaborino gli stessi comandi HTTP:
- [Action06] della riga 3 gestisce un POST (riga 2);
- [Action06] della riga c gestisce un GET (riga b).
L'azione [Action06] che gestisce il POST riceverà la seguente stringa di parametri:
Abbiamo bisogno di un modello di azione per incapsulare questi valori. Sarà il seguente modello [ActionModel06]:
![]() |
using System.ComponentModel.DataAnnotations;
namespace Exemple_03.Models
{
public class ActionModel06
{
[Required(ErrorMessage = "Le paramètre [personneId] est requis")]
public int PersonneId { get; set; }
[Required(ErrorMessage = "Le paramètre [valider] est requis")]
public string Valider { get; set; }
}
}
L'azione [Action06] riceve questo modello e lo trasmette così com'è alla vista [Action06Post] (riga 5 dell'azione) seguente:
![]() |
@model Exemple_03.Models.ActionModel06
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action06Post</title>
</head>
<body>
<h3>Action06 - POST</h3>
Valeurs postées :
<ul>
<li>ID de la personne sélectionnée : @Model.PersonneId</li>
<li>Commande utilisée : @Model.Valider</li>
</ul>
</body>
</html>
Il modello viene visualizzato alle righe 18 e 19.
Vediamo un esempio:
![]() |
In [1] si seleziona la terza persona di [Id], pari a 3. In [2] si invia il modulo. In [3], i valori ricevuti. In [4,5], si vede che è stato richiamato lo stesso URL, uno tramite un GET [4], l'altra da un POST e un [5]. Ciò non è visibile nel URL.
Nella vista visualizzata dopo il POST, si potrebbero preferire i nom e prénom della persona selezionata piuttosto che il suo numero. È quindi necessario aggiornare la vista del POST e il relativo modello.
Creiamo un’azione [Action07] per gestire questo caso. Questa azione dovrà utilizzare la sessione dell’utente per memorizzarvi l’elenco delle persone. Seguiremo il modello esaminato nel paragrafo 4.10, che consente di includere i dati di ambito [Application] e [Session] nel modello dell’azione.
Il modello della sessione sarà la seguente classe [SessionModel]:
![]() |
namespace Exemple_03.Models
{
public class SessionModel
{
public Personne2[] Personnes { get; set; }
}
}
- riga 2: la sessione memorizzerà l’elenco delle persone visualizzate nel menu a tendina;
Dobbiamo collegare il tipo precedente [SessionModel] a un binder che chiameremo [SessionModelBinder]. Questo sarà lo stesso descritto a pagina 82:
![]() |
using System.Web.Mvc;
namespace Exemple_03.Infrastructure
{
public class SessionModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// si restituiscono i dati dell'ambito [Session]
return controllerContext.HttpContext.Session["data"];
}
}
}
Il collegamento tra il modello [SessionModel] e i suoi binder e [SessionModelBinder] avviene in [Global.asax]:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
...
// model binders
ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
}
// Sessione
public void Session_Start()
{
Session["data"] = new SessionModel();
}
}
- riga 8: il collegamento del modello al relativo binder avviene in [Application_Start];
- riga 13: un'istanza di tipo [SessionModel] viene inserita nella sessione associata alla chiave [data].
Fatto ciò, l'azione [Action07] è la seguente:
// Azione07-GET
[HttpGet]
public ViewResult Action07(SessionModel session)
{
ViewModel05 modèleVue = new ViewModel05();
session.Personnes= modèleVue.Personnes;
return View("Action07Get", modèleVue);
}
- riga 3: l'azione recupera un tipo [SessionModel], ovvero il dato di ambito [Session] associato alla chiave [data];
- riga 5: si crea il modello della vista;
- riga 6: si inserisce nella sessione l'array delle persone. Ne avremo bisogno nella query successiva, quella relativa a POST. Il protocollo HTTP è un protocollo stateless. È necessario utilizzare una sessione per disporre di memoria tra una richiesta e l’altra. Una sessione è specifica per un utente ed è gestita dal server web;
- riga 7: viene visualizzata la vista [Action07Get.cshtml]. È la seguente:
@model Exemple_03.Models.ViewModel05
@using Exemple_03.Models
...
<body>
<h3>Action07 - GET</h3>
<p>Choisissez une personne</p>
<form method="post" action="/First/Action07">
....
</form>
</body>
</html>
È identica alla vista [Action06Get.cshtml] già esaminata. La differenza principale si trova alla riga 7: la vista URL alla quale verranno inviati i valori del modulo. Questi saranno elaborati dall'azione [Action07] successiva:
// Azione07-POST
[HttpPost]
public ViewResult Action07(SessionModel session, ActionModel06 modèle)
{
Personne2 personne = session.Personnes.Where(p => p.Id == modèle.PersonneId).First<Personne2>();
string strPersonne = string.Format("{0} {1}", personne.Prénom, personne.Nom);
return View("Action07Post", (object)strPersonne);
}
- riga 3: i valori inviati vengono incapsulati nel modello di azione [ActionModel06] già utilizzato in precedenza (di seguito):
using System.ComponentModel.DataAnnotations;
namespace Exemple_03.Models
{
public class ActionModel06
{
[Required(ErrorMessage = "Le paramètre [personneId] est requis")]
public int PersonneId { get; set; }
[Required(ErrorMessage = "Le paramètre [valider] est requis")]
public string Valider { get; set; }
}
}
- riga 3: il primo parametro è il dato di ambito [Session] associato alla chiave [data];
- riga 5: una richiesta LINQ recupera la persona con l'ID [Id] che è stato pubblicato;
- riga 6: si costruisce la stringa di caratteri che deve essere visualizzata dalla vista [Action07Post] (riga 8);
- riga 7: per richiamare il costruttore corretto [View], è necessario convertire il tipo [string] in [object].
La vista [Action07Post.cshtml] è la seguente:
@model string
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action07-Post</title>
</head>
<body>
<h3>Action07-POST</h3>
Vous avez sélectionné [@Model].
</body>
</html>
- riga 1: il modello è di tipo [string];
- riga 16: viene visualizzata la stringa di caratteri.
Ecco un esempio di esecuzione:
![]() | ![]() |
5.6. Modulo – un esempio completo
Nel paragrafo 2.5.2.1 abbiamo esaminato il seguente modulo HTML:
1 ![]() |
Esamineremo un'azione [Action08Get] che visualizza (GET) questo modulo e un'azione [Action08Post] che elabora (POST) i valori inseriti dall'utente. Uno schema classico.
Il modello della vista [1] sopra riportato sarà un'istanza della classe [ViewModel08]. Questa classe sarà al tempo stesso:
- il modello della vista generata da un GET sull'azione [Action08Get];
- il modello dell’azione [Action08Post] per una richiesta POST.
![]() |
![]() |
5.6.1. Il modello di ambito [Application]
Supponiamo che gli elementi visualizzati dai pulsanti di opzione, dalle caselle di controllo e dai vari elenchi siano dati dell'ambito [Application]. Si tratta di un caso frequente. Queste informazioni provengono da un file di configurazione o da un database utilizzati all’avvio dell’applicazione nel metodo [Application_Start] di [Global.asax]. Questo metodo si sviluppa come segue:
protected void Application_Start()
{
....
// model binders
ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
ModelBinders.Binders.Add(typeof(ApplicationModel), new ApplicationModelBinder());
// dati di ambito [Application]
Application["data"] = new ApplicationModel();
}
- riga 7: il tipo [ApplicationModel], che descriveremo tra poco, è associato al data binder [ApplicationModelBinder], già presentato a pagina 82;
- riga 10: un'istanza del tipo [ApplicationModel] viene registrata nel dizionario dell'applicazione, associata alla chiave [data].
La classe [ApplicationModel] serve a incapsulare tutti i dati dell’ambito [Application]. In questo caso incapsulerà i dati che il modulo deve visualizzare:
![]() |
namespace Exemple_03.Models
{
public class ApplicationModel
{
// le collezioni da visualizzare nel modulo
public Item[] RadioButtonFieldItems { get; set; }
public Item[] CheckBoxesFieldItems { get; set; }
public Item[] DropDownListFieldItems { get; set; }
public Item[] SimpleChoiceListFieldItems { get; set; }
public Item[] MultipleChoiceListFieldItems { get; set; }
// inizializzazione dei campi e delle collezioni
public ApplicationModel()
{
RadioButtonFieldItems = new Item[]{
new Item {Value="1",Label="oui"},
new Item {Value="2", Label="non"}
};
CheckBoxesFieldItems = new Item[]{
new Item {Value="1",Label="1"},
new Item {Value="2", Label="2"},
new Item {Value="3", Label="3"}
};
DropDownListFieldItems = new Item[]{
new Item {Value="1",Label="choix1"},
new Item {Value="2", Label="choix2"},
new Item {Value="3", Label="choix3"}
};
SimpleChoiceListFieldItems = new Item[]{
new Item {Value="1",Label="liste1"},
new Item {Value="2", Label="liste2"},
new Item {Value="3", Label="liste3"},
new Item {Value="4", Label="liste4"},
new Item {Value="5", Label="liste5"}
};
MultipleChoiceListFieldItems = new Item[]{
new Item {Value="1",Label="liste1"},
new Item {Value="2", Label="liste2"},
new Item {Value="3", Label="liste3"},
new Item {Value="4", Label="liste4"},
new Item {Value="5", Label="liste5"}
};
}
// elemento delle collezioni
public class Item
{
public string Label { get; set; }
public string Value { get; set; }
}
}
}
- righe 45-49: l'elemento delle diverse collezioni del modulo. [Label] è il testo visualizzato dall'elemento del modulo, [Value] il valore inviato da tale elemento quando viene selezionato;
- riga 6: la raccolta visualizzata dal pulsante di opzione;
- riga 7: la collezione visualizzata dalle caselle di controllo;
- riga 8: la collezione visualizzata dall'elenco a discesa;
- riga 9: la raccolta visualizzata dall'elenco a selezione singola;
- riga 10: la collezione visualizzata dall'elenco a selezione multipla;
- righe 13-43: queste collezioni vengono inizializzate dal costruttore senza parametri della classe.
Le diverse collezioni andranno ad alimentare il seguente modulo:
![]() |
5.6.2. Il modello dell'azione [Action08Get]
Il modulo precedente verrà visualizzato dalla seguente azione [Action08Get]:
// Azione08-GET
[HttpGet]
public ViewResult Action08Get(ApplicationModel application)
{
ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
return View("Formulaire", new ViewModel08(application));
}
- riga 2: [Action08Get] risponderà solo a un comando [GET];
- riga 3: riceve come parametro il modello dell'applicazione che abbiamo appena descritto;
- riga 5: inizializza un'informazione nel contenitore dinamico [ViewBag];
- riga 6: visualizza la vista [/First/Formulaire.cshtml] con il modello [ViewModel08]. Questo modello sarà quello del modulo presentato in precedenza. A tal fine, si passa al costruttore il modello dell’applicazione che definisce gli elementi da visualizzare.
5.6.3. Il modello della vista [Formulaire]
La classe [ViewModel08] costituirà il modello del modulo. Tale classe è la seguente:
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Exemple_03.Models;
namespace Exemple_03.Models
{
public class ViewModel08
{
// i campi di immissione
public string RadioButtonField { get; set; }
public string[] CheckBoxesField { get; set; }
public string TextField { get; set; }
public string PasswordField { get; set; }
public string TextAreaField { get; set; }
public string DropDownListField { get; set; }
public string SimpleChoiceListField { get; set; }
public string[] MultipleChoiceListField { get; set; }
// le collezioni da visualizzare nel modulo
public ApplicationModel.Item[] RadioButtonFieldItems { get; set; }
public ApplicationModel.Item[] CheckBoxesFieldItems { get; set; }
public ApplicationModel.Item[] DropDownListFieldItems { get; set; }
public ApplicationModel.Item[] SimpleChoiceListFieldItems { get; set; }
public ApplicationModel.Item[] MultipleChoiceListFieldItems { get; set; }
// costruttori
public ViewModel08()
{
}
public ViewModel08(ApplicationModel application)
{
// inizializzazione delle collezioni
RadioButtonFieldItems = application.RadioButtonFieldItems;
CheckBoxesFieldItems = application.CheckBoxesFieldItems;
DropDownListFieldItems = application.DropDownListFieldItems;
SimpleChoiceListFieldItems = application.SimpleChoiceListFieldItems;
MultipleChoiceListFieldItems = application.MultipleChoiceListFieldItems;
// inizializzazione dei campi
RadioButtonField = "2";
CheckBoxesField = new string[] { "2" };
TextField = "quelques mots";
PasswordField = "secret";
TextAreaField = "ligne1\nligne2";
DropDownListField = "2";
SimpleChoiceListField = "3";
MultipleChoiceListField = new string[] { "1", "3" };
}
}
}
- in un modulo sono presenti due tipi di elementi: quelli da visualizzare e quelli oggetto di immissione dati;
- le righe 20-24 definiscono gli elementi da visualizzare. Si tratta delle diverse raccolte del modulo. Queste si trovano nel modello dell'applicazione (righe 34-38);
- righe 10-17: definiscono i campi di immissione del modulo;
- riga 10: [RadioButtonField] recupererà il valore inviato tramite le righe successive del modulo:
<!-- i pulsanti di opzione -->
<tr>
<td>Etes-vous marié(e)</td>
<td>
<input type="radio" name="RadioButtonField" value="1" />oui
<input type="radio" name="RadioButtonField" value="2" checked="checked"/>non
</td>
</tr>
Si noti, alle righe 5 e 6, che l’attributo [name] dei due pulsanti di opzione è il nome della proprietà che verrà inizializzata. Nei dati inviati, si troverà una stringa della forma:
param1=val1&RadioButtonField=2¶m2=val2
se l’utente ha selezionato l’opzione denominata [non]. È infatti l’attributo [value] dell’opzione selezionata che viene inviato.
- riga 11: [CheckBoxesField] recupererà i valori inviati dalle seguenti righe del modulo:
<!-- caselle di selezione -->
<tr>
<td>Cases à cocher</td>
<td>
<input type="checkbox" name="CheckBoxesField" value="1" />1
<input type="checkbox" name="CheckBoxesField" value="2" checked="checked"/>2
<input type="checkbox" name="CheckBoxesField" value="3" />3
</td>
Si noti, nelle righe 5 e 6, che l’attributo [name] delle caselle di selezione è il nome della proprietà che verrà inizializzata. Nei dati inviati si troverà una stringa della forma:
param1=val1&CheckBoxesField=2&CheckBoxesField=3¶m2=val2
se l’utente ha selezionato le caselle di controllo denominate [2] e [3]. È l’attributo [value] delle opzioni selezionate che viene inviato. Poiché è possibile inviare più parametri con lo stesso nome, [CheckBoxesField] è un array di valori e non un singolo valore. Se nessuna casella è selezionata, il parametro [CheckBoxesField] sarà assente dalla stringa inviata e la proprietà con lo stesso nome nel modello non verrà inizializzata. Ciò può risultare fastidioso, come vedremo.
- riga 12: [TextField] recupererà il valore inviato dalle seguenti righe del modulo:
<!-- campo di immissione testo a riga singola -->
<tr>
<td>Champ de saisie</td>
<td>
<input type="text" name="TextField" value="quelques mots" size="30" />
</td>
</tr>
Riga 5: l’attributo [name] del campo di immissione è il nome della proprietà che verrà inizializzata. Nei dati inviati si troverà una stringa della forma:
param1=val1&TextField=abcdef¶m2=val2
se l'utente ha inserito [abcdef] nel campo di immissione.
- riga 13: [PasswordField] recupererà il valore inviato dalle righe successive del modulo:
<!-- il campo di immissione della password -->
<tr>
<td>Mot de passe</td>
<td>
<input type="password" name="PasswordField" value="secret" size="30" />
</td>
</tr>
Riga 5: l’attributo [name] del campo di immissione è il nome della proprietà che verrà inizializzata. Nei dati inviati si troverà una stringa della forma:
param1=val1&PasswordField=abcdef¶m2=val2
se l’utente ha inserito [abcdef] nel campo di immissione.
- riga 14: [TextAreaField] recupererà il valore inviato dalle righe successive del modulo:
<!-- il campo di immissione testo multilinea -->
<tr>
<td>Boîte de saisie</td>
<td>
<textarea name="TextAreaField" cols="40" rows="3">ligne1
ligne2</textarea>
</td>
</tr>
Riga 5: l’attributo [name] del campo di immissione è il nome della proprietà che verrà inizializzata. Nei dati inviati si troverà una stringa della forma:
param1=val1&TextAreaField=abcdef%0D%OAhijk¶m2=val2
se l’utente ha inserito [abcdef] seguito da un’interruzione di riga e da [ijk] nel campo di immissione.
- riga 15: [DropDownListField] recupererà il valore inviato dalle righe successive del modulo:
<!-- l'elenco a discesa -->
<tr>
<td>Liste déroulante</td>
<td>
<select name="DropDownListField">
<option value="1" >choix1</option>
<option value="2" selected="selected">choix2</option>
<option value="3" >choix3</option>
</select>
</tr>
Riga 5: l’attributo [name] del tag <select> è il nome della proprietà che verrà inizializzata. Nei dati inviati si troverà una stringa della forma:
param1=val1&DropDownListField=1¶m2=val2
se l'utente ha selezionato l'opzione [choix1]. È l'attributo [value] dell'opzione selezionata che viene inviato.
- riga 16: [SingleChoiceListField] recupererà il valore inviato dalle righe successive del modulo:
<!-- l'elenco a scelta singola -->
<tr>
<td>Liste à choix unique</td>
<td>
<select name="SimpleChoiceListField" size="3">
<option value="1" >liste1</option>
<option value="2" >liste2</option>
<option value="3" selected="selected">liste3</option>
<option value="4" >liste4</option>
<option value="5" >liste5</option>
</select>
</tr>
Riga 5: l'attributo [name] del tag <select> è il nome della proprietà che verrà inizializzata. È l'attributo [size="3"] che fa sì che non vi sia un menu a tendina. Nei dati inviati si troverà una stringa della forma:
param1=val1&SimpleChoiceListField=3¶m2=val2
se l'utente ha selezionato l'opzione [liste3]. È l'attributo [value] dell'opzione selezionata che viene inviato. Il parametro [SingleChoiceListField] può essere assente dalla stringa inviata se non è stato selezionato alcun elemento.
- riga 17: [MultipleChoiceListField] recupererà i valori inviati dalle righe successive del modulo:
<!-- l'elenco a scelta multipla -->
<tr>
<td>Liste à choix multiple</td>
<td>
<select name="MultipleChoiceListField" size="3" multiple="multiple">
<option value="1" selected="selected">liste1</option>
<option value="2" >liste2</option>
<option value="3" selected="selected">liste3</option>
<option value="4" >liste4</option>
<option value="5" >liste5</option>
</select>
</tr>
Riga 5: l'attributo [name] del tag <select> è il nome della proprietà che verrà inizializzata. È l’attributo [size="3"] che fa sì che non vi sia un menu a tendina, mentre l’attributo [multiple] consente all’utente di selezionare più elementi tenendo premuto il tasto [Ctrl]. Nei dati inviati si troverà una stringa della forma:
param1=val1&MultipleChoiceListField=1&MultipleChoiceListField=3¶m2=val2
se l’utente ha selezionato le opzioni [liste1] e [liste3]. È l’attributo [value] delle opzioni selezionate che viene inviato. Poiché è possibile inviare più parametri con lo stesso nome, [MultipleChoiceListField] è un array di valori e non un singolo valore. Se nessuna casella è spuntata, il parametro [MultipleChoiceListField] sarà assente dalla stringa inviata e la proprietà con lo stesso nome nel modello non verrà inizializzata.
I vari campi di immissione presentati in precedenza riceveranno i valori inviati dal modulo. È anche possibile inizializzarli prima di inviare il modulo. È ciò che è stato fatto qui:
// inizializzazione dei campi
RadioButtonField = "2";
CheckBoxesField = new string[] { "2" };
TextField = "quelques mots";
PasswordField = "secret";
TextAreaField = "ligne1\nligne2";
DropDownListField = "2";
SimpleChoiceListField = "3";
MultipleChoiceListField = new string[] { "1", "3" };
Se questi valori fossero stati ottenuti dopo l’invio del modulo tramite POST, ciò significherebbe che l’utente ha:
- riga 2: selezionato l’opzione [non] del pulsante di opzione;
- riga 3: selezionato l’opzione [2] nelle caselle di controllo;
- riga 4: digitato [quelques mots] nel campo di immissione;
- riga 5: digitato [secret] come password;
- riga 6: digitato [ligne1\nligne2] nel campo di immissione multilinea;
- riga 7: selezionata l'opzione [choix2] dall'elenco a discesa;
- riga 8: selezionata l'opzione [liste3] dall'elenco a scelta singola;
- riga 9: selezionate le opzioni [liste1] e [liste3] dall’elenco a scelta multipla;
Facciamo finta che si sia verificata un'operazione POST e che si voglia rinviare il modulo così come è stato compilato. Questo è in particolare ciò che avviene quando si rinvia all'utente un modulo errato. Il modulo viene rinviato così come è stato compilato.
5.6.4. La vista [Formulaire]
La vista [/First/Formulaire.cshtml] visualizza il modulo:
![]() |
@model Exemple_03.Models.ViewModel08
@using Exemple_03.Models
@{
Layout = null;
}
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Formulaire</title>
</head>
<body>
<form method="post" action="Action08Post">
<h2>Formulaire ASP.NET MVC</h2>
<h3>Affiché par : @ViewBag.info</h3>
<table>
<thead></thead>
<tbody>
<!-- i pulsanti di opzione -->
<tr>
<td>Etes-vous marié(e)</td>
<td>
@foreach (ApplicationModel.Item item in @Model.RadioButtonFieldItems)
{
string strChecked = item.Value == @Model.RadioButtonField ? "checked=\"checked\"" : "";
<input type="radio" name="RadioButtonField" value="@item.Value" @strChecked/>@item.Label
<text/>
}
</td>
</tr>
...
</tbody>
</table>
<input type="submit" value="Valider" />
</form>
</body>
</html>
- riga 1: [ViewModel08] è il modello del modulo;
- riga 12: il tag <form> del modulo. Questo verrà inviato con il metodo [POST] (attributo method) a URL [/First/Action08Post] (attributo action);
- riga 33: il pulsante di tipo [submit] che serve a inviare il modulo;
- righe 22-27: visualizzano i pulsanti di opzione:
- riga 22: si scorre la raccolta visualizzata dal pulsante di opzione;
- riga 24: il pulsante che ha come attributo [value] il valore della proprietà [RadioButtonField] deve essere selezionato. A tal fine, deve avere l’attributo [checked="checked"];
- riga 25: generazione del tag <input type="radio"> con valore [@item.Value] e etichetta [@item.Label];
- riga 26: il tag <text/> non è un tag HTML riconosciuto. È presente per [Razor]. Quando lo incontra, [Razor] genererà un'interruzione di riga. Ciò non ha alcuna incidenza sul modulo visualizzato, ma ne ha sul codice HTML generato. I tag <input type="radio"> si trovano quindi su due righe diverse invece che sulla stessa riga. Ciò rende il codice più leggibile quando, dal browser, si richiede di visualizzare il codice sorgente della pagina visualizzata;
Esaminiamo ora gli altri elementi della vista:
<!-- le caselle di controllo -->
<tr>
<td>Cases à cocher</td>
<td>
@{
foreach (ApplicationModel.Item item in @Model.CheckBoxesFieldItems)
{
string strChecked = @Model.CheckBoxesField.Contains(item.Value) ? "checked=\"checked\"" : "";
<input type="checkbox" name="CheckBoxesField" value="@item.Value" @strChecked/>@item.Label
<text/>
}
}
</td>
- riga 6: si percorre la collezione visualizzata tramite le caselle di selezione;
- riga 8: una casella che abbia come attributo [value] uno dei valori della proprietà [CheckBoxesField] deve essere spuntata. A tal fine, deve avere l'attributo [checked="checked"]. Si utilizza un'espressione LINQ che permette di verificare se un valore è contenuto in una tabella;
- riga 25: generazione del tag <input type="checkbox"> con valore [@item.Value] e etichetta [@item.Label];
<!-- il campo di immissione testo a riga singola -->
<tr>
<td>Champ de saisie</td>
<td>
<input type="text" name="TextField" value="@Model.TextField" size="30" />
</td>
</tr>
<!-- il campo di immissione della password -->
<tr>
<td>Mot de passe</td>
<td>
<input type="password" name="PasswordField" value="@Model.PasswordField" size="30" />
</td>
</tr>
<!-- il campo di immissione testo multilinea -->
<tr>
<td>Boîte de saisie</td>
<td>
<textarea name="TextAreaField" cols="40" rows="3">@Model.TextAreaField</textarea>
</td>
</tr>
- righe 5, 12: all'attributo [value] del tag viene assegnato il valore del modello;
- riga 19: idem, ma con una sintassi diversa.
<!-- l'elenco a discesa -->
<tr>
<td>Liste déroulante</td>
<td>
<select name="DropDownListField">
@{
foreach (ApplicationModel.Item item in @Model.DropDownListFieldItems)
{
string strChecked = item.Value == @Model.DropDownListField ? "selected=\"selected\"" : "";
<option value="@item.Value" @strChecked>@item.Label</option>
}
}
</select>
</tr>
- riga 7: si scorre la raccolta visualizzata dal menu a tendina;
- riga 9: deve quindi essere selezionata un'opzione che abbia come attributo [value] il valore della proprietà [DropDownListField]. A tal fine, deve avere l'attributo [selected="selected"];
- riga 25: generazione del tag <option value="valeur">libellé</option> con valore [@item.Value] e etichetta [@item.Label];
<!-- l'elenco a scelta singola -->
<tr>
<td>Liste à choix unique</td>
<td>
<select name="SimpleChoiceListField" size="3">
@{
foreach (ApplicationModel.Item item in @Model.SimpleChoiceListFieldItems)
{
string strChecked = item.Value == @Model.SimpleChoiceListField ? "selected=\"selected\"" : "";
<option value="@item.Value" @strChecked>@item.Label</option>
}
}
</select>
</tr>
La spiegazione è la stessa di quella relativa all’elenco a discesa.
<!-- l'elenco a scelta multipla -->
<tr>
<td>Liste à choix multiple</td>
<td>
<select name="MultipleChoiceListField" size="3" multiple="multiple">
@{
foreach (ApplicationModel.Item item in @Model.MultipleChoiceListFieldItems)
{
string strChecked = @Model.MultipleChoiceListField.Contains(item.Value) ? "selected=\"selected\"" : "";
<option value="@item.Value" @strChecked>@item.Label</option>
}
}
</select>
</tr>
- riga 7: si scorre la raccolta visualizzata dall’elenco;
- riga 9: deve essere selezionata un'opzione che abbia come attributo [value] uno dei valori della proprietà [MultipleChoiceListField]. A tal fine, deve avere l’attributo [selected="selected"]. Si utilizza un’espressione LINQ che permette di verificare se un valore è contenuto in un array;
- riga 10: generazione del tag libellé/option con valore [@item.Value] e etichetta [@item.Label];
5.6.5. Elaborazione del campo POST del modulo
Abbiamo visto che il modulo sarebbe stato inviato all’azione [Action08Post]:
<form method="post" action="Action08Post">
L'azione [Action08Post] è la seguente:
// Azione08-POST
[HttpPost]
public ViewResult Action08Post(ApplicationModel application, FormCollection posted)
{
ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
ViewModel08 modèle = new ViewModel08(application);
TryUpdateModel(modèle,posted);
return View("Formulaire", modèle);
}
- riga 3: il modello dell’applicazione è un parametro, così come i valori inviati. Questi sono disponibili in un tipo [FormCollection]. Il valore del parametro [RadioButtonField] inviato è ottenuto tramite l’espressione posted[" RadioButtonField"]. Si ottiene così una stringa di caratteri o il puntatore null. Se si scrive posted[" CheckBoxesField"], si otterrà un array di stringhe di caratteri o il puntatore null;
- perché non scrivere:
public ViewResult Action08Post(ApplicationModel application, ViewModel08 posted)
Ci sono due ragioni:
- la prima è che il framework istanzierà il modello [ViewModel08] con il costruttore senza parametri, il che comporterà la mancata inizializzazione delle collezioni del modello;
- la seconda è che si vuole controllare cosa viene inserito nel modello. Sappiamo che ci sono quattro possibili fonti per il modello: i parametri di un GET, di un POST, della route utilizzata e quelli di un file uploadé. In questo caso, si desidera inizializzare il modello esclusivamente con i valori inviati.
- riga 6: si istanzia il modello utilizzando il costruttore corretto;
- riga 7: lo si inizializza con i valori inviati. Dopo questa operazione, il modello corrisponde ai dati inseriti dall'utente;
- riga 8: si visualizza nuovamente il modulo. L’utente lo ritroverà così come è stato compilato.
Vediamo un esempio:
![]() |
In [2], il risultato di [POST] riflette correttamente quanto inserito in [1].
5.6.6. Gestione delle anomalie di POST
Abbiamo detto che se nessun valore era stato spuntato o selezionato per i campi [CheckBoxesField, SimpleChoiceListField, MultipleChoiceListField], i parametri corrispondenti non facevano parte della stringa inviata e quindi le proprietà con lo stesso nome nel modello non venivano inizializzate.
Vediamo il seguente esempio:
![]() |
- in [1], nessuna casella di controllo è stata selezionata;
- in [2], il [POST] restituisce una casella selezionata.
La spiegazione è la seguente:
- poiché non vi è alcuna casella selezionata, il parametro [CheckBoxesField] non fa parte dei valori inviati;
- l'azione [Action08Post] procede come segue:
[HttpPost]
public ViewResult Action08Post(ApplicationModel application, FormCollection posted)
{
ViewBag.info = ...
ViewModel08 modèle = new ViewModel08(application);
TryUpdateModel(modèle,posted);
return View("Formulaire", modèle);
}
- riga 5: il modello del modulo viene istanziato. Il costruttore utilizzato assegna l’array ["2"] alla proprietà [CheckBoxesField];
- riga 6: i valori inviati vengono registrati nel modello. Poiché il parametro [CheckBoxesField] non fa parte dei valori inviati, la proprietà con lo stesso nome non viene assegnata. Mantiene quindi il suo valore ["2"], il che fa sì che, in fase di visualizzazione, la casella n. 2 risulti spuntata quando invece non dovrebbe esserlo.
Questo problema può essere risolto in vari modi. Abbiamo scelto di risolverlo nel codice dell’azione [Action08Post]:
// Azione08-POST
[HttpPost]
public ViewResult Action08Post(ApplicationModel application, FormCollection posted)
{
ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
ViewModel08 modèle = new ViewModel08(application);
TryUpdateModel(modèle,posted);
// elaborazione dei valori non inviati
if (posted["CheckBoxesField"] == null)
{
modèle.CheckBoxesField = new string[] { };
}
if (posted["SimpleChoiceListField"] == null)
{
modèle.SimpleChoiceListField = "";
}
if (posted["MultipleChoiceListField"] == null)
{
modèle.MultipleChoiceListField = new string[] { };
}
// Visualizzazione del modulo
return View("Formulaire", modèle);
}
- righe 9-20: si verifica se determinati parametri sono stati inviati o meno. In caso contrario, vengono inizializzati con il valore corrispondente all’assenza di immissione da parte dell’utente. Il controllo non è stato effettuato per l’elenco a discesa, che presenta sempre un elemento selezionato, a differenza degli altri elenchi.
Il lettore è invitato a testare questa nuova versione.
5.7. Utilizzo di metodi specializzati nella generazione di moduli
5.7.1. Il nuovo modulo
Creiamo un nuovo modulo [Formulaire2.cshtml] che genererà un modulo identico al precedente:
![]() |
Torniamo al codice utilizzato per generare l'elenco a discesa del modulo:
<!-- L'elenco a discesa -->
<tr>
<td>Liste déroulante</td>
<td>
<select name="DropDownListField">
@{
foreach (ApplicationModel.Item item in @Model.DropDownListFieldItems)
{
string strChecked = item.Value == @Model.DropDownListField ? "selected=\"selected\"" : "";
<option value="@item.Value" @strChecked>@item.Label</option>
}
}
</select>
</tr>
Questo codice presenta due svantaggi:
- il più importante è che, a causa della complessità del codice, si perde di vista la natura del componente, in questo caso un menu a tendina;
- riga 5: se si commette un errore nel nome della proprietà del modello da utilizzare come attributo [name], ce ne accorgeremo solo al momento dell’esecuzione.
ASP.NET MVC offre metodi specializzati denominati [HTML Helpers] che, come suggerisce il nome, mirano a facilitare la generazione del HTML, in particolare per i moduli. Con queste classi, il precedente elenco a scorrimen e si scrive come segue:
<!-- l'elenco a discesa -->
<tr>
<td>Liste déroulante</td>
<td>@Html.DropDownListFor(m => m.DropDownListField,
new SelectList(@Model.DropDownListFieldItems, "Value", "Label"))
</td>
</tr>
L'elenco a discesa viene generato dalle righe 4-5. Il codice è decisamente meno complesso. Il codice HTML generato per l'elenco a discesa è il seguente:
<!-- l'elenco a discesa -->
<tr>
<td>Liste déroulante</td>
<td><select id="DropDownListField" name="DropDownListField"><option value="1">choix1</option>
<option selected="selected" value="2">choix2</option>
<option value="3">choix3</option>
</select></td>
</tr>
- riga 4: l'attributo [name] è corretto;
- righe 4-6: le opzioni sono state generate correttamente e l’opzione corretta è stata selezionata.
Torniamo al codice che ha generato queste righe HTML:
@Html.DropDownListFor(m => m.DropDownListField, new SelectList(@Model.DropDownListFieldItems, "Value", "Label"))
- il primo parametro è una funzione lambda (questo è il suo nome) in cui m rappresenta il modello della vista e m.DropDowListField è una proprietà di tale modello. Il generatore di codice HTML utilizzerà il nome di questa proprietà per generare gli attributi [id] e [name] del [select] che verrà generato. Se si utilizza una proprietà inesistente, si verificherà un errore in fase di compilazione e non più in fase di esecuzione. Si tratta di un miglioramento rispetto alla soluzione precedente, in cui gli errori di denominazione venivano rilevati solo in fase di esecuzione;
- il secondo parametro serve a indicare la collezione di elementi che alimenterà l'elenco a discesa. La classe [SelectList] consente di costruire questa collezione:
- il suo primo parametro è una collezione qualsiasi di elementi. In questo caso si tratta di una collezione di tipo [Item];
- il suo secondo parametro è la proprietà degli elementi che fornirà il valore del tag <option>. In questo caso, si tratta della proprietà [Value] della classe [Item];
- il suo terzo parametro è la proprietà degli elementi che fornirà il testo del tag <option>. In questo caso, si tratta della proprietà [Label] della classe [Item];
- per determinare quale opzione debba essere selezionata (attributo selected), il framework procede come noi: confronta il valore dell’opzione con il valore attuale della proprietà [DropDownListField].
Vediamo ora gli altri metodi che possiamo utilizzare:
Pulsanti di opzione
Il nuovo codice è il seguente:
<!-- i pulsanti di opzione -->
<tr>
<td>Etes-vous marié(e)</td>
<td>
@{
foreach (ApplicationModel.Item item in @Model.RadioButtonFieldItems)
{
@Html.RadioButtonFor(m => m.RadioButtonField, @item.Value)@item.Label
<text/>
}
}
</td>
</tr>
Il codice HTML generato è il seguente:
<!-- i pulsanti di opzione -->
<tr>
<td>Etes-vous marié(e)</td>
<td>
<input id="RadioButtonField" name="RadioButtonField" type="radio" value="1" />oui
<input checked="checked" id="RadioButtonField" name="RadioButtonField" type="radio" value="2" />non
</td>
</tr>
Il metodo utilizzato è [Html.RadioButtonFor]:
- il primo parametro è la proprietà del modello che verrà associata al pulsante di opzione (attributo [name]);
- il secondo parametro è il valore da assegnare al pulsante di opzione (attributo [value]).
Caselle di selezione
Il codice si evolve come segue:
<!-- le caselle di selezione -->
<tr>
<td>Cases à cocher</td>
<td>
@{
@Html.CheckBoxFor(m=>m.CheckBoxField1) @Model.CheckBoxesFieldItems[0].Label
@Html.CheckBoxFor(m=>m.CheckBoxField2) @Model.CheckBoxesFieldItems[1].Label
@Html.CheckBoxFor(m=>m.CheckBoxField3) @Model.CheckBoxesFieldItems[2].Label
}
</td>
Il metodo utilizzato per generare le caselle di selezione è [Html.CheckBoxFor]:
Il parametro è la proprietà booleana del modello che verrà associata alla casella di selezione. Se [Propriété=true], la casella sarà selezionata. Se [Propriété=false], la casella non sarà selezionata. In ogni caso, l’attributo [value] assume il valore true. Il codice HTML generato è il seguente:
<input id="Propriété" name="Propriété" type="checkbox" value="true" />
<input name="Propriété" type="hidden" value="false" />
- riga 1: la casella di selezione con l’attributo [value="true"];
- riga 2: un campo nascosto (type=hidden) con lo stesso nome [Propriété] della casella di controllo con l'attributo [value="false"]. Perché due tag [input] con lo stesso nome? Ci sono due casi:
- la casella della riga 1 è selezionata. In questo caso, la stringa di parametri inviata è Proprietà=true&Proprietà=false (righe 1 e 2). Poiché la proprietà [Propriété] richiede un solo valore, si potrebbe pensare che il framework assegni il valore [true] a [Propriété]. Basterebbe effettuare un'operazione logica OU tra i valori ricevuti per ottenere il risultato;
- la casella della riga 1 non è spuntata. Pertanto, la stringa di parametri inviata è Proprietà=false (solo riga 2) e quindi la proprietà [Propriété] riceve il valore [false], il che è corretto (la casella non è stata spuntata).
Campo di immissione a riga singola
Il nuovo codice è il seguente:
<!-- il campo di immissione testo a riga singola -->
<tr>
<td>Champ de saisie</td>
<td>
@Html.TextBoxFor(m => m.TextField, new { size = "30" })
</td>
</tr>
Il codice HTML generato è il seguente:
<!-- il campo di immissione testo a riga singola -->
<tr>
<td>Champ de saisie</td>
<td>
<input id="TextField" name="TextField" size="30" type="text" value="quelques mots" />
</td>
</tr>
Il metodo utilizzato è il seguente:
@Html.TextBoxFor(m => m.TextField, new { size = "30" })
- il primo parametro specifica la proprietà del modello associata al campo di immissione. Il nome della proprietà verrà utilizzato negli attributi [name] e [id] del tag <input> generato e il suo valore verrà assegnato all'attributo [value];
- il secondo parametro è una classe anonima che specifica alcuni attributi del tag HTML generato, in questo caso l'attributo [size].
Campo di immissione della password
Il nuovo codice è il seguente:
<!-- il campo di immissione della password -->
<tr>
<td>Mot de passe</td>
<td>
@Html.PasswordFor(m => m.PasswordField, new { size = "15" })
</td>
</tr>
Il codice HTML generato è il seguente:
<!-- il campo di immissione della password -->
<tr>
<td>Mot de passe</td>
<td>
<input id="PasswordField" name="PasswordField" size="15" type="password" />
</td>
</tr>
Il metodo utilizzato è il seguente:
@Html.PasswordFor(m => m.PasswordField, new { size = "15" })
Il funzionamento è analogo a quello del metodo [Html.TexBoxFor].
Campo di immissione multiriga
Il nuovo codice è il seguente:
<!-- il campo di immissione testo multilinea -->
<tr>
<td>Boîte de saisie</td>
<td>
@Html.TextAreaFor(m => m.TextAreaField, new { cols = "30", rows = "5" })
</td>
</tr>
Il codice HTML generato è il seguente:
<!-- il campo di immissione testo multilinea -->
<tr>
<td>Boîte de saisie</td>
<td>
<textarea cols="30" id="TextAreaField" name="TextAreaField" rows="5">
ligne1
ligne2</textarea>
</td>
</tr>
Il metodo utilizzato è il seguente:
@Html.TextAreaFor(m => m.TextAreaField, new { cols = "30", rows = "5" })
Il funzionamento è analogo a quello del metodo [Html.TexBoxFor].
Elenco a scelta singola
Il nuovo codice è il seguente:
<!-- l'elenco a scelta singola -->
<tr>
<td>Liste à choix unique</td>
<td>
@Html.DropDownListFor(m => m.SimpleChoiceListField, new SelectList(@Model.SimpleChoiceListFieldItems, "Value", "Label"), new { size = "3" })
</tr>
e il codice generato da HTML è il seguente:
<!-- l'elenco a scelta singola -->
<tr>
<td>Liste à choix unique</td>
<td>
<select id="SimpleChoiceListField" name="SimpleChoiceListField" size="3">
<option value="1">liste1</option>
<option value="2">liste2</option>
<option selected="selected" value="3">liste3</option>
<option value="4">liste4</option>
<option value="5">liste5</option>
</select>
</tr>
Abbiamo già esaminato il metodo [Html.DropDownListFor]. L'unica differenza in questo caso è il terzo parametro, che serve a specificare un attributo [size] diverso da 1. È questa caratteristica che fa passare da un elenco a tendina [size=1] a un elenco semplice.
L'elenco a scelta multipla
Il nuovo codice è il seguente:
<!-- l'elenco a scelta multipla -->
<tr>
<td>Liste à choix multiple</td>
<td>
@Html.ListBoxFor(m => m.MultipleChoiceListField, new SelectList(@Model.MultipleChoiceListFieldItems, "Value", "Label"), new { size = "5" })
</tr>
e il codice HTML generato è il seguente:
<!-- l'elenco a scelta multipla -->
<tr>
<td>Liste à choix multiple</td>
<td>
<select id="MultipleChoiceListField" multiple="multiple" name="MultipleChoiceListField" size="5">
<option selected="selected" value="1">liste1</option>
<option value="2">liste2</option>
<option selected="selected" value="3">liste3</option>
<option value="4">liste4</option>
<option value="5">liste5</option>
</select>
</tr>
Il metodo
@Html.ListBoxFor(m => m.MultipleChoiceListField, new SelectList(@Model.MultipleChoiceListFieldItems, "Value", "Label"), new { size = "5" })
funziona come il metodo [Html.DropDownListFor], tranne per il fatto che genera un elenco a selezione multipla. Le opzioni selezionate sono quelle il cui valore (attributo value) è presente nella tabella [MultipleChoiceListField].
Anche il tag <form> può essere generato con un metodo:
@using (Html.BeginForm("Action09Post", "First"))
{
...
}
Il codice HTML generato è il seguente:
<form action="/First/Action09Post" method="post">
...
</form>
Il metodo
Html.BeginForm("Action09Post", "First")
ha come primo parametro il nome di un'azione e come secondo parametro il nome di un controller.
5.7.2. Le azioni e il modello
Il modulo verrà generato dalla seguente azione [Action09Get]:
// Azione09-GET
[HttpGet]
public ViewResult Action09Get(ApplicationModel application)
{
ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
return View("Formulaire2", new ViewModel09(application));
}
La vista restituita alla riga 6 è [Formulaire2], associata al seguente modello [ViewModel09]:
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Exemple_03.Models;
namespace Exemple_03.Models
{
public class ViewModel09
{
// i campi di immissione
public string RadioButtonField { get; set; }
public bool CheckBoxField1 { get; set; }
public bool CheckBoxField2 { get; set; }
public bool CheckBoxField3 { get; set; }
public string TextField { get; set; }
public string PasswordField { get; set; }
public string TextAreaField { get; set; }
public string DropDownListField { get; set; }
public string SimpleChoiceListField { get; set; }
public string[] MultipleChoiceListField { get; set; }
// le raccolte da visualizzare nel modulo
public ApplicationModel.Item[] RadioButtonFieldItems { get; set; }
public ApplicationModel.Item[] CheckBoxesFieldItems { get; set; }
public ApplicationModel.Item[] DropDownListFieldItems { get; set; }
public ApplicationModel.Item[] SimpleChoiceListFieldItems { get; set; }
public ApplicationModel.Item[] MultipleChoiceListFieldItems { get; set; }
// costruttori
public ViewModel09()
{
}
public ViewModel09(ApplicationModel application)
{
// Inizializzazione delle collezioni
RadioButtonFieldItems = application.RadioButtonFieldItems;
CheckBoxesFieldItems = application.CheckBoxesFieldItems;
DropDownListFieldItems = application.DropDownListFieldItems;
SimpleChoiceListFieldItems = application.SimpleChoiceListFieldItems;
MultipleChoiceListFieldItems = application.MultipleChoiceListFieldItems;
// inizializzazione dei campi
RadioButtonField = "2";
CheckBoxField2 = true;
TextField = "quelques mots";
PasswordField = "secret";
TextAreaField = "ligne1\nligne2";
DropDownListField = "2";
SimpleChoiceListField = "3";
MultipleChoiceListField = new string[] { "1", "3" };
}
}
}
[ViewModel09] differisce da [ViewModel08] per la gestione delle caselle di selezione. Anziché utilizzare una tabella con tre caselle di selezione, sono state utilizzate tre caselle di selezione separate (righe 11-13).
Il modulo verrà elaborato dalla seguente azione [Action09Post]:
// Azione09-POST
[HttpPost]
public ViewResult Action09Post(ApplicationModel application, FormCollection posted)
{
ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
ViewModel09 modèle = new ViewModel09(application);
TryUpdateModel(modèle, posted);
// elaborazione dei valori non inviati
if (posted["SimpleChoiceListField"] == null)
{
modèle.SimpleChoiceListField = "";
}
if (posted["MultipleChoiceListField"] == null)
{
modèle.MultipleChoiceListField = new string[] { };
}
// visualizzazione del modulo
return View("Formulaire2", modèle);
}
L'azione [Action09Post] è identica all'azione [Action08Post] tranne che per due punti:
- riga 18: viene utilizzata la vista [Formulaire2] al posto della vista [Formulaire];
- non è più presente la gestione delle caselle di controllo che non sono state selezionate. Ora questa funzione è gestita correttamente dal metodo [Html.CheckBoxFor].
5.8. Generazione di un modulo a partire dai metadati del modello
Esistono altri metodi, oltre a quelli precedenti, per generare un modulo. Uno di questi consiste nell’associare informazioni a un campo del modello che consentiranno al framework MVC di sapere quale tag di inserimento deve generare. Queste informazioni sono denominate metadati.
Consideriamo il seguente modello di vista [ViewModel10]:
![]() |
using System;
using System.ComponentModel.DataAnnotations;
using System.Drawing;
namespace Exemple_03.Models
{
public class ViewModel10
{
[Display(Name="Text")]
[DataType(DataType.Text)]
public string Text { get; set; }
[Display(Name = "TextArea")]
[DataType(DataType.MultilineText)]
public string MultiLineText { get; set; }
[Display(Name = "Number")]
public int Number { get; set; }
[Display(Name = "Decimal")]
[UIHint("Decimal")]
public double Decimal { get; set; }
[Display(Name = "Tel")]
[DataType(DataType.PhoneNumber)]
public string Tel { get; set; }
[Display(Name = "Date")]
[DataType(DataType.Date)]
public DateTime Date { get; set; }
[Display(Name = "Time")]
[DataType(DataType.Time)]
public DateTime Time { get; set; }
[Display(Name = "HiddenInput")]
[UIHint("HiddenInput")]
public string HiddenInput { get; set; }
[Display(Name = "Boolean")]
[UIHint("Boolean")]
public bool Boolean { get; set; }
[Display(Name = "Email")]
[DataType(DataType.EmailAddress)]
public string Email{ get; set; }
[Display(Name = "Url")]
[DataType(DataType.Url)]
public string Url { get; set; }
[Display(Name = "Password")]
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name = "Currency")]
[DataType(DataType.Currency)]
public double Currency { get; set; }
[Display(Name = "CreditCard")]
[DataType(DataType.CreditCard)]
public string CreditCard { get; set; }
// costruttore
public ViewModel10()
{
Text = "tra la la";
MultiLineText = "ligne1\nligne2";
Number = 4;
Decimal = 10.2;
Tel = "0617181920";
Date = DateTime.Now;
Time = DateTime.Now;
HiddenInput = "caché";
Boolean = true;
Email = "x@y.z";
Url = "http://istia.univ-angers.fr";
Password = "mdp";
Currency = 4.2;
CreditCard = "0123456789012345";
}
}
}
I metadati sono costituiti dai tag [Display, DataType, UIHint].
Questo modello di vista verrà generato dalla seguente azione [Action10Get]:
// Azione10-GET
[HttpGet]
public ViewResult Action10Get()
{
return View(new ViewModel10());
}
Nella riga 5 sopra riportata, si richiede alla vista predefinita dell'azione [/First/Action10Get.cshtml ] di visualizzare il modello di vista di tipo [ViewModel10]. Tale vista è la seguente:
![]() |
@model Exemple_03.Models.ViewModel10
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action10Get</title>
</head>
<body>
<h3>Formulaire ASP.NET MVC - 2</h3>
@using (Html.BeginForm("Action10Post", "First"))
{
<table>
<thead>
<tr>
<th>LabelFor</th>
<th>EditorFor</th>
<th>DisplayFor</th>
</tr>
</thead>
<tbody>
<tr>
<td>@Html.LabelFor(m => m.Text)</td>
<td>@Html.EditorFor(m => m.Text)</td>
<td>@Html.DisplayFor(m => m.Text)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.MultiLineText)</td>
<td>@Html.EditorFor(m => m.MultiLineText)</td>
<td>@Html.DisplayFor(m => m.MultiLineText)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Number)</td>
<td>@Html.EditorFor(m => m.Number)</td>
<td>@Html.DisplayFor(m => m.Number)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Decimal)</td>
<td>@Html.EditorFor(m => m.Decimal)</td>
<td>@Html.DisplayFor(m => m.Decimal)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Tel)</td>
<td>@Html.EditorFor(m => m.Tel)</td>
<td>@Html.DisplayFor(m => m.Tel)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Date)</td>
<td>@Html.EditorFor(m => m.Date)</td>
<td>@Html.DisplayFor(m => m.Date)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Time)</td>
<td>@Html.EditorFor(m => m.Time)</td>
<td>@Html.DisplayFor(m => m.Time)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.HiddenInput)</td>
<td>@Html.EditorFor(m => m.HiddenInput)</td>
<td>@Html.DisplayFor(m => m.HiddenInput)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Boolean)</td>
<td>@Html.EditorFor(m => m.Boolean)</td>
<td>@Html.DisplayFor(m => m.Boolean)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Email)</td>
<td>@Html.EditorFor(m => m.Email)</td>
<td>@Html.DisplayFor(m => m.Email)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Url)</td>
<td>@Html.EditorFor(m => m.Url)</td>
<td>@Html.DisplayFor(m => m.Url)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Password)</td>
<td>@Html.EditorFor(m => m.Password)</td>
<td>@Html.DisplayFor(m => m.Password)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Currency)</td>
<td>@Html.EditorFor(m => m.Currency)</td>
<td>@Html.DisplayFor(m => m.Currency)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.CreditCard)</td>
<td>@Html.EditorFor(m => m.CreditCard)</td>
<td>@Html.DisplayFor(m => m.CreditCard)</td>
</tr>
</tbody>
</table>
<input type="submit" value="Valider" />
}
</body>
</html>
Per ciascuna delle proprietà del modello, utilizziamo il metodo:
- Html.LabelFor per visualizzare il valore del metadato [DisplayName] della proprietà;
- Html.EditorFor per generare il tag HTML per l’inserimento del valore della proprietà. Questo metodo utilizzerà i metadati [DataType] e [UIHint] della proprietà;
- Html.DisplayFor per visualizzare il valore della proprietà secondo il formato indicato dal metadato [DataType].
Ecco un esempio di esecuzione con il browser Chrome:

A seconda del browser utilizzato, le pagine visualizzate potrebbero variare. Infatti, la vista generata utilizza i nuovi tag introdotti dalla versione 5 di HTML, denominata HTML5. Non tutti i browser supportano ancora questa versione. Nel caso sopra riportato, il browser Chrome la supporta solo in parte.
5.8.1. Il [POST] del modulo
Il [POST] del modulo viene elaborato dalla seguente azione [Action10Post]:
// Azione10-POST
[HttpPost]
public ContentResult Action10Post(ViewModel10 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("Contrôleur={0}, Action={1}, valide={2}, erreurs={3}", RouteData.Values["controller"], RouteData.Values["action"], ModelState.IsValid, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
- riga 3: l'azione [Action10Post] ha come modello in ingresso il modulo inviato;
- riga 5: si recuperano gli errori di convalida di questo modulo;
- riga 6: si prepara la risposta testuale per il cliente;
- riga 7: la si invia.
Esaminiamo ora una per una le proprietà del modello [ViewModel10] e vediamo come i metadati associati influenzano il HTML generato e la convalida dei campi di immissione.
5.8.2. Proprietà [Text]
Definizione
[Display(Name="Text")]
[DataType(DataType.Text)]
public string Text { get; set; }
...
Text = "tra la la";
Vista
<tr>
<td>@Html.LabelFor(m => m.Text)</td>
<td>@Html.EditorFor(m => m.Text)</td>
<td>@Html.DisplayFor(m => m.Text)</td>
</tr>
Immagine
![]() |
HTML generato
<tr>
<td><label for="Text">Text</label></td>
<td><input class="text-box single-line" id="Text" name="Text" type="text" value="tra la la" /></td>
<td>tra la la</td>
</tr>
Commenti
- il metodo [Html.LabelFor] ha generato il tag <label> della riga 2. Il valore dell'attributo [for] è il nome della proprietà parametro del metodo [Html.LabelFor]
public string Text { get; set; }
Il testo visualizzato tra l'inizio e la fine del tag è il testo dei metadati
[Display(Name="Text")]
Il metodo [Html.LabelFor] procede sempre in questo modo. Non torneremo su questo argomento per le altre proprietà.
- Il metodo [Html.EditorFor] ha generato il tag <input> della riga 3. Si noti che presenta un attributo [class] che associa la classe CSS [text-box single-line] al tag. Gli attributi [id] e [name] hanno come valore il nome [Text] della proprietà parametro del metodo [Html.EditorFor]. L'attributo [type] ha assunto il valore [text] a causa del metadato
[DataType(DataType.Text)]
- il metodo [Html.DisplayFor] ha generato il testo della riga 4. Si tratta del valore della proprietà parametro del metodo [Html.DisplayFor ]. Questo metodo è influenzato dal metadato
[DataType(DataType.Text)]
che fa sì che il valore venga visualizzato come testo non formattato.
5.8.3. Proprietà [MultiLineText]
Definizione
[Display(Name = "TextArea")]
[DataType(DataType.MultilineText)]
public string MultiLineText { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.MultiLineText)</td>
<td>@Html.EditorFor(m => m.MultiLineText)</td>
<td>@Html.DisplayFor(m => m.MultiLineText)</td>
</tr>
Immagine
HTML generato
<tr>
<td><label for="MultiLineText">TextArea</label></td>
<td><textarea class="text-box multi-line" id="MultiLineText" name="MultiLineText">
ligne1
ligne2</textarea></td>
<td>ligne1
ligne2</td>
</tr>
Commenti
- il metodo [Html.EditorFor] ha generato il tag <textarea> della riga 3. Si noti che presenta un attributo [class] che associa la classe CSS [text-box multi-line] al tag. Gli attributi [id] e [name] hanno come valore il nome [MultiLineText] della proprietà parametro del metodo [Html.EditorFor]. È sempre così. Non lo menzioneremo più. Il tag generato è <textarea> a causa del metadato
[DataType(DataType.MultilineText)]
che specificava che la proprietà era un testo su più righe.
- Il metodo [Html.DisplayFor] ha generato il testo delle righe 4-5. Si tratta del valore della proprietà parametro del metodo [Html.DisplayFor ].
5.8.4. Proprietà [Number]
Definizione
[Display(Name = "Number")]
public int Number { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Number)</td>
<td>@Html.EditorFor(m => m.Number)</td>
<td>@Html.DisplayFor(m => m.Number)</td>
</tr>
Immagine
HTML generato
<tr>
<td><label for="Number">Number</label></td>
<td><input class="text-box single-line" data-val="true" data-val-number="Le champ Number doit être un nombre." data-val-required="Le champ Number est requis." id="Number" name="Number" type="number" value="4" /></td>
<td>4</td>
</tr>
Commenti
- il metodo [Html.EditorFor] ha generato il tag <input> della riga 3 con un attributo [type] di tipo [number]. A quanto pare semplicemente perché la proprietà è di tipo [int]. Gli attributi [data-val], [data-val-number] e [data-val-required] sono attributi non riconosciuti da HTML5. Sono utilizzati da un framework JavaScript per la validazione dei dati sul lato client;
- il metodo [Html.DisplayFor] ha generato il testo della riga 4, ovvero il valore della proprietà.
Convalida
Gli attributi [data-x] influenzano la convalida dei dati lato client. Ecco due esempi:
Si inserisce un numero errato e si esegue la convalida:
![]() |
Nell’esempio sopra riportato, la convalida è avvenuta lato client. Il modulo non verrà inviato finché l’errore non sarà stato corretto.
Un altro esempio: non si inserisce nulla:
![]() |
Nel codice [1] sopra riportato, [Action10Post] segnala un errore. Forse ricorderete che avevamo già ottenuto questo comportamento utilizzando l’attributo [Required] sulla proprietà da controllare (cfr. pagina 69), in questo caso la proprietà [Number]. In questo caso, non è stato necessario farlo.
5.8.5. Proprietà [Decimal]
Definizione
[Display(Name = "Decimal")]
[UIHint("Decimal")]
public double Decimal { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Decimal)</td>
<td>@Html.EditorFor(m => m.Decimal)</td>
<td>@Html.DisplayFor(m => m.Decimal)</td>
</tr>
Immagine
HTML generato
<tr>
<td><label for="Decimal">Decimal</label></td>
<td><input class="text-box single-line" data-val="true" data-val-number="Le champ Decimal doit être un nombre." data-val-required="Le champ Decimal est requis." id="Decimal" name="Decimal" type="text" value="10,20" /></td>
<td>10,20</td>
</tr>
Commenti
- il metodo [Html.EditorFor] ha generato il tag <input> della riga 3 con un attributo [type] di tipo [text]. Gli altri attributi sono identici a quelli generati per la proprietà [Number] precedente. I metadati:
[UIHint("Decimal")]
fa sì che il valore della proprietà venga visualizzato con due cifre decimali per entrambi i metodi [Html.EditorFor] e [Html.DisplayFor]
Convalida
A differenza del caso precedente, non viene segnalato alcun errore di convalida sul lato client. L’errore viene segnalato solo dall’azione [Action10Post]. Anche in questo caso, il numero decimale è obbligatorio senza che sia necessario impostare l’attributo [Required].
5.8.6. Proprietà [Tel]
Definizione
[Display(Name = "Tel")]
[DataType(DataType.PhoneNumber)]
public string Tel { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Tel)</td>
<td>@Html.EditorFor(m => m.Tel)</td>
<td>@Html.DisplayFor(m => m.Tel)</td>
</tr>
Immagine
HTML generato
<tr>
<td><label for="Tel">Tel</label></td>
<td><input class="text-box single-line" id="Tel" name="Tel" type="tel" value="0617181920" /></td>
<td>0617181920</td>
</tr>
Commenti
- il metodo [Html.EditorFor] ha generato il tag <input> della riga 3 con un attributo [type] di tipo [tel]. Questo valore è stato generato in base ai metadati:
[DataType(DataType.PhoneNumber)]
Il tipo [tel] per un tag <input> è una novità rispetto a HTML5. Il browser Chrome lo ha trattato come un tag <input> con il tipo [text].
Convalida
Non vengono segnalati errori di convalida né lato client né lato server. È possibile inserire qualsiasi contenuto.
5.8.7. Proprietà [Date]
Definizione
[Display(Name = "Date")]
[DataType(DataType.Date)]
public DateTime Date { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Date)</td>
<td>@Html.EditorFor(m => m.Date)</td>
<td>@Html.DisplayFor(m => m.Date)</td>
</tr>
Immagine
![]() |
HTML generato
<tr>
<td><label for="Date">Date</label></td>
<td><input class="text-box single-line" data-val="true" data-val-date="Le champ Date doit être une date." data-val-required="Le champ Date est requis." id="Date" name="Date" type="date" value="11/10/2013" /></td>
<td>11/10/2013</td>
</tr>
Commenti
- il metodo [Html.EditorFor] ha generato il tag <input> della riga 3 con un attributo [type] di tipo [date]. Questo valore è stato generato in base ai metadati:
[DataType(DataType.Date)]
Il tipo [date] per un tag <input> è una novità rispetto a HTML5. Il browser Chrome lo riconosce e consente di inserire la data tramite un calendario. Inoltre, la data inserita viene visualizzata nel formato [jj/mm/aaaa], ovvero Chrome adatta il formato della data a quello [locale] del browser.
- Anche il metodo [Html.DisplayFor] ha scritto la data nel formato [jj/mm/aaaa], sempre a causa della presenza del metadato [Date].
Convalida
Viene segnalata una data non valida sul lato client [1], impedendo l’invio del POST dal modulo al server.
![]() |
L'assenza di una data non viene segnalata sul lato client, ma lo è sul lato server [2].
5.8.8. Proprietà [Time]
Definizione
[Display(Name = "Time")]
[DataType(DataType.Time)]
public DateTime Time { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Time)</td>
<td>@Html.EditorFor(m => m.Time)</td>
<td>@Html.DisplayFor(m => m.Time)</td>
</tr>
Immagine
HTML generato
<tr>
<td><label for="Time">Time</label></td>
<td><input class="text-box single-line" data-val="true" data-val-required="Le champ Time est requis." id="Time" name="Time" type="time" value="11:17" /></td>
<td>11:17</td>
</tr>
Commenti
- il metodo [Html.EditorFor] ha generato il tag <input> della riga 3 con un attributo [type] di tipo [time]. Questo valore è stato generato a causa dei metadati:
[DataType(DataType.Time)]
Il tipo [time] per un tag <input> è una novità rispetto a HTML5. Il browser Chrome lo riconosce e consente di inserire un'ora nel formato [hh:mm];
- anche il metodo [Html.DisplayFor] ha scritto l'ora nel formato [hh:mm], sempre a causa della presenza del metadato [Time].
Convalida
Tecnicamente non è possibile inserire un'ora non valida. L'assenza di un'ora viene segnalata dal server:
![]() |
5.8.9. Proprietà [HiddenInput]
Definizione
[Display(Name = "HiddenInput")]
[UIHint("HiddenInput")]
public string HiddenInput { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.HiddenInput)</td>
<td>@Html.EditorFor(m => m.HiddenInput)</td>
<td>@Html.DisplayFor(m => m.HiddenInput)</td>
</tr>
Immagine
HTML generato
<tr>
<td><label for="HiddenInput">HiddenInput</label></td>
<td>caché<input id="HiddenInput" name="HiddenInput" type="hidden" value="nascosto" /></td>
<td>caché</td>
</tr>
Commenti
- il metodo [Html.EditorFor] ha generato il tag <input> della riga 3 con un attributo [type] di tipo [hidden], ovvero un campo nascosto (ma comunque inviato). Questo valore è stato generato a causa del metadato:
[UIHint("HiddenInput")]
- il metodo [Html.DisplayFor] ha a sua volta scritto il valore del campo nascosto.
5.8.10. Proprietà [Boolean]
Definizione
[Display(Name = "Boolean")]
public bool Boolean { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Boolean)</td>
<td>@Html.EditorFor(m => m.Boolean)</td>
<td>@Html.DisplayFor(m => m.Boolean)</td>
</tr>
Immagine
HTML generato
<tr>
<td><label for="Boolean">Boolean</label></td>
<td><input checked="checked" class="check-box" data-val="true" data-val-required="Le champ Boolean est requis." id="Boolean" name="Boolean" type="checkbox" value="true" /><input name="Boolean" type="hidden" value="false" /></td>
<td><input checked="checked" class="check-box" disabled="disabled" type="checkbox" /></td>
</tr>
Commenti
- il metodo [Html.EditorFor] ha generato il tag <input> della riga 3 con un attributo [type] di tipo [checkbox], ovvero una casella di controllo. Questo valore è stato generato perché la proprietà è booleana:
public bool Boolean { get; set; }
- il metodo [Html.DisplayFor] ha generato la riga 4, anch’essa una casella di controllo (attributo type) ma disattivata (attributo disabled).
5.8.11. Proprietà [Email]
Definizione
[Display(Name = "Email")]
[DataType(DataType.EmailAddress)]
public string Email{ get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Email)</td>
<td>@Html.EditorFor(m => m.Email)</td>
<td>@Html.DisplayFor(m => m.Email)</td>
</tr>
Immagine
HTML generato
<tr>
<td><label for="Email">Email</label></td>
<td><input class="text-box single-line" id="Email" name="Email" type="email" value="x@y.z" /></td>
<td><a href="mailto:x@y.z">x@y.z</a></td>
</tr>
Commenti
- il metodo [Html.EditorFor] ha generato il tag <input> della riga 3 con un attributo [type] di tipo [email]. Questo tipo è nuovo in HTML5. Questo tipo è stato generato a causa dei metadati:
[DataType(DataType.EmailAddress)]
Chrome sembra aver trattato questo tipo come un tipo [text].
- Il metodo [Html.DisplayFor] ha generato la riga 4: un link all'indirizzo e-mail.
Convalida
Viene segnalato un indirizzo non valido sul lato client [1]:
![]() |
L'assenza di immissione dati non provoca alcun errore.
5.8.12. Proprietà [Url]
Definizione
[Display(Name = "Url")]
[DataType(DataType.Url)]
public string Url { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Url)</td>
<td>@Html.EditorFor(m => m.Url)</td>
<td>@Html.DisplayFor(m => m.Url)</td>
</tr>
Immagine
HTML generato
<tr>
<td><label for="Url">Url</label></td>
<td><input class="text-box single-line" id="Url" name="Url" type="url" value="http://istia.univ-angers.fr" /></td>
<td><a href="http://istia.univ-angers.fr">http://istia.univ-angers.fr</a></td>
</tr>
Commenti
- il metodo [Html.EditorFor] ha generato il tag <input> della riga 3 con un attributo [type] di tipo [url]. Questo tipo è nuovo in HTML5. È stato generato a causa dei metadati:
[DataType(DataType.Url)]
Chrome sembra trattare questo tipo come un tipo [text].
- Il metodo [Html.DisplayFor] ha generato la riga 4: un collegamento a URL.
Convalida
Viene segnalato un URL non valido sul lato client [1]:
![]() |
L'assenza di immissione dati non provoca alcun errore.
5.8.13. Proprietà [Password]
Definizione
[Display(Name = "Password")]
[DataType(DataType.Password)]
public string Password { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Password)</td>
<td>@Html.EditorFor(m => m.Password)</td>
<td>@Html.DisplayFor(m => m.Password)</td>
</tr>
Immagine
HTML generato
<tr>
<td><label for="Password">Password</label></td>
<td><input class="text-box single-line password" id="Password" name="Password" type="password" value="mdp" /></td>
<td>mdp</td>
</tr>
Commenti
- il metodo [Html.EditorFor] ha generato il tag <input> della riga 3 con un attributo [type] di tipo [password]. Questo tipo è stato generato a causa dei metadati:
[DataType(DataType.Password)]
- il metodo [Html.DisplayFor] ha generato la riga 4.
5.8.14. Proprietà [Currency]
Definizione
[Display(Name = "Currency")]
[DataType(DataType.Currency)]
public double Currency { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Currency)</td>
<td>@Html.EditorFor(m => m.Currency)</td>
<td>@Html.DisplayFor(m => m.Currency)</td>
</tr>
Immagine
HTML generato
<tr>
<td><label for="Currency">Currency</label></td>
<td><input class="text-box single-line" data-val="true" data-val-number="Le champ Currency doit être un nombre." data-val-required="Le champ Currency est requis." id="Currency" name="Currency" type="text" value="4,2" /></td>
<td>4,20 €</td>
</tr>
Commenti
- il metodo [Html.EditorFor] ha generato il tag <input> della riga 3 con un attributo [type] di tipo [text];
- il metodo [Html.DisplayFor] ha generato la riga 4, un numero con due cifre decimali e un simbolo monetario. Questo formato è stato utilizzato in base ai metadati:
[DataType(DataType.Currency)]
Convalida
Sul lato server viene segnalato un valore non valido [1] o l'assenza di un valore [2]:
![]() |
5.8.15. Proprietà [CreditCard]
Definizione
[Display(Name = "CreditCard")]
[DataType(DataType.CreditCard)]
public string CreditCard { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.CreditCard)</td>
<td>@Html.EditorFor(m => m.CreditCard)</td>
<td>@Html.DisplayFor(m => m.CreditCard)</td>
</tr>
Immagine
HTML generato
<tr>
<td><label for="CreditCard">CreditCard</label></td>
<td><input class="text-box single-line" id="CreditCard" name="CreditCard" type="text" value="0123456789012345" /></td>
<td>0123456789012345</td>
</tr>
Commenti
- il metodo [Html.EditorFor] ha generato il tag <input> della riga 3 con un attributo [type] di tipo [text]. Il metodo [Html.DisplayFor] ha generato la riga 4. In questo caso non è chiaro quale sia il contributo dei metadati:
[DataType(DataType.CreditCard)]
Convalida
Non viene effettuata alcuna verifica, né lato client né lato server.
5.9. Convalida di un modulo
Abbiamo già affrontato il problema della convalida del modello di un'azione nel paragrafo 4.5 e nei paragrafi successivi. Torniamo su questa tematica nel contesto di un modulo:
- come segnalare all’utente gli errori di inserimento dati;
- effettuare le convalide sia lato client che lato server per segnalare più rapidamente gli errori all'utente.
5.9.1. Convalida lato server
Consideriamo il seguente modello:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Net.Mail;
namespace Exemple_03.Models
{
public class ViewModel11 : IValidatableObject
{
[Required(ErrorMessage = "Information requise")]
[Display(Name = "Chaîne d'au moins quatre caractères")]
[RegularExpression(@"^.{4,}$", ErrorMessage = "Information incorrecte")]
public string Chaine1 { get; set; }
[Display(Name = "Chaîne d'au plus quatre caractères")]
[Required(ErrorMessage = "Information requise")]
[RegularExpression(@"^.{1,4}$", ErrorMessage = "Information incorrecte")]
public string Chaine2 { get; set; }
[Required(ErrorMessage = "Information requise")]
[Display(Name = "Chaîne de quatre caractères exactement")]
[RegularExpression(@"^.{4,4}$", ErrorMessage = "Information incorrecte")]
public string Chaine3 { get; set; }
[Required(ErrorMessage = "Information requise")]
[Display(Name = "Nombre entier")]
public int Entier1 { get; set; }
[Display(Name = "Nombre entier dans l'intervalle [1,100]")]
[Required(ErrorMessage = "Information requise")]
[Range(1, 100, ErrorMessage = "Information incorrecte")]
public int Entier2 { get; set; }
[Display(Name = "Nombre réel")]
[Required(ErrorMessage = "Information requise")]
public double Reel1 { get; set; }
[Display(Name = "Nombre réel dans l'intervalle [10.2, 11.3]")]
[Required(ErrorMessage = "Information requise")]
[Range(10.2, 11.3, ErrorMessage = "Information incorrecte")]
public double Reel2 { get; set; }
[Display(Name = "Adresse mail")]
[Required(ErrorMessage = "Information requise")]
public string Email1 { get; set; }
[Display(Name = "Date sous la forme dd/jj/aaaa")]
[RegularExpression(@"\s*\d{2}/\d{2}/\d{4}\s*", ErrorMessage = "Information incorrecte")]
[Required(ErrorMessage = "Information requise")]
public string Regexp1 { get; set; }
[Display(Name = "Date postérieure à celle d'aujourd'hui")]
[Required(ErrorMessage = "Information requise")]
[DataType(DataType.Date)]
public DateTime Date1 { get; set; }
// convalida
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
List<ValidationResult> résultats = new List<ValidationResult>();
// Data 1
if (Date1.Date <= DateTime.Now.Date)
{
résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Date1" }));
}
// Email1
try
{
new MailAddress(Email1);
}
catch
{
résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Email1" }));
}
// viene restituito l'elenco degli errori
return résultats;
}
}
}
Questo modello verrà visualizzato dalla seguente vista [Action11Get.cshtml]:
![]() |
@model Exemple_03.Models.ViewModel11
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action11Get</title>
<link rel="stylesheet" href="~/Content/Site.css" />
</head>
<body>
<h3>Formulaire ASP.NET MVC – Validation 1</h3>
@using (Html.BeginForm("Action11Post", "First"))
{
<table>
<thead>
<tr>
<th>Type attendu</th>
<th>Valeur saisie</th>
<th>Message d'erreur</th>
</tr>
</thead>
<tbody>
<tr>
<td>@Html.LabelFor(m => m.Chaine1)</td>
<td>@Html.EditorFor(m => m.Chaine1)</td>
<td>@Html.ValidationMessageFor(m => m.Chaine1)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Chaine2)</td>
<td>@Html.EditorFor(m => m.Chaine2)</td>
<td>@Html.ValidationMessageFor(m => m.Chaine2)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Chaine3)</td>
<td>@Html.EditorFor(m => m.Chaine3)</td>
<td>@Html.ValidationMessageFor(m => m.Chaine3)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Entier1)</td>
<td>@Html.EditorFor(m => m.Entier1)</td>
<td>@Html.ValidationMessageFor(m => m.Entier1)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Entier2)</td>
<td>@Html.EditorFor(m => m.Entier2)</td>
<td>@Html.ValidationMessageFor(m => m.Entier2)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Reel1)</td>
<td>@Html.EditorFor(m => m.Reel1)</td>
<td>@Html.ValidationMessageFor(m => m.Reel1)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Reel2)</td>
<td>@Html.EditorFor(m => m.Reel2)</td>
<td>@Html.ValidationMessageFor(m => m.Reel2)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Email1)</td>
<td>@Html.EditorFor(m => m.Email1)</td>
<td>@Html.ValidationMessageFor(m => m.Email1)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Regexp1)</td>
<td>@Html.EditorFor(m => m.Regexp1)</td>
<td>@Html.ValidationMessageFor(m => m.Regexp1)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Date1)</td>
<td>@Html.EditorFor(m => m.Date1)</td>
<td>@Html.ValidationMessageFor(m => m.Date1)</td>
</tr>
</tbody>
</table>
<p>
<input type="submit" value="Valider" />
</p>
}
</body>
</html>
- riga 12: si fa riferimento al foglio di stile [Site.css]. Esso contiene, per impostazione predefinita, delle classi utilizzate per evidenziare gli errori di compilazione del modulo;
- righe 18-25: una tabella a tre colonne:
- la colonna 1 visualizza il testo con il metodo [Html.LabelFor],
- la colonna 2 visualizza i dati inseriti con il metodo [Html.EditorFor],
- la colonna 3 visualizza l'eventuale errore di immissione con il metodo [Html.ValidationMessageFor];
L'azione [Action11Get] serve a visualizzare il modulo:
// Azione11-GET
[HttpGet]
public ViewResult Action11Get()
{
return View("Action11Get", new ViewModel11());
}
L'azione [Action11Post] serve a visualizzare nuovamente il modulo con gli eventuali errori di immissione:
// Azione11-POST
[HttpPost]
public ViewResult Action11Post(ViewModel11 modèle)
{
return View("Action11Get", modèle);
}
- riga 3: il modello [ViewModel11] viene creato e quindi inizializzato con i valori inviati. A questo punto possono verificarsi degli errori. A ogni proprietà errata P del modello è associato un messaggio di errore. È proprio questo messaggio che permette di ottenere il metodo [Html.ValidationMessageFor] del modulo.
Ecco un esempio di esecuzione:
![]() | ![]() |
Ecco un altro esempio:
![]() |
Si noti che entrambe le date sono errate (oggi è l'11/10/2013), ma che gli errori non vengono segnalati. Questi errori vengono rilevati dal metodo [Validate] del modello:
// convalida
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
List<ValidationResult> résultats = new List<ValidationResult>();
// Data 1
if (Date1.Date <= DateTime.Now.Date)
{
résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Date1" }));
}
// Email1
try
{
new MailAddress(Email1);
}
catch
{
résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Email1" }));
}
// Espressione regolare 1
try
{
DateTime.ParseExact(Regexp1, "dd/MM/yyyy", CultureInfo.CreateSpecificCulture("fr-FR"));
}
catch
{
résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Regexp1" }));
}
// viene restituito l'elenco degli errori
return résultats;
}
Il metodo [Validate] viene eseguito solo quando tutte le validazioni degli attributi sono state superate. È quanto mostra un ultimo esempio:
![]() |
5.9.2. Convalida lato client
Tutte le verifiche precedenti sono state effettuate sul lato server. È quindi necessario uno scambio di dati tra il client e il server affinché l’utente si accorga dei propri errori. La convalida lato client utilizza codice JavaScript per segnalare all’utente i propri errori il prima possibile e in ogni caso prima del POST. Quest’ultimo può avvenire solo quando tutti gli errori rilevati sono stati corretti.
Riprendiamo il modello [ViewModel11] precedente, ma ora lo visualizziamo con la seguente vista [Action12Get.cshtml]:
![]() |
@model Exemple_03.Models.ViewModel11
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action12Get</title>
<link rel="stylesheet" href="~/Content/Site.css" />
<script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js" ></script>
<script type="text/javascript" src="~/Scripts/jquery.validate.min.js" ></script>
<script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js" ></script>
</head>
<body>
<h3>Formulaire ASP.NET MVC - Validation 1</h3>
@using (Html.BeginForm("Action11Post", "First"))
{
<table>
<thead>
<tr>
<th>Type attendu</th>
<th>Valeur saisie</th>
<th>Message d'erreur</th>
</tr>
</thead>
<tbody>
...
</tbody>
</table>
<p>
<input type="submit" value="Valider" />
</p>
}
</body>
</html>
Nota: riga 13, adattare la versione di jQuery a quella in uso con la propria versione di Visual Studio (vedere di seguito).
La convalida lato client richiede la presenza della riga 3 riportata di seguito nel file [Web.config] dell’applicazione.
<appSettings>
...
<add key="ClientValidationEnabled" value="true" />
</appSettings>
- righe 1-4: la sezione [appSettings] deve essere un elemento figlio diretto della sezione [configuration] del file [Web.config];
La vista [Action12Get] è identica alla vista precedente [Action11Get], ad eccezione delle righe 13-15. Queste ultime includono nella vista gli script JavaScript necessari per la convalida lato client. Questi script si trovano nella cartella [Scripts] del progetto:
![]() |
Ogni script ha una versione normale [.js] e una versione minificata [min.js]. Quest’ultima versione è più leggera ma illeggibile. Viene utilizzata in produzione. La versione leggibile viene utilizzata in fase di sviluppo.
La vista [Action12Get.cshtml] verrà visualizzata dalla seguente azione [Action12Get]:
// Azione12-GET
[HttpGet]
public ViewResult Action12Get()
{
return View("Action12Get", new ViewModel11());
}
Il modulo compilato verrà elaborato dalla seguente azione [Action12Post]:
// Azione12-POST
[HttpPost]
public ViewResult Action12Post(ViewModel11 modèle)
{
return View("Action12Get", modèle);
}
Vediamo cosa cambia con un esempio:
![]() |
Non appena si digita un carattere in [1], viene visualizzato il messaggio in [2] perché il valore previsto deve avere almeno quattro caratteri. La convalida viene quindi effettuata ad ogni nuovo carattere digitato. Il messaggio di errore scompare al quarto carattere digitato. Fatto ciò, convalidiamo il modulo:
![]() |
Il messaggio URL [3] ci mostra che il [POST] non si è verificato. Tuttavia, cliccando sul pulsante [Valider] sono state attivate tutte le validazioni lato client e sono comparsi nuovi messaggi di errore.
Diamo un'occhiata, ad esempio, al codice HTML generato per il primo inserimento:
<tr>
<td><label for="Chaine1">Chaîdi almeno quattro caratteri</label></td>
<td><input class="text-box single-line" data-val="true" data-val-regex="Information incorrecte" data-val-regex-pattern="^.{4,}$" data-val-required="Information requise" id="Chaine1" name="Chaine1" type="text" value="" /></td>
<td><span class="field-validation-valid" data-valmsg-for="Chaine1" data-valmsg-replace="true"></span></td>
</tr>
- alla riga 3 si trova:
- il messaggio di errore nel caso in cui l’inserimento sia mancante [data-val-required],
- il messaggio di errore nel caso in cui l'inserimento sia errato [data-val-regex],
- l'espressione regolare per la stringa inserita [data-val-regex-pattern];
- riga 4, altri attributi [data-x] utilizzati per visualizzare l’eventuale messaggio di errore;
Gli attributi [data-x] dei tag generati vengono utilizzati dal codice JavaScript che abbiamo incorporato nella vista. Se quest'ultimo è assente, tali attributi vengono semplicemente ignorati e non si ha quindi alcuna convalida lato client. Il funzionamento è lo stesso dell’esempio precedente. Da qui deriva il termine [unobtrusive] per questa tecnica.
5.10. Gestione dei link di navigazione e di azione
Creeremo le due viste seguenti per illustrare la gestione dei link in una vista:
![]() |
- in [1] e [2], ci sono due link di navigazione;
- in [3], c'è un link di azione che invia il modulo. Non serve per la navigazione.
La pagina 1 è generata dalla vista [Action16Get.cshtml] seguente:
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action16Get</title>
<script>
function postForm() {
// si recupera il modulo dal documento
var form = document.forms[0];
// invio
form.submit();
}
</script>
</head>
<body>
<h3>Navigation - page 1</h3>
<h4>@ViewBag.info</h4>
@using (Html.BeginForm("Action16Post", "Second"))
{
@Html.Label("data", "Tapez un texte")
@Html.TextBox("data")
<a href="javascript:postForm()">Valider</a>
}
<p>
@Html.ActionLink("Page 2", "Action17Get", "Second")
</p>
</body>
</html>
- riga 22: un'informazione inizializzata dall'azione che genererà la vista;
- righe 23-28: un modulo;
- riga 25: un'etichetta per il campo [data];
- riga 26: un campo di immissione denominato [data];
- riga 27: un link di tipo [submit]. Cliccandoci sopra, viene eseguita la funzione JavaScript [postForm] (attributo href). Questa è definita alle righe 12-17;
- riga 14: si recupera un riferimento al primo modulo del documento, quello della riga 23;
- riga 16: questo modulo viene inviato. In definitiva, è come se si fosse cliccato su un pulsante di tipo [submit]. Il modulo viene inviato al controller e all’azione specificati alla riga 23;
- riga 30: un link di navigazione. Il codice HTML generato è il seguente:
<a href="/Second/Action17Get">Page 2</a>
Il metodo utilizzato è ActionLink(Testo, Azione, Controller).
La pagina 2 viene generata dalla vista [Action17Get.cshtml] seguente:
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action17Get</title>
</head>
<body>
<h3>Navigation - Page 2</h3>
<h4>@ViewBag.info</h4>
<p>
@Html.ActionLink("Page 1", "Action16Get", "Second")
</p>
</body>
</html>
Le azioni che generano queste viste sono le seguenti:
// Azione16-GET
[HttpGet]
public ViewResult Action16Get()
{
ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
return View("Action16Get");
}
// Azione16-POST
[HttpPost]
public ViewResult Action16Post(string data)
{
ViewBag.info = string.Format("Contrôleur={0}, Action={1}, Data={2}", RouteData.Values["controller"], RouteData.Values["action"], data);
return View("Action16Get");
}
// Azione17-GET
[HttpGet]
public ViewResult Action17Get()
{
ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
return View();
}
- riga 6, l'azione [Action16Get] genera la vista [Action16Get.cshtml], ovvero la pagina 1 dell'esempio. Questa vista ha come modello la [ViewBag] (riga 5);
- riga 19, l'azione [Action17Get] genera la vista [Action17Get.cshtml], ovvero la pagina 2 dell'esempio. Questa vista ha come modello la vista [ViewBag] (riga 21);
- riga 11: l'azione [Action16Post] elabora il campo POST del modulo della vista [Action16Get.cshtml]. Riceve il parametro denominato [data]. Ricordiamo che si tratta del nome del campo di immissione nel modulo;
- riga 13: viene inserita un'informazione nel campo [ViewBag];
- riga 14: viene visualizzata la vista [Action16Get.cshtml].
Si invita il lettore a provare questo esempio.































































