7. Ajaxificazione di un’applicazione ASP.NET MVC
7.1. Il ruolo di AJAX in un’applicazione web
Per il momento, gli esempi didattici esaminati presentano la seguente architettura:
![]() |
Per passare da una vista [Vue1] a una vista [Vue2], il browser:
- invia una richiesta all’applicazione web;
- riceve la vista [Vue2] e la visualizza al posto della vista [Vue1].
Questo è lo schema classico:
- richiesta del browser;
- generazione di una vista in risposta al client da parte del server web;
- visualizzazione di questa nuova vista da parte del browser.
Esiste un altro modo di interazione tra il browser e il server web: AJAX (Asynchronous JavaScript and XML). Si tratta infatti di interazioni tra la vista visualizzata dal browser e il server web. Il browser continua a fare ciò che sa fare, ovvero visualizzare una vista HTML, ma ora viene gestito dal JavaScript incorporato nella vista HTML visualizzata. Lo schema è il seguente:
![]() |
- in [1], si verifica un evento nella pagina visualizzata nel browser (clic su un pulsante, modifica di un testo, ...). Questo evento viene intercettato dal JavaScript (JS) incorporato nella pagina;
- in [2], il codice JavaScript effettua una richiesta HTTP proprio come avrebbe fatto il browser. La richiesta è asincrona: l’utente può continuare a interagire con la pagina senza rimanere bloccato in attesa della risposta alla richiesta HTTP. La richiesta segue il classico processo di elaborazione. Nulla (o quasi) la distingue da una richiesta tradizionale;
- in [3], viene inviata una risposta al client JS. Piuttosto che una vista completa HTML, viene inviata una vista parziale HTML, un flusso XML o JSON (notazione oggetto JavaScript);
- in [4], il JavaScript recupera questa risposta e la utilizza per aggiornare una sezione della pagina HTML visualizzata.
Per l’utente si verifica un cambiamento nella visualizzazione poiché ciò che vede è cambiato. Tuttavia, non avviene un ricaricamento totale della pagina, ma semplicemente una modifica parziale della pagina visualizzata. Ciò contribuisce a conferire fluidità e interattività alla pagina: poiché non vi è un ricaricamento totale della pagina, è possibile gestire eventi che in precedenza non venivano gestiti. Ad esempio, proporre all’utente un elenco di opzioni man mano che digita i caratteri in una casella di immissione. Ad ogni nuovo carattere digitato, viene inviata una richiesta AJAX al server, che a sua volta restituisce ulteriori suggerimenti. Senza Ajax, questo tipo di assistenza alla digitazione era in precedenza impossibile. Non era possibile ricaricare una nuova pagina ad ogni carattere digitato.
7.2. Nozioni di base su JQuery e JavaScript
Abbiamo spesso integrato la libreria JavaScript JQuery nelle nostre pagine. Vi si trova quindi la riga:
<script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
Nota: adattate la versione di jQuery a quella della vostra versione di Visual Studio.
La tecnologia Ajax di ASP.NET MVC utilizza JQuery. Scriveremo noi stessi alcuni script JQuery. Presentiamo quindi ora le nozioni di base su JQuery necessarie per comprendere gli script di questo capitolo.
Creiamo un nuovo progetto [Exemple-04] all’interno della nostra soluzione [Exemples]:
![]() |
Per utilizzare Ajax con ASP.NET MVC, nel file di configurazione [Web.config] [1] deve essere presente una riga:
<appSettings>
...
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
La riga 3 consente l'utilizzo di Ajax nelle viste ASP.NET. È presente per impostazione predefinita.
Creiamo un file HTML [JQuery-01.html] nella cartella [Content] del nuovo progetto [2]:
![]() |
Questo file avrà il seguente contenuto:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>JQuery-01</title>
<script type="text/javascript" src="/Scripts/jquery-1.8.2.min.js"></script>
</head>
<body>
<h3>Rudiments de JQuery</h3>
<div id="element1">
Elément 1
</div>
</body>
</html>
- riga 6: importazione di JQuery (adattare la versione a quella del proprio Visual Studio);
- righe 10-12: un elemento della pagina con id [element1]. Ora sperimenteremo con questo elemento.
Visualizziamo questo file nel browser Google Chrome [4] e [5]:
![]() |
Con Google Chrome, digitare [Ctrl-Maj-I] per visualizzare gli strumenti di sviluppo [6]. La scheda [Console] [7] consente di eseguire codice JavaScript. Di seguito riportiamo alcuni comandi JavaScript da digitare, corredati da una spiegazione.
JS | risultato |
|
: restituisce la collezione di tutti gli elementi con id [element1], quindi normalmente una collezione di 0 o 1 elemento poiché non è possibile avere due id identici in una pagina HTML. | ![]() |
|
: applica il testo [blabla] a tutti gli elementi della collezione. Ciò comporta la modifica del contenuto visualizzato dalla pagina | ![]() |
|
nasconde gli elementi della collezione. Il testo [blabla] non viene più visualizzato. | ![]() |
|
: visualizza nuovamente la collezione. Questo ci permette di vedere che l'elemento con id [element1] ha l'attributo CSS style='display: none;', che fa sì che l'elemento sia nascosto. | |
|
: visualizza gli elementi della collezione. Il testo [blabla] ricompare. È l’attributo CSS style='display: block;' a garantire questa visualizzazione. | ![]() |
|
: assegna un attributo a tutti gli elementi della collezione. L’attributo in questo caso è [style] e il suo valore è [color: red]. Il testo [blabla] diventa rosso. | ![]() |
![]() | |
![]() |
Si noti che il codice URL del browser non è cambiato durante tutte queste operazioni. Non c'è stato alcun scambio con il server web. Tutto avviene all'interno del browser. Ora visualizziamo il codice sorgente della pagina:
![]() |
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>JQuery-01</title>
<script type="text/javascript" src="/Scripts/jquery-1.8.2.min.js"></script>
</head>
<body>
<h3>Rudiments de JQuery</h3>
<div id="element1">
Elément 1
</div>
</body>
</html>
Questo è il testo iniziale. Non riflette in alcun modo le modifiche apportate all’elemento nelle righe 10-12. È importante tenerlo presente quando si esegue il debug di JavaScript. Spesso, quindi, è inutile visualizzare il codice sorgente della pagina visualizzata. Per conoscere il codice sorgente della pagina attualmente visualizzata, si procederà nel modo seguente:
![]() |
Sappiamo abbastanza per comprendere gli script JS che seguiranno.
7.3. Aggiornamento di una pagina con un feed HTML
7.3.1. Le viste
Ci proponiamo di studiare la seguente applicazione:
![]() |
- in [1], l'ora di caricamento della pagina;
- in [2], si eseguono le quattro operazioni aritmetiche su due numeri reali A e B;
- in [3], la risposta del server viene inserita in una sezione della pagina;
- in [4], l'ora del calcolo. Questa è diversa dall'ora di caricamento della pagina [5]. Quest'ultimo è pari a [1], il che dimostra che l'area [6] non è stata ricaricata. Inoltre, i valori URL e [7] della pagina non sono cambiati.
7.3.2. Il controller, le azioni, il modello, la vista
Creiamo un controller denominato [Premier]:
![]() |
Per visualizzare la vista iniziale, creiamo la seguente azione [Action01Get]:
[HttpGet]
public ViewResult Action01Get()
{
ViewModel01 modèle = new ViewModel01();
modèle.HeureChargement = DateTime.Now.ToString("hh:mm:ss");
return View(modèle);
}
- riga 4: istanziazione del modello della vista;
- riga 5: inizializzazione del tempo di caricamento della vista;
- riga 6: visualizzazione della vista [Action10Get.cshtml] e del relativo modello.
Il modello [ViewModel01] è il seguente:
![]() |
using System;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Exemple_04.Models
{
[Bind(Exclude = "AplusB, AmoinsB, AmultipliéparB, AdiviséparB, Erreur, HeureChargement, HeureCalcul")]
public class ViewModel01
{
// modulo
[Required(ErrorMessage="Donnée requise")]
[Display(Name="Valeur de A")]
[Range(0, Double.MaxValue, ErrorMessage = "Tapez un nombre positif ou nul")]
public double A { get; set; }
[Required(ErrorMessage = "Donnée requise")]
[Display(Name = "Valeur de B")]
[Range(0, Double.MaxValue, ErrorMessage="Tapez un nombre positif ou nul")]
public double B { get; set; }
// risultati
public string AplusB { get; set; }
public string AmoinsB { get; set; }
public string AmultipliéparB { get; set; }
public string AdiviséparB { get; set; }
public string Erreur { get; set; }
public string HeureChargement { get; set; }
public string HeureCalcul { get; set; }
}
}
- righe 11-14: il valore A del modulo;
- righe 15-18: il valore B del modulo;
- righe 21-24: i risultati delle quattro operazioni aritmetiche su A e B;
- riga 25: il testo di un eventuale errore;
- riga 26: l'ora di caricamento della vista nel browser;
- riga 27: l'ora di calcolo dei campi delle righe 21-24;
- riga 7: questo modello di vista è anche un modello di azione. Da quest'ultimo vengono esclusi i campi che non vengono inviati dal browser.
La vista [Action01Get.cshtml] è la seguente:
![]() |
@model Exemple_04.Models.ViewModel01
@{
Layout = null;
AjaxOptions ajaxOpts = new AjaxOptions
{
UpdateTargetId = "résultats",
HttpMethod = "post",
Url = Url.Action("Action01Post"),
LoadingElementId = "loading",
LoadingElementDuration = 1000
};
}
<!DOCTYPE html>
<html lang="fr-FR">
<head>
<meta name="viewport" content="width=device-width" />
<title>Ajax-01</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>
<script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
<script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.fr-FR.js"></script>
<script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.en-US.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
<script type="text/javascript" src="~/Scripts/myScripts-01.js"></script>
</head>
<body>
<h2>Ajax - 01</h2>
<p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
<h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
@using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
{
<table>
<thead>
<tr>
<th>@Html.LabelFor(m => m.A)</th>
<th>@Html.LabelFor(m => m.B)</th>
</tr>
</thead>
<tbody>
<tr>
<td>@Html.TextBoxFor(m => m.A)</td>
<td>@Html.TextBoxFor(m => m.B)</td>
</tr>
<tr>
<td>@Html.ValidationMessageFor(m => m.A)</td>
<td>@Html.ValidationMessageFor(m => m.B)</td>
</tr>
</tbody>
</table>
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
}
<hr />
<div id="résultats" />
</body>
</html>
- riga 1: la vista ha come modello un tipo [ViewModel01];
- riga 21: è necessario JQuery sia per le validazioni che per Ajax;
- righe 22-23: le librerie di validazione;
- righe 24-26: le librerie di internazionalizzazione;
- riga 27: la libreria Ajax;
- riga 28: una libreria JavaScript locale;
- riga 33: visualizzazione dell'ora di caricamento della vista;
- riga 35: un modulo Ajax - ne riparleremo più avanti;
- righe 40-41: etichette per l'inserimento dei numeri A e B;
- righe 46-47: campi di immissione dei numeri A e B;
- righe 50-51: messaggi di errore per l'inserimento dei numeri A e B;
- riga 56: il pulsante che invia il modulo. Questo verrà inviato tramite una richiesta Ajax;
- riga 57: un'immagine di caricamento visualizzata durante la richiesta Ajax;
- riga 58: un link per inviare il modulo tramite una richiesta Ajax;
- riga 62: un tag <div> con l'ID [résultats]. È qui che inseriremo il flusso HTML restituito dal server web.
Questa vista mostra la pagina seguente:
![]() |
Esaminiamo ora il codice che rende il modulo compatibile con AJAX:
...
@{
Layout = null;
AjaxOptions ajaxOpts = new AjaxOptions
{
UpdateTargetId = "résultats",
HttpMethod = "post",
Url = Url.Action("Action01Post"),
LoadingElementId = "loading",
LoadingElementDuration = 1000
};
}
...
@using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
{
....
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
}
...
<div id="résultats" />
- riga 15: invece di utilizzare [@Html.BeginForm], si utilizza [@Ajax.BeginForm]. Questo metodo ammette numerose sovraccarichi. Quello utilizzato ha la seguente firma:
Ajax.BeginForm(string ActionName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions, IDictionary<string,object> htmlAttributes)
Qui utilizziamo i seguenti parametri effettivi:
Action01Post: il nome dell'azione che elaborerà il POST del modulo,
null: non ci sono informazioni di percorso da fornire,
ajaxOpts: le opzioni della chiamata Ajax. Sono state definite alle righe 6-10,
new { id = "form" }: per assegnare l'attributo [id='formulaire'] al tag <form> generato;
Le opzioni Ajax utilizzate sono le seguenti:
- riga 8: l'URL di destinazione della richiesta Ajax;
- riga 7: metodo della richiesta Ajax HTTP;
- riga 6: ID dell’area della pagina che verrà aggiornata dalla risposta alla richiesta Ajax;
- riga 9: ID dell’area della pagina che verrà visualizzata durante la richiesta Ajax – in genere un’immagine di attesa. In questo caso verrà visualizzata la riga 20, che contiene un’immagine animata che simboleggia l’attesa. Inizialmente, questa immagine è nascosta dallo stile [display : none];
- riga 10: tempo di attesa in millisecondi prima che l’immagine animata venga visualizzata, in questo caso 1 secondo.
Il codice HTML generato dal modulo Ajax è il seguente:
<form action="/Premier/Action01Post" data-ajax="true" data-ajax-loading="#loading" data-ajax-loading-duration="1000" data-ajax-method="post" data-ajax-mode="replace" data-ajax-update="#résultats" data-ajax-url="/Premier/Action01Post" id="formulaire" method="post"> <table>
...
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
</form>
<hr />
<div id="résultats" />
- riga 1: il tag <form> generato. Si notino gli attributi [data-ajax-attr] che riflettono i valori dei campi dell’oggetto di tipo [AjaxOptions] associato alla richiesta Ajax. Questi attributi sono gestiti dalla libreria Ajax. Senza di essi, il tag <form> diventa:
<form action="/Premier/Action01Post" id="formulaire" method="post">
...
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
</form>
Ci troviamo quindi di fronte a un modulo HTML classico. È questo il codice che verrà eseguito se l’utente disattiva JavaScript sul proprio browser. Le righe 5-6 risultano quindi inutilizzate.
7.3.3. L'azione [Action01Post]
L’azione [Action01Post] che gestisce la richiesta Ajax HTTP è la seguente:
[HttpPost]
public PartialViewResult Action01Post(FormCollection postedData, SessionModel session)
{
// simulazione in attesa
Thread.Sleep(2000);
// istanza del modello dell'azione
ViewModel01 modèle = new ViewModel01();
// ora di calcolo
modèle.HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
// aggiornamento del modello
TryUpdateModel(modèle, postedData);
if (!ModelState.IsValid)
{
// viene restituito un errore
modèle.Erreur = getErrorMessagesFor(ModelState);
return PartialView("Action01Error", modèle);
}
// una volta su due, si simula un errore
int val = session.Randomizer.Next(2);
if (val == 0)
{
modèle.Erreur = "[erreur aléatoire]";
return PartialView("Action01Error", modèle);
}
// calcoli
modèle.AplusB = string.Format("{0}", modèle.A + modèle.B);
modèle.AmoinsB = string.Format("{0}", modèle.A - modèle.B);
modèle.AmultipliéparB = string.Format("{0}", modèle.A * modèle.B);
modèle.AdiviséparB = string.Format("{0}", modèle.A / modèle.B);
// vista
return PartialView("Action01Success", modèle);
}
- riga 1: l'azione elabora solo un [POST];
- riga 2: ammette come modello di azione:
- [FormCollection postedData]: l'insieme dei valori inviati dalla richiesta Ajax POST,
- [SessionModel session]: gli elementi della sessione. Qui si utilizza una tecnica descritta al paragrafo 4.10;
- riga 2: l'azione restituirà un frammento HTML e non una pagina completa HTML;
- riga 5: artificialmente, ci si ferma per due secondi per simulare un'azione Ajax di lunga durata;
- riga 7: viene istanziato un modello di tipo [ViewModel01];
- riga 9: viene inizializzato il tempo di calcolo;
- riga 11: si tenta di aggiornare il modello di tipo [ViewModel01] con i valori inviati. Si ricorda che ce ne sono due: i valori dei numeri A e B;
- riga 12: si verifica se l'aggiornamento è andato a buon fine;
- riga 15: in caso di errore, si popola il campo [Erreur] del modello;
- riga 16: si restituisce una vista parziale [Action01Error.cshtml] che utilizza il modello [ViewModel01];
- righe 19-24: una volta su due, si simula un errore;
- riga 19: si genera un numero intero casuale nell'intervallo [0,1]. Il generatore di numeri viene prelevato dalla sessione;
- riga 20: se il valore generato è 0, si simula un errore;
- riga 22: il messaggio di errore viene inserito nel modello;
- riga 23: viene restituita una vista parziale [Action01Error.cshtml] che utilizza il modello [ViewModel01];
- righe 26-29: vengono eseguiti i calcoli aritmetici sui numeri A e B e i risultati vengono inseriti nel modello sotto forma di stringhe di caratteri;
- riga 31: viene restituita una vista parziale [Action01Success.cshtml] che utilizza il modello [ViewModel01];
7.3.4. La vista [Action01Error]
La vista [Action01Error.cshtml] è la seguente:
@model Exemple_04.Models.ViewModel01
<h4>Résultats</h4>
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p style="color: red;">Une erreur s'est produite : @Model.Erreur</p>
Si ricorda che questo flusso parziale HTML verrà inviato in risposta alla richiesta Ajax HTTP di tipo POST e inserito nella pagina nell’area con id [résultats]. Tutte queste informazioni provengono dalla configurazione Ajax utilizzata nella pagina principale [Action01Get.cshtml]:
@model Exemple_04.Models.ViewModel01
@{
Layout = null;
AjaxOptions ajaxOpts = new AjaxOptions
{
UpdateTargetId = "résultats",
HttpMethod = "post",
Url = Url.Action("Action01Post"),
LoadingElementId = "loading",
LoadingElementDuration = 1000
};
}
Ecco un esempio di risposta con errore:
![]() |
7.3.5. La vista [Action01Success]
La vista [Action01Success.cshtml] è la seguente:
@model Exemple_04.Models.ViewModel01
<h4>Résultats</h4>
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A+B=@Model.AplusB</p>
<p>A-B=@Model.AmoinsB</p>
<p>A*B=@Model.AmultipliéparB</p>
<p>A/B=@Model.AdiviséparB</p>
Anche in questo caso, questo flusso parziale HTML verrà inviato in risposta alla richiesta Ajax HTTP di tipo POST e inserito nella pagina nell'area con id [résultats]:
![]() |
7.3.6. G gestione della sessione
Abbiamo visto che [Action01Post] utilizzava la sessione. Il modello della sessione è il seguente tipo [SessionModel]:
using System;
namespace Exemple_03.Models
{
public class SessionModel
{
public Random Randomizer { get; set; }
}
}
La sessione viene inizializzata in [Global.asax]:
// Sessione
protected void Session_Start()
{
SessionModel sessionModel=new SessionModel();
sessionModel.Randomizer=new Random(DateTime.Now.Millisecond);
Session["data"] = sessionModel;
}
L'associazione della sessione a un modello viene effettuata in [Application_Start]:
protected void Application_Start()
{
...
// model binders
ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
}
È stata descritta la classe [SessionModelBinder].
7.3.7. Gestione dell'immagine di attesa
@model Exemple_04.Models.ViewModel01
@{
Layout = null;
AjaxOptions ajaxOpts = new AjaxOptions
{
...
LoadingElementId = "loading",
LoadingElementDuration = 1000
};
}
...
<body>
...
@using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
{
...
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
}
...
Quando viene avviata la richiesta Ajax, dopo un secondo viene visualizzata la regione con id [loading] alla riga 7 di [ligne 8]. Questa regione corrisponde all'immagine della riga 21 inizialmente nascosta. Si ottiene così la seguente interfaccia:
![]() |
7.3.8. Gestione del link [Calculer]
Esaminiamo il link [Calculer] della pagina principale [Action01Get.cshtml]:
<head>
<meta name="viewport" content="width=device-width" />
<title>Ajax-01</title>
...
<script type="text/javascript" src="~/Scripts/myScripts-01.js"></script>
</head>
<body>
<h2>Ajax - 01</h2>
<p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
<h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
@using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
{
...
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
}
<hr />
<div id="résultats" />
- riga 18: cliccando sul link [Calculer] si avvia l’esecuzione della funzione JS [postForm]. Quest’ultima è definita nel file [myScripts-01.js] alla riga 5. Lo script è il seguente:
![]() |
function postForm() {
// si effettua manualmente una chiamata Ajax con JQuery
var loading = $("#loading");
var formulaire = $("#formulaire");
var résultats = $('#risultati');
$.ajax({
url: '/Premier/Action01Post',
type: 'POST',
data: formulaire.serialize(),
dataType: 'html',
begin: loading.show(),
success: function (data) {
loading.hide()
résultats.html(data);
}
})
}
// http://blog.instance-factory.com/?p=268
$.validator.methods.number = function (value, element) {
return this.optional(element) ||
!isNaN(Globalize.parseFloat(value));
}
$.validator.methods.date = function (value, element) {
return this.optional(element) ||
!isNaN(Globalize.parseDate(value));
}
jQuery.extend(jQuery.validator.methods, {
range: function (value, element, param) {
//Utilizza il plugin Globalization per analizzare il valore
var val = Globalize.parseFloat(value);
return this.optional(element) || (
val >= param[0] && val <= param[1]);
}
});
Le funzioni delle righe 19-37 sono già state illustrate nel paragrafo 6.1. Gestiscono l’internazionalizzazione delle pagine. Non ci soffermeremo nuovamente su di esse. Nelle righe 1-17, effettuiamo manualmente la chiamata Ajax che, nel caso del pulsante [Calculer], veniva eseguita dalla libreria Ajax associata al progetto. A tal fine, utilizziamo la libreria JQuery associata al progetto.
- riga 3: un riferimento al componente con id [loading]. [$("#loading")] restituisce la collezione degli elementi con id [loading]. Ce n’è solo uno;
- riga 4: un riferimento al componente con ID [formulaire];
- riga 5: un riferimento al componente con ID [résultats];
- riga 6: la chiamata Ajax con le relative opzioni;
- riga 7: il URL come destinazione della chiamata Ajax;
- riga 8: il metodo utilizzato;
- riga 9: i dati inviati. [formulaire.serialize] crea la stringa [A=val1&B=val2] del campo POST del modulo con ID [formulaire];
- riga 10: il tipo di dati atteso in risposta. Si sa che il server restituirà un flusso HTML;
- riga 11: il metodo da eseguire all’avvio della richiesta. Qui si indica che occorre visualizzare il componente con ID [loading]. Si tratta dell’immagine animata di attesa;
- riga 12: il metodo da eseguire in caso di esito positivo della richiesta Ajax. Il parametro [data] è la risposta completa del server. Sappiamo che si tratta di un flusso HTML;
- riga 13: si nasconde l’indicatore di attesa;
- riga 14: si aggiorna il componente con ID [résultats] con il valore HTML del parametro [data].
Si invita il lettore a provare il link [Calculer]. Funziona come il pulsante [Calculer], con l'unica differenza di un'anomalia. Una volta utilizzato questo link, è possibile inviare valori non validi per A e B:
![]() |
- in [1] e [2] sono stati inseriti valori non validi, segnalati dai validatori lato client;
- in [3], si è cliccato sul link [Calculer];
- in [4], si è verificato un [POST] poiché si ottiene la risposta [4].
Quando i valori non sono validi e si clicca sul pulsante [Calculer], il [POST] verso il server non viene inviato. Nello stesso caso, con il link [Calculer], la richiesta [POST] al server viene inviata. Si osserva quindi un comportamento del pulsante [Calculer] che non siamo riusciti a riprodurre con il link [Calculer]. Anziché cercare di risolvere questo problema ora, lo lasciamo per un esempio successivo che illustrerà anche un altro problema di convalida lato client.
7.4. Aggiornamento di una pagina HTML con un flusso JSON
Nell’esempio precedente, il server web rispondeva alla richiesta Ajax HTTP con un flusso HTML. In questo flusso erano presenti dati accompagnati dalla formattazione HTML. Ci proponiamo di riprendere l’esempio precedente, ma questa volta con risposte JSON (JavaScript Object Notation) contenenti solo i dati. Il vantaggio è che in questo modo si trasmettono meno byte.
7.4.1. L'azione [Action02Get]
L’azione [Action02Get] costituirà il punto di ingresso della nuova applicazione. Il suo codice è il seguente:
@model Exemple_04.Models.ViewModel02
@{
Layout = null;
AjaxOptions ajaxOpts = new AjaxOptions
{
HttpMethod = "post",
Url = Url.Action("Action02Post"),
LoadingElementId = "loading",
LoadingElementDuration = 1000,
OnBegin = "OnBegin",
OnFailure = "OnFailure",
OnSuccess = "OnSuccess",
OnComplete = "OnComplete"
};
}
<!DOCTYPE html>
<html lang="fr-FR">
<head>
<meta name="viewport" content="width=device-width" />
<title>Ajax-02</title>
....
<script type="text/javascript" src="~/Scripts/myScripts-02.js"></script>
</head>
<body>
<h2>Ajax - 02</h2>
<p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
<h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
@using (Ajax.BeginForm("Action02Post", null, ajaxOpts, new { id = "formulaire" }))
{
...
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
}
<hr />
<div id="entete">
<h4>Résultats</h4>
<p><strong>Heure de calcul : <span id="heureCalcul"/></strong></p>
</div>
<div id="résultats">
<p>A+B=<span id="AplusB"/></p>
<p>A-B=<span id="AmoinsB"/></p>
<p>A*B=<span id="AmultipliéparB"/></p>
<p>A/B=<span id="AdiviséparB"/></p>
</div>
<div id="erreur">
<p style="color: red;">Une erreur s'est produite : <span id="msg"/></p>
</div>
</body>
</html>
- righe 4-14: le opzioni della chiamata Ajax;
- riga 10: la funzione JS da eseguire all’avvio della richiesta. Questa funzione è definita nel file JS a cui si fa riferimento alla riga 24;
- riga 11: la funzione JS da eseguire in caso di fallimento della richiesta;
- riga 12: la funzione JS da eseguire in caso di esito positivo della richiesta;
- riga 13: la funzione JS da eseguire dopo che la richiesta Ajax ha ottenuto il risultato (fallimento o esito positivo);
- righe 40-43: una regione con id [entete];
- righe 44-49: una regione con id [résultats]. Visualizzerà i risultati delle quattro operazioni aritmetiche;
- righe 50-52: una regione con id [erreur]. Visualizzerà un eventuale messaggio di errore.
7.4.2. L'azione [Action02Post]
La richiesta Ajax viene gestita dalla seguente azione [Action02Post]:
[HttpPost]
public JsonResult Action02Post(FormCollection postedData, SessionModel session)
{
// simulazione in attesa
Thread.Sleep(2000);
// convalida del modello
ViewModel02 modèle = new ViewModel02();
// tempi di caricamento e calcolo
string HeureChargement = DateTime.Now.ToString("hh:mm:ss");
string HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
// aggiornamento del modello
TryUpdateModel(modèle, postedData);
if (!ModelState.IsValid)
{
// viene restituito un errore
return Json(new { Erreur = getErrorMessagesFor(ModelState), HeureCalcul = HeureCalcul });
}
// una volta su due viene simulato un errore
int val = session.Randomizer.Next(2);
if (val == 0)
{
// viene restituito un errore
return Json(new { Erreur = "[erreur aléatoire]", HeureCalcul = HeureCalcul });
}
// calcoli
string AplusB = string.Format("{0}", modèle.A + modèle.B);
string AmoinsB = string.Format("{0}", modèle.A - modèle.B);
string AmultipliéparB = string.Format("{0}", modèle.A * modèle.B);
string AdiviséparB = string.Format("{0}", modèle.A / modèle.B);
// si restituiscono i risultati
return Json(new { Erreur = "", AplusB = AplusB, AmoinsB = AmoinsB, AmultipliéparB = AmultipliéparB, AdiviséparB = AdiviséparB, HeureCalcul = HeureCalcul });
}
- riga 2: il metodo restituisce un tipo [JsonResult], ovvero un testo nel formato JSON;
- riga 16: le informazioni vengono restituite sotto forma di un'istanza di classe anonima serializzata in JSON. Il metodo [getErrorMessagesFor] è già stato illustrato. La stringa JSON inviata al browser avrà la seguente forma:
- riga 31: stessa procedura per i risultati aritmetici. In questo caso, la stringa JSON inviata al browser avrà la seguente forma:
{"Erreur":"","AplusB":"4","AmoinsB":"-2","AmultipliéparB":"3","AdiviséparB":"0,333333333333333","HeureCalcul":"05:52:17"}
7.4.3. Il codice JavaScript lato client
Ricordiamo la configurazione della chiamata Ajax nella pagina HTML inviata al browser client:
AjaxOptions ajaxOpts = new AjaxOptions
{
HttpMethod = "post",
Url = Url.Action("Action02Post"),
LoadingElementId = "loading",
LoadingElementDuration = 1000,
OnBegin = "OnBegin",
OnFailure = "OnFailure",
OnSuccess = "OnSuccess",
OnComplete = "OnComplete"
};
Le funzioni JS a cui si fa riferimento alle righe 7-10 (a destra del segno =) sono definite nel seguente file [myScripts-02.js]:
// dati globali
var entete;
var loading;
var résultats;
var erreur;
var heureCalcul;
var msg;
var AplusB;
var AmoinsB;
var AmultipliéparB;
var AdiviséparB;
var formulaire;
...
function postForm() {
...
}
// al caricamento del documento
$(document).ready(function () {
formulaire = $("#formulaire");
entete = $("#entete");
loading = $("#loading");
erreur = $("#erreur");
résultats = $('#risultati');
heureCalcul = $("#heureCalcul");
msg = $("#msg");
AplusB = $("#AplusB");
AmoinsB = $("#AmoinsB");
AmultipliéparB = $("#AmultipliéparB");
AdiviséparB = $("#AdiviséparB");
// si nascondono alcuni elementi della pagina
entete.hide();
résultats.hide();
erreur.hide();
});
// avvio
function OnBegin() {
....
}
// fine della richiesta
function OnComplete() {
...
}
// operazione riuscita
function OnSuccess(data) {
....
}
// errore
function OnFailure(request, error) {
...
}
- riga 19: la funzione JS viene eseguita al termine del caricamento della pagina nel browser;
- righe 20-30: si recuperano i riferimenti di tutti i componenti della pagina che ci interessano. La ricerca di un componente in una pagina ha un costo ed è preferibile effettuarla una sola volta;
- righe 33-35: i componenti [entete], [résultats] e [loading] vengono nascosti;
All’avvio della richiesta Ajax, viene eseguita la seguente funzione:
// avvio
function OnBegin() {
// segnale di attesa acceso
loading.show();
// alcuni elementi della pagina vengono nascosti
entete.hide();
résultats.hide();
erreur.hide();
}
- riga 4: viene visualizzato il componente [loading]. Si tratta dell'immagine animata;
- righe 6-8: i componenti [entete], [résultats] e [erreur] vengono nascosti;
Se la richiesta Ajax va a buon fine, viene eseguito il seguente codice JS:
// operazione riuscita
function OnSuccess(data) {
// visualizzazione dei risultati
heureCalcul.text(data.HeureCalcul);
entete.show();
if (data.Erreur != '') {
msg.text(data.Erreur);
erreur.show();
return;
}
// nessun errore
AplusB.text(data.AplusB);
AmoinsB.text(data.AmoinsB);
AmultipliéparB.text(data.AmultipliéparB);
AdiviséparB.text(data.AdiviséparB);
résultats.show();
}
Per comprendere questo codice è necessario ricordare i due testi JSON che potrebbero essere inviati in risposta al browser:
in caso di errore, altrimenti la stringa:
{"Erreur":"","AplusB":"4","AmoinsB":"-2","AmultipliéparB":"3","AdiviséparB":"0,333333333333333","HeureCalcul":"05:52:17"}
Se chiamiamo questa stringa [data], il valore del campo [Erreur] si ottiene con la notazione [data.Erreur] o [data["Erreur"]], a scelta. Lo stesso vale per gli altri campi della stringa JSON. Inoltre, per assegnare un testo non formattato a un componente con ID X, si scrive [X.text(chaine)]. Torniamo al codice della funzione [OnSuccess]:
- riga 2: [data] è la stringa JSON ricevuta;
- riga 4: il componente [heureCalcul] riceve il suo valore;
- riga 5: viene visualizzato il componente [entete];
- riga 6: verifica del campo [Erreur] della stringa JSON;
- riga 7: il componente [msg] riceve il proprio valore;
- riga 8: viene visualizzato il componente [erreur];
- riga 9: il caso di errore è terminato;
- riga 12: il componente [AplusB] riceve il proprio valore;
- riga 13: il componente [AmoinsB] riceve il proprio valore;
- riga 14: il componente [AmultipliéparB] riceve il proprio valore;
- riga 15: il componente [AdiviséparB] riceve il proprio valore;
- riga 16: viene visualizzato il componente [résultats].
La funzione [OnFailure] verrà eseguita in caso di fallimento della richiesta Ajax HTTP. Tale fallimento viene rilevato tramite il codice HTTP restituito dal server. Il codice 500 [Internal Server Error], ad esempio, indica che il server non è riuscito a eseguire la richiesta. La funzione [OnFailure] è la seguente:
// errore
function OnFailure(request, error) {
alert("L'erreur suivante s'est produite :" + error);
}
Ci si limita a visualizzare una finestra di dialogo con l’errore che si è verificato. In pratica, bisognerebbe essere più precisi. Presto proporremo un’altra soluzione.
Infine, la funzione [OnComplete] viene eseguita al termine della richiesta, sia in caso di esito positivo che negativo.
// fine della richiesta
function OnComplete() {
// segnale di attesa disattivato
loading.hide();
}
Ricordiamo che è la configurazione della chiamata Ajax nella vista [Action02Get.cshtml] a determinare la chiamata di queste diverse funzioni:
AjaxOptions ajaxOpts = new AjaxOptions
{
...
OnBegin = "OnBegin",
OnFailure = "OnFailure",
OnSuccess = "OnSuccess",
OnComplete = "OnComplete"
};
7.4.4. Il link [Calculer]
Il codice HTML del link [Calculer] nella vista [Action02Get.cshtml] è il seguente:
<a href="javascript:postForm()">Calculer</a>
La funzione JS [postForm] si trova nel file importato [myScripts-02.js]:
<script type="text/javascript" src="~/Scripts/myScripts-02.js"></script>
Il suo codice è il seguente:
function postForm() {
// si esegue manualmente una chiamata Ajax con JQuery
$.ajax({
url: '/Premier/Action02Post',
type: 'POST',
data: formulaire.serialize(),
dataType: 'json',
beforeSend: OnBegin,
success: OnSuccess,
error: OnFailure,
complete: OnComplete
})
}
Abbiamo già riscontrato un codice simile.
- riga 4: URL destinazione della chiamata Ajax;
- riga 5: comando HTTP utilizzato dalla chiamata Ajax;
- riga 6: valori inviati. Sono il risultato della serializzazione dei valori del modulo. Quest'ultimo, identificato dall'ID [formulaire], è referenziato dalla variabile [formulaire]. [data] sarà una stringa di caratteri della forma [A=val1&B=val2];
- riga 7: tipo di formattazione della risposta prevista. Si tratta di una stringa JSON;
- riga 8: funzione JS da eseguire all’avvio della chiamata Ajax;
- riga 9: funzione JS da eseguire se la chiamata Ajax va a buon fine;
- riga 10: funzione JS da eseguire se la chiamata Ajax fallisce;
- riga 11: funzione JS da eseguire una volta ricevuta la risposta dal server, indipendentemente dal fatto che si tratti di un esito positivo o di un errore.
Torniamo alla funzione JavaScript che gestisce il caso in cui la chiamata Ajax fallisca (riga 10). La chiamata Ajax fallisce in diverse situazioni, ad esempio quando il server restituisce un codice di errore come [403 Forbidden], [404 Not Found], [500 Internal Server Error], [301 Moved Permanently], ...
Nell’esempio precedente, la funzione [OnFailure] è la seguente:
// errore
function OnFailure(request, error) {
alert("L'erreur suivante s'est produite :" + error);
}
In genere, la visualizzazione dell'oggetto [error] non fornisce alcuna informazione rilevante. Se si utilizza una chiamata Ajax effettuata con JQuery, è possibile utilizzare il seguente metodo [OnFailure]:
// errore
function OnFailure(jqXHR) {
alert("Erreur : " + jqXHR.status + " " + jqXHR.statusText);
msg.html(jqXHR.responseText);
erreur.show();
}
L'oggetto JQuery [jqXHR] presenta tra le sue proprietà le seguenti:
- responseText: il testo della risposta del server;
- status: il codice di errore restituito dal server;
- statusText: il testo associato a questo codice di errore.
- riga 3: viene visualizzato il codice di errore e la descrizione corrispondente;
- riga 4: si inserisce la risposta HTML del server nel componente con id [msg];
- riga 5: si visualizza la regione con ID [erreur].
Per testare questa funzione di gestione degli errori, creeremo artificialmente un'eccezione nell'azione [Action02Post]:
[HttpPost]
public JsonResult Action02Post(FormCollection postedData, SessionModel session)
{
// un'eccezione artificiale per testare la gestione degli errori della chiamata Ajax
throw new Exception();
// simulazione di attesa
Thread.Sleep(2000);
// convalida del modello
...
La riga 5 genera un'eccezione. Ora proviamo l'applicazione:
![]() |
Si ottiene la seguente risposta [1] e [2]:
![]() |
La risposta del server ci permette di vedere in quale riga del codice server si è verificato l’errore. Spesso è un’informazione utile da conoscere. D’ora in poi useremo questa tecnica per gestire gli errori delle chiamate Ajax.
7.5. Applicazione web a pagina singola
La tecnologia Ajax consente di realizzare applicazioni a pagina singola:
- la prima pagina deriva da una richiesta classica del browser;
- le pagine successive vengono ottenute tramite chiamate Ajax. Pertanto, in definitiva, il browser non cambia mai pagina (URL) e non carica mai una nuova pagina. Questo tipo di applicazione viene chiamata Applicazione a Pagina Singola (APU) o, in inglese, Single Page Application (SPA).
Ecco un esempio semplice di tale applicazione. La nuova applicazione avrà due viste:
![]() |
![]() |
- in [1], l’azione [Action03Get] ci permette di visualizzare la prima pagina, la pagina 1;
- in [2], un link ci permette di passare alla pagina 2 tramite una chiamata Ajax;
- in [3], l'URL non è cambiato. La pagina visualizzata è la pagina 2;
- in [4], un link ci permette di tornare alla pagina 1 tramite una chiamata Ajax;
- in [5], la pagina URL non è cambiata. La pagina visualizzata è la pagina 1.
Il codice dell'azione [Action03Get] è il seguente:
[HttpGet]
public ViewResult Action03Get()
{
return View();
}
- riga 4: viene visualizzata la vista [Action03Get.cshtml].
La vista [Action03Get.cshtml] è la seguente:
![]() |
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action03Get</title>
<script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
</head>
<body>
<h3>Ajax - 03 - Single Page Application</h3>
<div id="content">
@Html.Partial("Page1")
</div>
</body>
</html>
- righe 16-18: un elemento con id [content]. È in questo elemento che verranno visualizzate le diverse pagine;
- riga 17: per impostazione predefinita, verrà visualizzata per prima la pagina [Page1.cshtml].
La pagina [Page1.cshtml] è la seguente:
<h4>Page 1</h4>
<p>
@Ajax.ActionLink("Page 2", "Action04", new { Page = 2 }, new AjaxOptions() { UpdateTargetId = "content" })
</p>
- riga 1: il titolo della pagina per distinguerla dalla pagina 2;
- riga 3: un link Ajax con i seguenti parametri:
- il testo del link [Page 2];
- l'azione di destinazione del link [Action04];
- i parametri della richiesta URL. In questo caso sarà [/Premier/Action04?Page=2];
- le opzioni della chiamata Ajax. In questo caso, solo l’ID della regione da aggiornare con la risposta del server. Per le altre opzioni, vengono utilizzati i valori predefiniti, se presenti. Il metodo predefinito di HTTP è GET.
Vediamo cosa succede quando si fa clic sul link. Viene richiesta la URL [/Premier/Action04?Page=2] con una GET. Viene quindi eseguita l’azione [Action04]:
[HttpGet]
public PartialViewResult Action04(string page = "1")
{
string vue = "Page1";
if (page == "2")
{
vue = "Page2";
}
return PartialView(vue);
}
- riga 2: l’azione restituisce un flusso HTML parziale;
- riga 2: l'azione ha come modello la stringa [page]. Tuttavia, sappiamo che URL contiene questa informazione: [/Premier/Action04?Page=2]. Ricordiamo che il modello non fa distinzione tra maiuscole e minuscole;
- righe 4-8: [vue] riceverà il valore [Page2];
- riga 9: viene restituita la vista parziale [Page2.cshtml].
La vista parziale [Page2.cshtml] è la seguente:
<h4>Page 2</h4>
<p>
@Ajax.ActionLink("Page 1", "Action04", new { Page = 1 }, new AjaxOptions() { UpdateTargetId = "content" })
</p>
Il server restituisce quindi il flusso HTML sopra riportato come risposta alla chiamata Ajax GET [/Premier/Action04?Page=2]. Ricordiamo che questa chiamata Ajax utilizza questa risposta per aggiornare l’area con id [content] (riga 3 qui sotto):
<h4>Page 1</h4>
<p>
@Ajax.ActionLink("Page 2", "Action04", new { Page = 2 }, new AjaxOptions() { UpdateTargetId = "content" })
</p>
Ciò determina la visualizzazione del seguente nuovo contenuto [1]:
![]() |
Seguendo lo stesso ragionamento, si nota che cliccando sul link [Page 1] da [1] verrà visualizzato [2].
Torniamo allo schema generale di un'applicazione ASP.NET MVC:
![]() |
Grazie al codice JavaScript incorporato nelle pagine HTML ed eseguito nel browser, è possibile trasferire parte del codice sul browser e ottenere la seguente architettura:
![]() |
- in [1], il livello Web ASP.NET MVC è diventato un'interfaccia web per l'accesso ai dati, generalmente ospitati in un database. Le viste fornite contengono solo dati e nessun elemento di impaginazione HTML, ad esempio i flussi XML o JSON;
- in [2]: il browser visualizza viste statiche (cioè non generate dinamicamente) fornite da un server web che può trovarsi o meno sulla stessa macchina del server [1]. Queste pagine statiche vengono poi arricchite dai dati ottenuti tramite JavaScript dall’interfaccia web [1];
- il codice JavaScript incorporato nelle pagine HTML può essere strutturato in livelli:
- il livello [présentation] gestisce le interazioni con l’utente,
- il livello [DAO] gestisce l’accesso ai dati tramite il server web [1],
- il livello [métier] corrisponde al livello [métier] che in precedenza si trovava sul server [1] ed è stato trasferito sul browser [2];
Il vantaggio di questa architettura è che richiede competenze diverse:
- il codice del server web [1] richiede competenze relative a .NET ma non competenze in Javascript, HTML, CSS;
- il codice incorporato nel browser [2] richiede competenze in JavaScript, HTML, CSS, ma è indipendente dalla tecnologia del server web [1].
Pertanto, questa architettura facilita il lavoro in parallelo di team con competenze diverse. Si presta particolarmente bene alle applicazioni a pagina singola.
7.6. Applicazione web a pagina singola e convalida lato client
Abbiamo menzionato in precedenza un'anomalia nell'esempio Ajax-01. Ne ricordiamo il contesto:
![]() |
- in [1] e [2] sono stati inseriti valori non validi. Questi vengono segnalati dai validatori lato client;
- in [3], si è cliccato sul link [Calculer];
- in [4], si è verificato un [POST] poiché si ottiene la risposta [4].
Quando i valori non sono validi e si clicca sul pulsante [Calculer], la richiesta [POST] al server non viene inviata. Nello stesso caso, con il link [Calculer], la richiesta [POST] al server viene inviata. Si osserva quindi un comportamento del pulsante [Calculer] che non siamo riusciti a riprodurre con il link [Calculer].
Riprenderemo questo esempio in un nuovo contesto: l’applicazione avrà diverse viste e sarà del tipo [Application à Page Unique] che abbiamo appena descritto.
7.6.1. Le viste dell’esempio
L’esempio presenta diverse viste:
![]() |
- in [1], la vista [Action05Get];
- da [2], la vista parziale [Formulaire05];
- in [3], la vista parziale [Failure05];
![]() |
- in [4], la vista parziale [Success05].
L'applicazione è a pagina singola: questa viene caricata dal browser al momento della prima richiesta. Viene poi aggiornata tramite chiamate Ajax.
Le pagine precedenti sono generate dalle seguenti viste [cshtml]:
![]() |
La vista caricata inizialmente è la seguente vista [Action05Get.cshtml]:
@model Exemple_04.Models.ViewModel05
@{
Layout = null;
}
<!DOCTYPE html>
<html lang="fr-FR">
<head>
<meta name="viewport" content="width=device-width" />
<title>Ajax-05</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>
<script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
<script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.fr-FR.js"></script>
<script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.en-US.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
<script type="text/javascript" src="~/Scripts/myScripts-05.js"></script>
</head>
<body>
<h2>Ajax - 05, Page unique - Validation formulaire côté client</h2>
<p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
<h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
<div id="content">
@Html.Partial("Formulaire05", Model)
</div>
</body>
</html>
Si notino i seguenti punti:
- riga 1: il modello della vista è il tipo [ViewModel05] che presenteremo tra poco;
- righe 13-19: sono presenti gli script JavaScript necessari per l'Ajax e la validazione lato client;
- riga 20: aggiungeremo le nostre funzioni JavaScript in [myScripts-05.js];
- riga 27: l'immagine animata di attesa;
- righe 28-30: un tag con id [content]. È all’interno di questo tag che verranno inserite le viste parziali [Formulaire05, Success05, Failure05];
- riga 29: inserimento della vista parziale [Formulaire05].
La vista [Action05Get] è responsabile della visualizzazione della parte [1] della pagina iniziale:
![]() |
La vista parziale [Formulaire05] genererà la parte [2] sopra indicata. Il suo codice è il seguente:
@model Exemple_04.Models.ViewModel05
@using (Html.BeginForm("Action05Post", "Premier", FormMethod.Post, new { id = "formulaire" }))
{
<table>
<thead>
<tr>
<th>@Html.LabelFor(m => m.A)</th>
<th>@Html.LabelFor(m => m.B)</th>
</tr>
</thead>
<tbody>
<tr>
<td>@Html.TextBoxFor(m => m.A)</td>
<td>@Html.TextBoxFor(m => m.B)</td>
</tr>
<tr>
<td>@Html.ValidationMessageFor(m => m.A)</td>
<td>@Html.ValidationMessageFor(m => m.B)</td>
</tr>
</tbody>
</table>
<p>
<table>
<tbody>
<tr>
<td><a href="javascript:calculer()">Calculer</a>
</td>
<td style="width: 20px" />
<td><a href="javascript:effacer()">Effacer</a>
</td>
</tr>
</tbody>
</table>
</p>
}
- riga 1: la vista parziale accetta come modello un tipo [ViewModel05];
- riga 3: il modulo generato dal metodo [Html.BeginForm]. Poiché questo modulo verrà inviato tramite una chiamata Ajax, i primi tre parametri del metodo verranno ignorati. A meno che l’utente non abbia disabilitato JavaScript sul proprio browser. In questo caso ignoriamo tale possibilità. Il quarto parametro è importante. Il modulo avrà l’ID [formulaire];
- righe 5-22: il modulo per l’inserimento dei numeri A e B;
- riga 27: un link JavaScript che avvia l'esecuzione delle quattro operazioni aritmetiche su A e B;
- riga 30: un link JavaScript che cancella i dati inseriti e gli eventuali messaggi di errore ad essi correlati.
Si noti che il modulo non presenta alcun pulsante di tipo [submit]. Dovremo quindi calcolare manualmente il [Post] dei valori A e B inseriti.
Se non ci sono errori, vengono visualizzati i risultati:
![]() |
La parte [4] sopra riportata è generata dalla seguente vista parziale [Success05.cshtml]:
@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h4>Résultats</h4>
<p>A+B=@Model.AplusB</p>
<p>A-B=@Model.AmoinsB</p>
<p>A*B=@Model.AmultipliéparB</p>
<p>A/B=@Model.AdiviséparB</p>
<p>
<a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>
- riga 1: la vista parziale [Success05.cshtml] riceve un modello di tipo [ViewModel05];
- riga 12: un link JavaScript per tornare ai campi di immissione.
In caso di errore, viene visualizzata un'altra vista parziale [3]:
![]() |
Questa vista è generata dal seguente codice [Failure05.cshtml]:
@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
@foreach (string msg in Model.Erreurs)
{
<li>@msg</li>
}
</ul>
<p>
<a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>
- riga 1: la vista parziale [Failure05.cshtml] riceve un modello di tipo [ViewModel05];
- riga 14: un link JavaScript per tornare ai campi di immissione.
7.6.2. Il modello delle viste
Tutte le viste precedenti condividono lo stesso modello [ViewModel05]:
![]() |
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Exemple_04.Models
{
[Bind(Exclude = "AplusB, AmoinsB, AmultipliéparB, AdiviséparB, Erreurs, HeureChargement, HeureCalcul")]
public class ViewModel05
{
// modulo
[Required(ErrorMessage="Donnée A requise")]
[Display(Name="Valeur de A")]
[Range(0, Double.MaxValue, ErrorMessage = "Tapez un nombre A positif ou nul")]
public string A { get; set; }
[Required(ErrorMessage = "Donnée B requise")]
[Display(Name = "Valeur de B")]
[Range(0, Double.MaxValue, ErrorMessage="Tapez un nombre B positif ou nul")]
public string B { get; set; }
// risultati
public string AplusB { get; set; }
public string AmoinsB { get; set; }
public string AmultipliéparB { get; set; }
public string AdiviséparB { get; set; }
public List<string> Erreurs { get; set; }
public string HeureChargement { get; set; }
public string HeureCalcul { get; set; }
}
}
Si tratta del modello [ViewModel01] già presentato, con alcune piccole differenze:
- righe 15 e 19: i campi A e B sono ora di tipo [string] per visualizzare campi di immissione vuoti anziché campi con valore 0, al momento della visualizzazione iniziale del modulo di immissione;
- righe 14 e 18: ciò non impedisce di verificare il valore inserito con un validatore [Range];
- riga 26: un elenco di messaggi di errore visualizzato dalla vista [Failure05].
7.6.3. I dati di ambito [Session]
Nel paragrafo 7.3.6 abbiamo visto che i dati della sessione erano incapsulati nel seguente modello [SessionModel]:
![]() |
using System;
namespace Exemple_03.Models
{
public class SessionModel
{
// il generatore di numeri casuali
public Random Randomizer { get; set; }
}
}
Questo modello di sessione viene esteso per includere i valori di A e B:
using System;
namespace Exemple_03.Models
{
public class SessionModel
{
// il generatore di numeri casuali
public Random Randomizer { get; set; }
// i valori di A e B
public string A { get; set; }
public string B { get; set; }
}
}
È infatti necessario memorizzare i valori di A e B nella sessione, come mostra la seguente sequenza:
Richiesta 1
![]() |
Richiesta 2
![]() |
In [4] si ritrovano i dati inseriti in [1]. Tuttavia, ci sono due richieste distinte HTTP. È noto che la memoria tra due richieste HTTP è la sessione. Affinché la seconda richiesta possa recuperare i valori inviati dalla prima, è necessario che questi ultimi siano inseriti nella sessione.
7.6.4. L’azione server [Action05Get]
L’azione [Action05Get] è quella che visualizza la pagina iniziale unica. Il suo codice è il seguente:
[HttpGet]
public ViewResult Action05Get()
{
ViewModel05 modèle = new ViewModel05();
modèle.HeureChargement = DateTime.Now.ToString("hh:mm:ss");
return View(modèle);
}
- riga 6: la vista [Action05Get.cshtml] già esaminata viene visualizzata con un modello di tipo [ViewModel05];
7.6.5. L'azione client [Calculer]
Esaminiamo le interazioni dell’utente con le viste:
![]() |
Il link [1] è un link JavaScript:
<a href="javascript:calculer()">Calculer</a>
La funzione JavaScript [calculer] si trova nel file [myScripts-05.js]:
<script type="text/javascript" src="~/Scripts/myScripts-05.js"></script>
Il codice della funzione JavaScript [calculer] è il seguente:
// dati globali
var content;
var loading;
function calculer() {
// innanzitutto i riferimenti su DOM
var formulaire = $("#formulaire");
// poi la convalida del modulo
if (!formulaire.validate().form()) {
// modulo non valido - terminato
return;
}
// si effettua manualmente una chiamata Ajax
$.ajax({
url: '/Premier/Action05FaireCalcul',
type: 'POST',
data: formulaire.serialize(),
dataType: 'html',
beforeSend: function () {
loading.show();
},
success: function (data) {
content.html(data);
},
complete: function () {
loading.hide();
},
error: function (jqXHR) {
// visualizzazione della risposta del server
content.html(jqXHR.responseText);
}
})
}
function retourSaisies() {
...
}
function effacer() {
...
}
// al caricamento del documento
$(document).ready(function () {
// si recuperano i riferimenti dei vari componenti della pagina
loading = $("#loading");
content = $("#content");
// si memorizza l'immagine animata nella cache
loading.hide();
});
- si ricorda che il codice JavaScript viene sempre eseguito sul lato client, nel browser;
- riga 44: la funzione JS viene eseguita al termine del caricamento iniziale della pagina singola;
- riga 46: riferimento all’immagine animata con id [loading];
- riga 47: riferimento all’area con id [content]. È questa area che riceve le viste parziali [Formulaire05, Success05, Failure05];
- righe 2-3: le variabili delle righe 46-47 sono dichiarate globali affinché le altre funzioni possano accedervi. La ricerca di elementi in una pagina comporta un costo (righe 46-47). Non è necessario ripetere questa ricerca se è possibile evitarlo;
- riga 5: la funzione [calculer];
- riga 7: si recupera un riferimento al modulo. La vista parziale [Formulaire05] gli ha assegnato l’ID [formulaire];
- riga 9: questa istruzione esegue i validatori del modulo lato client. È ciò che mancava nell’anomalia rilevata a pagina 183. Questo metodo è fornito dalla libreria [jquery.unobstrusive-ajax] utilizzata dalla pagina singola:
<script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
L'istruzione restituisce [false] se il modulo viene dichiarato non valido;
- riga 11: la chiamata Ajax al server non viene effettuata se il modulo è non valido;
- righe 14-32: viene effettuata la chiamata Ajax al server;
- riga 15: l'URL di destinazione è l'azione server [Action05FaireCalcul];
- riga 16: viene richiesta tramite un [POST];
- riga 17: i valori inviati. Si tratta dei dati inseriti nel modulo, in questo caso i valori di A e B;
- righe 22-24: in caso di esito positivo della chiamata Ajax, la funzione [calculer] aggiorna l’area con id [content] con il flusso HTML inviato dal server.
Questo flusso HTML è quello inviato dall’azione [Action05FaireCalcul] a cui è diretta la chiamata Ajax. Il codice di questa azione lato server è il seguente:
[HttpPost]
public PartialViewResult Action05FaireCalcul(FormCollection postedData, SessionModel session)
{
// modello
ViewModel05 modèle = new ViewModel05();
// ora di calcolo
modèle.HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
// aggiornamento del modello
TryUpdateModel(modèle, postedData);
if (!ModelState.IsValid)
{
// viene restituito un errore
modèle.Erreurs = getListOfMessagesFor(ModelState);
return PartialView("Failure05", modèle);
}
...
}
- riga 1: l’azione accetta solo un [post];
- riga 2: restituisce una vista parziale;
- riga 2: riceve come parametri i valori inviati via POST (postedData) e il modello della sessione (session);
- riga 5: viene creato il modello della vista parziale;
- riga 7: viene aggiornato con l'ora di calcolo;
- riga 9: si tenta di applicare i valori inviati al modello. Verranno quindi eseguiti i validatori di quest’ultimo. Ci si potrebbe chiedere perché ci si prenda questo disturbo, dato che i validatori lato client impediscono l’esecuzione di POST se i dati inseriti non sono validi. In realtà, non si è certi della provenienza del POST. Potrebbe essere stato generato da un codice che non è il nostro. Pertanto, è necessario effettuare sempre le verifiche lato server;
- riga 10: si verifica se i validatori hanno avuto esito positivo;
- riga 13: se il modello non è valido, lo si aggiorna con un elenco di errori. Non si entrerà nel dettaglio del metodo interno [getListOfMessagesFor], analogo al metodo [GetErrorMessagesFor] descritto a pagina 65;
- riga 14: viene visualizzata la vista parziale [Failure05] con il relativo modello. Si riporta il codice di questa vista;
@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
@foreach (string msg in Model.Erreurs)
{
<li>@msg</li>
}
</ul>
<p>
<a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>
- righe 7-12: l'elenco degli errori del modello viene visualizzato tramite il tag <ul>.
Si ricorda che la funzione JS [calculer] all’origine del [Post] nell’azione server [Action05FaireCalcul] inserirà questo flusso HTML nella regione con ID [content]. Il risultato è simile al seguente:
![]() |
Continuiamo l'analisi del codice dell'azione [Action05FaireCalcul]:
[HttpPost]
public PartialViewResult Action05FaireCalcul(FormCollection postedData, SessionModel session)
{
// modello
ViewModel05 modèle = new ViewModel05();
...
// si inseriscono i valori di A e B nella sessione
session.A = modèle.A;
session.B = modèle.B;
// nessun errore per il momento
List<string> erreurs = new List<string>();
// una volta su due, si simula un errore
int val = session.Randomizer.Next(2);
if (val == 0)
{
erreurs.Add("[erreur aléatoire]");
}
if (erreurs.Count != 0)
{
modèle.Erreurs = erreurs;
return PartialView("Failure05", modèle);
}
// calcoli
double A = double.Parse(modèle.A);
double B = double.Parse(modèle.B);
modèle.AplusB = string.Format("{0}", A + B);
modèle.AmoinsB = string.Format("{0}", A - B);
modèle.AmultipliéparB = string.Format("{0}", A * B);
modèle.AdiviséparB = string.Format("{0}", A / B);
// visualizzazione
return PartialView("Success05", modèle);
}
- riga 7: il modello è stato dichiarato valido;
- righe 8-9: si memorizzano nella sessione i valori inseriti A e B. Si vuole poterli ritrovare nella query che seguirà;
- righe 11-22: si genera un errore in modo casuale una volta su due;
- righe 24-29: si eseguono le quattro operazioni aritmetiche sui numeri reali inseriti;
- riga 31: si restituisce la vista parziale [Success05] con il relativo modello. Questa vista parziale è la seguente:
@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h4>Résultats</h4>
<p>A+B=@Model.AplusB</p>
<p>A-B=@Model.AmoinsB</p>
<p>A*B=@Model.AmultipliéparB</p>
<p>A/B=@Model.AdiviséparB</p>
<p>
<a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>
Si ricorda che la funzione JS [calculer] all’origine di [Post] nell'azione server [Action05FaireCalcul] inserirà questo flusso HTML nella regione con ID [content]. Il risultato è simile al seguente:
![]() |
7.6.6. L'azione client [Effacer]
Il link JavaScript [Effacer] consente di riportare il modulo allo stato iniziale:
![]() | ![]() |
Nel modulo, il link JS [Effacer] è definito come segue:
<a href="javascript:effacer()">Effacer</a>
La funzione JS [effacer] è definita nel file [myScripts-05.js] nel modo seguente:
// dati globali
var content;
var loading;
function calculer() {
...
}
function retourSaisies() {
...
}
function effacer() {
// prima i riferimenti su DOM
var formulaire = $("#formulaire");
var A = $("#A");
var B = $("#B");
// si assegnano valori validi ai campi inseriti
A.val("0");
B.val("0");
// quindi si convalida il modulo per far scomparire
// eventuali messaggi di errore
formulaire.validate().form();
// quindi si assegnano stringhe vuote ai campi di immissione
A.val("");
B.val("");
}
// al caricamento del documento
$(document).ready(function () {
// si recuperano i riferimenti dei vari componenti della pagina
loading = $("#loading");
content = $("#content");
// si nasconde l'immagine animata
loading.hide();
});
- righe 15-17: si recuperano i riferimenti a vari elementi del DOM (Document Object Model);
- righe 19-20: si inseriscono valori validi nei campi di immissione dei numeri A e B;
- riga 23: si eseguono i validatori lato client. Poiché i valori di A e B sono validi, ciò farà scomparire eventuali messaggi di errore che potrebbero essere visualizzati;
- righe 25-26: si inseriscono stringhe vuote nei campi di immissione dei numeri A e B;
7.6.7. L'azione client [Retour aux Saisies]
Il link JavaScript [Retour aux Saisies] consente di tornare al modulo dopo aver ottenuto i risultati:
![]() | ![]() |
Nel modulo, il link JS [Retour aux Saisies] è definito come segue:
<a href="javascript:retourSaisies()">Retour aux saisies</a>
La funzione JS [retourSaisies] è definita nel file [myScripts-05.js] nel modo seguente:
// dati globali
var content;
var loading;
function calculer() {
...
}
function retourSaisies() {
// si effettua manualmente una chiamata Ajax
$.ajax({
url: '/Premier/Action05RetourSaisies',
type: 'POST',
dataType: 'html',
beforeSend: function () {
loading.show();
},
success: function (data) {
content.html(data);
},
complete: function () {
loading.hide();
// IMPORTANT !! convalida
$.validator.unobtrusive.parse($("#formulaire"));
},
error: function (jqXHR) {
content.html(jqXHR.responseText);
}
})
}
function effacer() {
...
}
// al caricamento del documento
$(document).ready(function () {
// si recuperano i riferimenti dei vari componenti della pagina
loading = $("#loading");
content = $("#content");
// si nasconde l'immagine animata
loading.hide();
});
- righe 11-29: una chiamata Ajax;
- riga 12: la funzione di destinazione URL;
- riga 13: verrà richiesta tramite un comando HTTP POST. Si tratta di un POST senza parametri inviati. Per questo motivo non è presente una riga del tipo:
nella chiamata Ajax;
- riga 14: il flusso atteso dal server è un flusso HTML;
- righe 18-20: questo flusso HTML servirà ad aggiornare la regione con id [content];
L'azione del server [Action05RetourSaisies] è la seguente:
[HttpPost]
public PartialViewResult Action05RetourSaisies(SessionModel session)
{
// visualizzazione
return PartialView("Formulaire05", new ViewModel05() { A = session.A, B = session.B });
}
- riga 2: l’azione riceve come parametro il modello della sessione in cui abbiamo precedentemente memorizzato i valori di A e B inseriti;
- riga 5: si restituisce la vista parziale [Formulaire05] con un modello di tipo [ViewModel05] in cui ci si assicura di inizializzare i campi A e B con i valori di A e B prelevati dalla sessione;
Ora torniamo al codice della funzione JavaScript [retourSaisies]:
function retourSaisies() {
// si effettua manualmente una richiesta Ajax
$.ajax({
url: '/Premier/Action05RetourSaisies',
type: 'POST',
dataType: 'html',
beforeSend: function () {
loading.show();
},
success: function (data) {
content.html(data);
},
complete: function () {
loading.hide();
// IMPORTANT !! convalida
$.validator.unobtrusive.parse($("#formulaire"));
},
error: function (jqXHR) {
content.html(jqXHR.responseText);
}
})
}
- riga 13: il metodo eseguito al termine della chiamata Ajax;
- riga 14: l’immagine animata di attesa viene nascosta;
- riga 16: un'istruzione un po' oscura per me, trovata su Internet per risolvere il seguente problema: nel modulo visualizzato dal link [Retour aux saisies], i validatori lato client non funzionavano più. Cercando informazioni sulla libreria JS [jquery.unobtrusive-ajax], ho trovato la soluzione riportata alla riga 16. Essa analizza il modulo, forse per attivare i validatori lato client.
7.7. Rendere accessibile su Internet un’applicazione ASP.NET
Vedere il paragrafo 9.26.
7.8. Generazione di un’applicazione nativa per Android a partire da un’applicazione a pagina singola APU
Vedere il paragrafo 9.27.

















































