7. Ajaxificare un'applicazione ASP.NET MVC
7.1. Il ruolo di AJAX in un'applicazione web
Per ora, gli esempi didattici che abbiamo studiato hanno la seguente architettura:
![]() |
Per passare da una vista [View1] a una vista [View2], il browser:
- invia una richiesta all'applicazione web;
- riceve la vista [View2] e la visualizza al posto della vista [View1].
Questo è il modello classico:
- richiesta dal browser;
- il server web genera una vista in risposta al client;
- visualizzazione di questa nuova vista da parte del browser.
Esiste un'altra modalità di interazione tra il browser e il server web: AJAX (Asynchronous JavaScript and XML). Ciò comporta interazioni tra la vista visualizzata dal browser e il server web. Il browser continua a fare ciò che sa fare meglio — visualizzare una vista HTML — ma ora è controllato da JavaScript incorporato nella vista HTML visualizzata. Il processo è il seguente:
![]() |
- in [1], si verifica un evento sulla pagina visualizzata nel browser (clic su un pulsante, modifica del testo, ecc.). 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 essere bloccato in attesa della risposta HTTP. La richiesta segue il flusso di elaborazione standard. Nulla (o quasi nulla) la distingue da una richiesta standard;
- In [3], viene inviata una risposta al client JavaScript. Piuttosto che una vista HTML completa, in genere viene inviata una vista HTML parziale, un feed XML o JSON (JavaScript Object Notation);
- In [4], JavaScript recupera questa risposta e la utilizza per aggiornare una regione della pagina HTML visualizzata.
Per l'utente, la visualizzazione cambia perché ciò che vede è diverso. Tuttavia, non si verifica un ricaricamento completo della pagina; al contrario, si ha solo una modifica parziale della pagina visualizzata. Ciò contribuisce a rendere la pagina più fluida e interattiva: poiché non c'è un ricaricamento completo della pagina, possiamo gestire eventi che prima non era possibile gestire. Ad esempio, offrire all'utente un elenco di opzioni mentre digita i caratteri in un campo di immissione. Ad ogni nuovo carattere digitato, viene inviata una richiesta AJAX al server, che restituisce ulteriori suggerimenti. Senza AJAX, questo tipo di assistenza all'immissione era in precedenza impossibile. Non potevamo ricaricare una nuova pagina ad ogni carattere digitato.
7.2. Nozioni di base su jQuery e JavaScript
Abbiamo spesso incluso la libreria JavaScript jQuery nelle nostre pagine. Ad esempio, troverete la seguente riga:
<script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
Nota: abbina la versione di jQuery alla tua versione di Visual Studio.
La tecnologia Ajax di ASP.NET MVC utilizza jQuery. Scriveremo noi stessi alcuni script jQuery. Vediamo quindi le nozioni di base su jQuery necessarie per comprendere gli script di questo capitolo.
Creiamo un nuovo progetto [Example-04] all'interno della nostra soluzione [Examples]:
![]() |
Per utilizzare Ajax con ASP.NET MVC, nel file di configurazione [Web.config] deve essere presente la seguente riga [1]:
<appSettings>
...
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
La riga 3 abilita l'uso 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 (modificare la versione in base alla propria versione di Visual Studio);
- Righe 10–12: un elemento della pagina con l'ID [element1]. Lavoreremo con questo elemento.
Visualizza questo file nel browser Google Chrome [4] e [5]:
![]() |
In Google Chrome, premi [Ctrl-Shift-I] per aprire gli strumenti di sviluppo [6]. La scheda [Console] [7] ti permette di eseguire codice JavaScript. Di seguito, forniamo i comandi JavaScript da digitare e spieghiamo cosa fanno.
JS | risultato |
|
: restituisce l'insieme di tutti gli elementi con l'ID [element1], quindi normalmente un insieme di 0 o 1 elemento poiché non è possibile avere due ID identici in una pagina HTML. | ![]() |
|
: imposta il testo [blabla] per tutti gli elementi della raccolta. Questo modifica il contenuto visualizzato sulla pagina | ![]() |
|
nasconde gli elementi della raccolta. Il testo [blabla] non viene più visualizzato. | ![]() |
|
: visualizza nuovamente la collezione. Questo ci permette di vedere che l'elemento con l'ID [element1] ha l'attributo CSS style='display: none;', il che fa sì che l'elemento sia nascosto. | |
|
: visualizza gli elementi della collezione. Il testo [blabla] appare di nuovo. L'attributo CSS style='display: block;' è responsabile di questa visualizzazione. | ![]() |
|
: imposta un attributo su 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 l'URL del browser non è cambiato durante tutte queste operazioni. Non c'è stata alcuna comunicazione 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 le modifiche apportate all'elemento nelle righe 10–12. È importante tenerlo presente durante il debug di JavaScript. In questi casi, spesso non è necessario visualizzare il codice sorgente della pagina visualizzata. Per visualizzare il codice sorgente della pagina attualmente visualizzata, procedere come segue:

Ora sappiamo abbastanza per comprendere gli script JS che seguono.
7.3. Aggiornamento di una pagina con un feed HTML
7.3.1. Le viste
Esamineremo la seguente applicazione:
![]() |
- in [1], il tempo di caricamento della pagina;
- in [2], eseguiamo le quattro operazioni aritmetiche su due numeri reali A e B;
- in [3], la risposta del server viene visualizzata in una regione della pagina;
- in [4], il tempo di calcolo. Questo è diverso dal tempo di caricamento della pagina [5]. Quest'ultimo è uguale a [1], il che dimostra che l'area [6] non è stata ricaricata. Inoltre, l'URL della pagina [7] non è cambiato.
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: istanziamento del modello di vista;
- riga 5: inizializzazione del tempo di caricamento della vista;
- riga 6: visualizzazione della vista [Action10Get.cshtml] e del suo 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
{
// form
[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; }
// results
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 dal modulo;
- righe 15–18: il valore B dal modulo;
- righe 21–24: i risultati delle quattro operazioni aritmetiche su A e B;
- riga 25: il testo di eventuali errori;
- riga 26: l'ora in cui la vista è stata caricata nel browser;
- riga 27: l'ora in cui sono stati calcolati i campi nelle righe 21–24;
- riga 7: questo modello di vista è anche un modello di azione. I campi che non vengono inviati dal browser sono esclusi da quest'ultimo.
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 si basa su un tipo [ViewModel01];
- riga 21: jQuery è necessario sia per la convalida 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: visualizza il tempo di caricamento della vista;
- riga 35: un modulo Ajax — ci torneremo più avanti;
- righe 40–41: etichette per i campi di immissione dei numeri A e B;
- righe 46–47: campi di immissione per i numeri A e B;
- righe 50-51: messaggi di errore per gli input numerici A e B;
- riga 56: il pulsante che invia il modulo. L'invio avverrà tramite una richiesta Ajax;
- riga 57: un'immagine di caricamento visualizzata durante la richiesta Ajax;
- riga 58: un link per inviare il modulo con una richiesta Ajax;
- riga 62: un tag div con l'id [results]. Qui inseriremo l'output HTML restituito dal server web.
Questa vista visualizza la seguente pagina:

Ora esaminiamo il codice che gestisce la funzionalità Ajax del modulo:
...
@{
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], utilizziamo [@Ajax.BeginForm]. Questo metodo supporta molti 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à la richiesta POST del modulo,
null: non ci sono informazioni sul percorso da fornire,
ajaxOpts: le opzioni per la chiamata Ajax. Sono state definite alle righe 6–10,
new { id = "form" }: per assegnare l'attributo [id='form'] al tag <form> generato;
Le opzioni Ajax utilizzate sono le seguenti:
- riga 8: l'URL di destinazione della richiesta HTTP Ajax;
- riga 7: metodo della richiesta HTTP Ajax;
- riga 6: ID dell'area della pagina che verrà aggiornata dalla risposta alla richiesta Ajax;
- riga 9: l'ID della regione della pagina che verrà visualizzata durante la richiesta Ajax, solitamente un'immagine di caricamento. In questo caso, verrà visualizzata la riga 20. Contiene un'immagine animata che simboleggia lo stato di caricamento. Inizialmente, questa immagine è nascosta dallo stile [display: none];
- riga 10: tempo di attesa in millisecondi prima che venga visualizzata l'immagine animata, 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. Notare gli attributi [data-ajax-attr], che riflettono i valori dei campi nell'oggetto [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>
Questo è un modulo HTML standard. Questo codice verrà eseguito se l'utente disabilita JavaScript nel proprio browser. Le righe 5–6 non verranno quindi utilizzate.
7.3.3. L'azione [Action01Post]
L'azione [Action01Post] che gestisce la richiesta HTTP Ajax è la seguente:
[HttpPost]
public PartialViewResult Action01Post(FormCollection postedData, SessionModel session)
{
// wait simulation
Thread.Sleep(2000);
// action model instantiation
ViewModel01 modèle = new ViewModel01();
// calculation time
modèle.HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
// model update
TryUpdateModel(modèle, postedData);
if (!ModelState.IsValid)
{
// returns an error
modèle.Erreur = getErrorMessagesFor(ModelState);
return PartialView("Action01Error", modèle);
}
// every other time, an error is simulated
int val = session.Randomizer.Next(2);
if (val == 0)
{
modèle.Erreur = "[erreur aléatoire]";
return PartialView("Action01Error", modèle);
}
// calculations
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);
// view
return PartialView("Action01Success", modèle);
}
- riga 1: l'azione gestisce solo un [POST];
- riga 2: accetta il seguente modello di azione:
- [FormCollection postedData]: l'insieme dei valori inviati dalla richiesta POST Ajax,
- [SessionModel session]: gli elementi della sessione. Qui utilizziamo una tecnica descritta nella sezione 4.10;
- riga 2: l'azione restituirà un frammento HTML anziché una pagina HTML completa;
- riga 5: artificialmente, mettiamo in pausa per due secondi per simulare un'azione Ajax lunga;
- riga 7: viene istanziato un modello di tipo [ViewModel01];
- riga 9: il tempo di calcolo viene inizializzato;
- riga 11: tentiamo di aggiornare il modello [ViewModel01] con i valori inviati. Ricordiamo che ce ne sono due: i valori dei numeri A e B;
- riga 12: verifichiamo se l'aggiornamento è andato a buon fine;
- riga 15: se si verifica un errore, viene popolato il campo [Error] del modello;
- riga 16: viene renderizzata una vista parziale [Action01Error.cshtml] che utilizza il modello [ViewModel01];
- righe 19–24: a intervalli regolari, viene simulato un errore;
- riga 19: viene generato un numero intero casuale nell'intervallo [0,1]. Il generatore di numeri casuali viene prelevato dalla sessione;
- riga 20: se il valore generato è 0, viene simulato un errore;
- riga 22: il messaggio di errore viene inserito nel modello;
- riga 23: viene visualizzata una vista parziale [Action01Error.cshtml] che utilizza il modello [ViewModel01];
- righe 26–29: vengono eseguiti calcoli aritmetici sui numeri A e B e i risultati vengono inseriti nel modello di vista come stringhe;
- riga 31: viene visualizzata una vista parziale [Action01Success.cshtml] utilizzando 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 noti che questo frammento di codice HTML verrà inviato in risposta alla richiesta HTTP Ajax di tipo POST e inserito nella pagina nell'area con l'ID [results]. 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 un 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>
Ancora una volta, questo flusso HTML parziale verrà inviato in risposta alla richiesta HTTP Ajax di tipo POST e inserito nella pagina nell'area con l'ID [results]:

7.3.6. G Gestione delle sessioni in
Abbiamo visto che [Action01Post] utilizza la sessione. Il modello di sessione è del seguente tipo [SessionModel]:
using System;
namespace Exemple_03.Models
{
public class SessionModel
{
public Random Randomizer { get; set; }
}
}
La sessione viene inizializzata in [Global.asax]:
// Session
protected void Session_Start()
{
SessionModel sessionModel=new SessionModel();
sessionModel.Randomizer=new Random(DateTime.Now.Millisecond);
Session["data"] = sessionModel;
}
La sessione è associata a un modello in [Application_Start]:
protected void Application_Start()
{
...
// model binders
ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
}
La classe [SessionModelBinder] è stata descritta.
7.3.7. Gestione dell'immagine segnaposto
@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 la richiesta Ajax ha inizio, la regione con id [loading] alla riga 7 viene visualizzata dopo un secondo [riga 8]. Questa regione è l'immagine della riga 21, che inizialmente era nascosta. Il risultato è la seguente interfaccia:
![]() |
7.3.8. Gestione del link [Calculate]
Esaminiamo il link [Calculate] nella 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 [Calcola] si avvia l'esecuzione della funzione JS [postForm]. Questa funzione è definita nel file [myScripts-01.js] alla riga 5. Lo script è il seguente:
![]() |
function postForm() {
// on fait un appel Ajax à la main avec JQuery
var loading = $("#loading");
var formulaire = $("#formulaire");
var résultats = $('#results');
$.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) {
//Use the Globalization plugin to parse the value
var val = Globalize.parseFloat(value);
return this.optional(element) || (
val >= param[0] && val <= param[1]);
}
});
Le funzioni alle righe 19–37 sono già state illustrate nella Sezione 6.1. Si occupano dell'internazionalizzazione delle pagine. Non le riprenderemo in questa sede. Alle righe 1–17, effettuiamo manualmente la chiamata Ajax, che nel caso del pulsante [Calcola] era precedentemente gestita dalla libreria Ajax del progetto. A tal fine, utilizziamo la libreria jQuery del progetto.
- Riga 3: un riferimento al componente con l'ID [loading]. [$("#loading")] restituisce l'insieme degli elementi con l'ID [loading]. Ce n'è solo uno;
- Riga 4: un riferimento al componente con l'ID [form];
- riga 5: un riferimento al componente con l'ID [results];
- Riga 6: la chiamata Ajax con le relative opzioni;
- riga 7: l'URL di destinazione della chiamata Ajax;
- riga 8: il metodo HTTP utilizzato;
- riga 9: i dati inviati. [form.serialize] crea la stringa POST [A=val1&B=val2] per il modulo con ID [form];
- riga 10: il tipo di dati previsto nella risposta. Sappiamo che il server restituirà un flusso HTML;
- riga 11: il metodo da eseguire all'avvio della richiesta. Qui specifichiamo che deve essere visualizzato il componente con l'ID [loading]. Si tratta dell'immagine animata di caricamento;
- riga 12: il metodo da eseguire se la richiesta Ajax ha esito positivo. Il parametro [data] è la risposta completa dal server. Sappiamo che si tratta di un flusso HTML;
- riga 13: nascondiamo l'indicatore di caricamento;
- riga 14: aggiorniamo il componente con l'ID [results] con l'HTML del parametro [data].
Il lettore è invitato a provare il link [Calculate]. Funziona esattamente come il pulsante [Calculate], con l'eccezione del messaggio di errore " ." Una volta utilizzato questo link, è possibile inserire valori non validi per A e B:
![]() |
- in [1] e [2], abbiamo inserito valori non validi. Questi vengono segnalati dai validatori lato client;
- in [3], abbiamo cliccato sul link [Calcola];
- in [4], si è verificato un [POST] poiché riceviamo la risposta [4].
Quando i valori non sono validi e clicchiamo sul pulsante [Calcola], la richiesta [POST] al server non viene inviata. Nello stesso scenario, l'utilizzo del link [Calcola] attiva la richiesta [POST] al server. C'è quindi un comportamento del pulsante [Calcola] che non siamo riusciti a riprodurre con il link [Calcola]. Piuttosto che cercare di risolvere questo problema ora, lo lasceremo per un esempio successivo che illustrerà anche un altro problema di validazione lato client.
7.4. Aggiornamento di una pagina HTML con un feed JSON
Nell'esempio precedente, il server web ha risposto alla richiesta HTTP Ajax con un flusso HTML. Questo flusso conteneva dati accompagnati da formattazione HTML. Riprenderemo l'esempio precedente, questa volta utilizzando risposte JSON (JavaScript Object Notation) che contengono solo i dati. Il vantaggio è che vengono trasmessi meno byte.
7.4.1. L'azione [Action02Get]
L'azione [Action02Get] sarà il punto di ingresso per la 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 se la richiesta fallisce;
- riga 12: la funzione JS da eseguire se la richiesta ha esito positivo;
- riga 13: la funzione JS da eseguire dopo che la richiesta Ajax ha restituito il suo risultato (fallimento o successo);
- righe 40–43: una regione con l'ID [header];
- righe 44–49: una regione con l'ID [results]. Visualizzerà i risultati delle quattro operazioni aritmetiche;
- righe 50–52: una regione con l'ID [error]. Visualizzerà eventuali messaggi 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)
{
// wait simulation
Thread.Sleep(2000);
// model validation
ViewModel02 modèle = new ViewModel02();
// loading and calculation times
string HeureChargement = DateTime.Now.ToString("hh:mm:ss");
string HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
// model update
TryUpdateModel(modèle, postedData);
if (!ModelState.IsValid)
{
// returns an error
return Json(new { Erreur = getErrorMessagesFor(ModelState), HeureCalcul = HeureCalcul });
}
// every other time, an error is simulated
int val = session.Randomizer.Next(2);
if (val == 0)
{
// returns an error
return Json(new { Erreur = "[erreur aléatoire]", HeureCalcul = HeureCalcul });
}
// calculations
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);
// we return the results
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 testo in formato JSON;
- riga 16: le informazioni vengono restituite come istanza di classe anonima serializzata in JSON. Il metodo [getErrorMessagesFor] è già stato presentato. La stringa JSON inviata al browser avrà la seguente forma:
- riga 31: stesso approccio per i risultati aritmetici. Questa volta, 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
Esaminiamo la configurazione della chiamata Ajax nella pagina HTML inviata al browser del 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]:
// global data
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() {
...
}
// document loading
$(document).ready(function () {
formulaire = $("#formulaire");
entete = $("#entete");
loading = $("#loading");
erreur = $("#erreur");
résultats = $('#résultats');
heureCalcul = $("#heureCalcul");
msg = $("#msg");
AplusB = $("#AplusB");
AmoinsB = $("#AmoinsB");
AmultipliéparB = $("#AmultipliéparB");
AdiviséparB = $("#AdiviséparB");
// hide certain page elements
entete.hide();
résultats.hide();
erreur.hide();
});
// start
function OnBegin() {
....
}
// end of query
function OnComplete() {
...
}
// success
function OnSuccess(data) {
....
}
// error
function OnFailure(request, error) {
...
}
- riga 19: la funzione JS eseguita quando la pagina finisce di caricarsi nel browser;
- righe 20–30: recuperiamo i riferimenti di tutti i componenti della pagina che ci interessano. La ricerca di un componente su una pagina comporta un costo, quindi è meglio farlo una sola volta;
- righe 33–35: i componenti [header], [results] e [loading] vengono nascosti;
Quando la richiesta Ajax ha inizio, viene eseguita la seguente funzione:
// start
function OnBegin() {
// wait signal on
loading.show();
// hide certain page elements
entete.hide();
résultats.hide();
erreur.hide();
}
- riga 4: viene visualizzato il componente [loading]. Si tratta dell'immagine animata;
- righe 6–8: i componenti [header], [results] ed [error] vengono nascosti;
Se la richiesta Ajax va a buon fine, viene eseguito il seguente codice JS:
// réussite
function OnSuccess(data) {
// affichage résultats
heureCalcul.text(data.HeureCalcul);
entete.show();
if (data.Erreur != '') {
msg.text(data.Erreur);
erreur.show();
return;
}
// pas d'erreur
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 le due stringhe JSON che possono essere inviate 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 [Error] si ottiene utilizzando la notazione [data.Error] o [data["Error"]], a seconda delle preferenze. Lo stesso vale per gli altri campi nella stringa JSON. Inoltre, per assegnare del testo non formattato a un componente con ID X, scriviamo [X.text(string)]. Torniamo al codice della funzione [OnSuccess]:
- riga 2: [data] è la stringa JSON ricevuta;
- riga 4: il componente [calculationTime] riceve il suo valore;
- riga 5: viene visualizzato il componente [entete];
- riga 6: controlla il campo [Error] della stringa JSON;
- riga 7: il componente [msg] riceve il suo valore;
- riga 8: viene visualizzato il componente [error];
- riga 9: questo è tutto per il caso di errore;
- riga 12: il componente [AplusB] riceve il suo valore;
- riga 13: il componente [AminusB] riceve il suo valore;
- riga 14: il componente [AmultipliedbyB] riceve il suo valore;
- riga 15: al componente [AdividedbyB] viene assegnato un valore;
- riga 16: viene visualizzato il componente [risultati].
La funzione [ OnFailure] verrà eseguita se la richiesta HTTP Ajax fallisce. Questo errore è determinato dal codice di stato HTTP restituito dal server. Ad esempio, un codice 500 [Internal Server Error] indica che il server non è stato in grado di elaborare la richiesta. La funzione [OnFailure] è la seguente:
// error
function OnFailure(request, error) {
alert("L'erreur suivante s'est produite :" + error);
}
Visualizziamo semplicemente una finestra di dialogo con l'errore che si è verificato. In pratica, dovremmo essere più specifici. Proporremo presto un'altra soluzione.
Infine, la funzione [OnComplete] viene eseguita al completamento della richiesta, indipendentemente dal fatto che abbia avuto esito positivo o negativo.
// end of query
function OnComplete() {
// wait signal off
loading.hide();
}
Si noti che è la configurazione della chiamata Ajax nella vista [Action02Get.cshtml] a determinare la chiamata di queste varie funzioni:
AjaxOptions ajaxOpts = new AjaxOptions
{
...
OnBegin = "OnBegin",
OnFailure = "OnFailure",
OnSuccess = "OnSuccess",
OnComplete = "OnComplete"
};
7.4.4. Il collegamento [Calculate]
Il codice HTML per il collegamento [Calculate] 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() {
// make a manual Ajax call with JQuery
$.ajax({
url: '/Premier/Action02Post',
type: 'POST',
data: formulaire.serialize(),
dataType: 'json',
beforeSend: OnBegin,
success: OnSuccess,
error: OnFailure,
complete: OnComplete
})
}
Abbiamo già incontrato un codice simile.
- riga 4: URL di destinazione della chiamata Ajax;
- riga 5: metodo HTTP utilizzato dalla chiamata Ajax;
- riga 6: valori inviati. Questi sono il risultato della serializzazione dei valori del modulo. Il modulo, identificato dall'id [form], è referenziato dalla variabile [form]. [data] sarà una stringa nel formato [A=val1&B=val2];
- riga 7: tipo di formato di risposta previsto. 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 ha esito positivo;
- riga 10: funzione JS da eseguire se la chiamata Ajax fallisce;
- riga 11: funzione JS da eseguire una volta ricevuta la risposta del server, indipendentemente dal fatto che si tratti di un successo o di un errore.
Rivediamo la funzione JavaScript che gestisce il caso in cui la chiamata Ajax fallisca (riga 10). La chiamata Ajax fallisce in varie 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:
// error
function OnFailure(request, error) {
alert("L'erreur suivante s'est produite :" + error);
}
In genere, la visualizzazione dell'oggetto [error] non fornisce alcuna informazione utile. Se si utilizza una chiamata Ajax effettuata con jQuery, è possibile utilizzare il seguente metodo [OnFailure];
// error
function OnFailure(jqXHR) {
alert("Erreur : " + jqXHR.status + " " + jqXHR.statusText);
msg.html(jqXHR.responseText);
erreur.show();
}
L'oggetto jQuery [jqXHR] ha le seguenti proprietà:
- 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: visualizziamo il codice di errore e il messaggio corrispondente;
- Riga 4: memorizziamo la risposta HTML del server nel componente con l'ID [msg];
- Riga 5: visualizziamo la regione con l'ID [error].
Per testare questa gestione degli errori, creeremo artificialmente un'eccezione nell'azione [Action02Post]:
[HttpPost]
public JsonResult Action02Post(FormCollection postedData, SessionModel session)
{
// an artificial exception to test the Ajax call error function
throw new Exception();
// wait simulation
Thread.Sleep(2000);
// model validation
...
La riga 5 genera un'eccezione. Ora proviamo l'applicazione:
![]() |
Otteniamo la seguente risposta [1] e [2]:
![]() |
La risposta del server ci permette di vedere in quale riga del codice del server si è verificato l'errore. Spesso è utile conoscere questa informazione. D'ora in poi, useremo questa tecnica per gestire gli errori nelle chiamate Ajax.
7.5. Applicazione web a pagina singola
La tecnologia Ajax ci permette di creare applicazioni a pagina singola:
- la prima pagina viene caricata tramite una richiesta standard del browser;
- le pagine successive vengono ottenute tramite chiamate Ajax. Di conseguenza, il browser non cambia mai il proprio URL e non carica mai una nuova pagina. Questo tipo di applicazione è chiamata Applicazione a Pagina Singola (SPA).
Ecco un esempio di base di tale applicazione. La nuova applicazione avrà due viste:
![]() |
![]() |
- in [1], l'azione [Action03Get] visualizza 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], l'URL non è cambiato. La pagina visualizzata è la pagina 1.
Il codice per l'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 l'ID [content]. Questo è l'elemento in cui verranno visualizzate le diverse pagine;
- riga 17: per impostazione predefinita, la pagina [Page1.cshtml] verrà visualizzata per prima.
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:
- l'etichetta del link [Pagina 2];
- l'azione di destinazione del link [Action04];
- i parametri dell'URL richiesto. Questo sarà [/Premier/Action04?Page=2];
- le opzioni della chiamata Ajax. Qui, solo l'ID della regione da aggiornare con la risposta del server. Per le altre opzioni, vengono utilizzati i valori predefiniti quando esistono. Il metodo HTTP predefinito è GET.
Vediamo cosa succede quando si clicca sul link. L'URL [/Premier/Action04?Page=2] viene richiesto tramite una richiesta 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 utilizza la stringa [page] come modello. Sappiamo che l'URL contiene questa informazione: [/Premier/Action04?Page=2]. Si noti che il modello non fa distinzione tra maiuscole e minuscole;
- righe 4–8: [view] riceverà il valore [Page2];
- riga 9: viene visualizzata 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 richiesta GET Ajax [/Premier/Action04?Page=2]. Ricordiamo che questa richiesta Ajax utilizza questa risposta per aggiornare la regione con id [content] (riga 3 di seguito):
<h4>Page 1</h4>
<p>
@Ajax.ActionLink("Page 2", "Action04", new { Page = 2 }, new AjaxOptions() { UpdateTargetId = "content" })
</p>
Questo porta alla seguente nuova visualizzazione [1]:
![]() |
Seguendo la stessa logica, vediamo che cliccando sul link [Pagina 1] in [1] verrà visualizzata [2].
Torniamo allo schema generale di un'applicazione ASP.NET MVC:
![]() |
Grazie al JavaScript incorporato nelle pagine HTML ed eseguito nel browser, possiamo trasferire il codice al browser e ottenere la seguente architettura:
![]() |
- in [1], il livello web ASP.NET MVC è diventato un'interfaccia web per l'accesso ai dati, tipicamente memorizzati in un database. Le viste restituite contengono solo dati e nessun markup HTML, come feed 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 viste statiche vengono poi arricchite con i dati ottenuti da JavaScript dall'interfaccia web [1];
- Il codice JavaScript incorporato nelle pagine HTML può essere strutturato in livelli:
- il livello [presentazione] gestisce le interazioni dell'utente,
- il livello [DAO] gestisce l'accesso ai dati tramite il server web [1],
- il livello [logica di business] corrisponde al livello [logica di business] che in precedenza si trovava sul server [1] ed è stato spostato sul browser [2];
Il vantaggio di questa architettura è che attinge a diverse competenze:
- il codice del server web [1] richiede competenze .NET ma non competenze JavaScript, HTML o CSS;
- il codice incorporato nel browser [2] richiede competenze in JavaScript, HTML e CSS, ma è indipendente dalla tecnologia del server web [1].
Pertanto, questa architettura facilita il lavoro in parallelo da parte di team con competenze diverse. È particolarmente adatta 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. Ecco un riepilogo del contesto:
![]() |
- in [1] e [2] sono stati inseriti valori non validi. Questi vengono segnalati dai validatori lato client;
- in [3], abbiamo cliccato sul link [Calcola];
- in [4], si è verificato un [POST] poiché riceviamo la risposta [4].
Quando i valori non sono validi e si fa clic sul pulsante [Calcola], la richiesta [POST] al server non viene inviata. Nello stesso scenario, fare clic sul link [Calcola] attiva la richiesta [POST] al server. Pertanto, esiste un comportamento del pulsante [Calcola] che non siamo riusciti a riprodurre utilizzando il link [Calcola].
Riesamineremo questo esempio in un nuovo contesto: l'applicazione avrà più viste e sarà del tipo [Applicazione a pagina singola] che abbiamo appena descritto.
7.6.1. Le viste nell'esempio
L'esempio presenta diverse viste:
![]() |
- in [1], la vista [Action05Get];
- in [2], la vista parziale [Form05];
- in [3], la vista parziale [Failure05];
![]() |
- in [4], la vista parziale [Success05].
L'applicazione è un'applicazione a pagina singola: la pagina viene caricata dal browser durante la 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 prega di notare i seguenti punti:
- riga 1: il modello di visualizzazione è di tipo [ViewModel05], di cui parleremo tra poco;
- righe 13–19: contengono gli script JavaScript necessari per Ajax e la convalida lato client;
- riga 20: aggiungeremo le nostre funzioni JavaScript in [myScripts-05.js];
- riga 27: l'immagine di caricamento animata;
- righe 28–30: un tag `id` [content]. Qui verranno inserite le viste parziali [Form05, Success05, Failure05];
- riga 29: inserimento della vista parziale [Form05].
La vista [Action05Get] è responsabile della visualizzazione della sezione [1] della pagina iniziale:
![]() |
La vista parziale [Formulaire05] genererà la sezione [2] sopra riportata. 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 un tipo [ViewModel05] come modello;
- 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 nel proprio browser. Qui ignoriamo questa possibilità. Il quarto parametro è importante. Il modulo avrà l'ID [form];
- righe 5–22: il modulo per l'inserimento dei numeri A e B;
- riga 27: un link JavaScript che attiva 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 associati.
Si noti che il modulo non ha un pulsante [submit]. Dovremo inviare manualmente [Post] i valori inseriti di A e B.
Se non ci sono errori, vengono visualizzati i risultati:
![]() |
La sezione [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 di vista
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
{
// form
[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; }
// results
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; }
}
}
Questo è il modello [ViewModel01] già presentato, con alcune piccole modifiche:
- righe 15 e 19: i campi A e B sono ora di tipo [string], in modo che, quando il modulo di immissione viene visualizzato per la prima volta, vengano mostrati campi di immissione vuoti anziché campi con valore 0;
- righe 14 e 18: ciò non impedisce che il valore inserito venga convalidato utilizzando un validatore [Range];
- riga 26: un elenco di messaggi di errore visualizzati dalla vista [Failure05].
7.6.3. Dati nell'ambito [Session]
Nella Sezione 7.3.6, abbiamo visto che i dati di sessione erano incapsulati nel seguente modello [SessionModel]:
![]() |
using System;
namespace Exemple_03.Models
{
public class SessionModel
{
// the random number generator
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
{
// the random number generator
public Random Randomizer { get; set; }
// the values of A and B
public string A { get; set; }
public string B { get; set; }
}
}
È infatti necessario memorizzare i valori di A e B nella sessione, come mostrato nella seguente sequenza:
Richiesta 1
![]() |
Richiesta 2
![]() |
In [4] vediamo le voci inserite in [1]. Tuttavia, ci sono due richieste HTTP distinte. Sappiamo che la sessione funge da memoria tra due richieste HTTP. Affinché la seconda richiesta possa recuperare i valori inviati dalla prima, tali valori devono essere memorizzati nella sessione.
7.6.4. L'azione del server [Action05Get]
L'azione [Action05Get] è l'azione che visualizza la singola pagina iniziale. 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], di cui abbiamo già parlato, viene visualizzata con un modello di tipo [ViewModel05];
7.6.5. L'azione client [Calculate]
Esaminiamo le interazioni dell'utente con le viste:
![]() |
Il link [1] è un link JavaScript:
<a href="javascript:calculer()">Calculer</a>
La funzione JavaScript [calculate] si trova nel file [myScripts-05.js]:
<script type="text/javascript" src="~/Scripts/myScripts-05.js"></script>
Il codice per la funzione JavaScript [calculate] è il seguente:
// global data
var content;
var loading;
function calculer() {
// first the references on DOM
var formulaire = $("#formulaire");
// then validate the form
if (!formulaire.validate().form()) {
// invalid form - terminated
return;
}
// make a manual Ajax call
$.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) {
// display server response
content.html(jqXHR.responseText);
}
})
}
function retourSaisies() {
...
}
function effacer() {
...
}
// document loading
$(document).ready(function () {
// retrieve the references of the page's various components
loading = $("#loading");
content = $("#content");
// we hide the moving image
loading.hide();
});
- Si noti che il codice JavaScript viene sempre eseguito sul lato client, nel browser;
- riga 44: la funzione JS viene eseguita al completamento del caricamento iniziale della singola pagina;
- riga 46: riferimento all'immagine animata con id [loading];
- riga 47: riferimento alla regione con id [content]. Questa regione riceve le viste parziali [Form05, Success05, Failure05];
- righe 2-3: le variabili delle righe 46-47 sono dichiarate globali in modo che altre funzioni possano accedervi. La ricerca di elementi su una pagina comporta un costo (righe 46-47). Non è necessario ripetere questa ricerca se è possibile evitarlo;
- riga 5: la funzione [calculate];
- riga 7: viene recuperato un riferimento al modulo. La vista parziale [Form05] gli ha assegnato l'ID [form];
- Riga 9: questa istruzione esegue i validatori del modulo lato client. Questo è ciò che mancava nel problema identificato a pagina 176. Questo metodo è fornito dalla libreria [jquery.unobtrusive-ajax] utilizzata dall'applicazione a 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: la chiamata Ajax viene effettuata al server;
- riga 15: l'URL di destinazione è l'azione del 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: se la chiamata Ajax ha esito positivo, la funzione [calculate] aggiorna la regione con id [content] con lo stream HTML inviato dal server.
Questo flusso HTML è quello inviato dall'azione [Action05FaireCalcul] a cui è diretta la chiamata Ajax. Il codice per questa azione lato server è il seguente:
[HttpPost]
public PartialViewResult Action05FaireCalcul(FormCollection postedData, SessionModel session)
{
// model
ViewModel05 modèle = new ViewModel05();
// calculation time
modèle.HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
// model update
TryUpdateModel(modèle, postedData);
if (!ModelState.IsValid)
{
// returns an error
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 i valori inviati (postedData) e il modello di sessione (session) come parametri;
- 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 suoi validatori. Ci si potrebbe chiedere perché ci prendiamo questo disturbo quando i validatori lato client impediscono il POST se i dati inseriti non sono validi. In realtà, non siamo sicuri dell'origine del POST. Potrebbe essere stato effettuato da codice che non è nostro. Pertanto, dobbiamo sempre eseguire validazioni lato server;
- riga 10: verifichiamo se i validatori hanno avuto esito positivo;
- riga 13: se il modello non è valido, lo aggiorniamo con un elenco di errori. Non entreremo nei dettagli del metodo interno [getListOfMessagesFor], che è analogo al metodo [GetErrorMessagesFor] descritto a pagina 60;
- riga 14: la vista parziale [Failure05] viene visualizzata con il suo modello. Ecco il codice per 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 modulo viene visualizzato utilizzando un tag <ul>.
Ricordiamo che la funzione JS [calculate], che attiva l'azione [Post] verso il server [Action05FaireCalcul], inserirà questo output HTML nella regione con l'id [content]. Il risultato sarà simile a questo:
![]() |
Continuiamo ad esaminare il codice dell'azione [Action05FaireCalcul]:
[HttpPost]
public PartialViewResult Action05FaireCalcul(FormCollection postedData, SessionModel session)
{
// model
ViewModel05 modèle = new ViewModel05();
...
// we put the values of A and B in session
session.A = modèle.A;
session.B = modèle.B;
// no errors so far
List<string> erreurs = new List<string>();
// every other time, an error is simulated
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);
}
// calculations
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);
// view
return PartialView("Success05", modèle);
}
- riga 7: il modello è stato dichiarato valido;
- righe 8-9: i valori inseriti A e B vengono memorizzati nella sessione. Vogliamo poterli recuperare nella query seguente;
- righe 11–22: viene generato un errore in modo casuale una volta su due;
- righe 24–29: eseguiamo le quattro operazioni aritmetiche sui numeri reali inseriti;
- riga 31: viene restituita la vista parziale [Success05] con il suo 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>
Ricordate che la funzione JS [calculate], che attiva l'azione [Post] verso il server [Action05FaireCalcul], inserirà questo flusso HTML nella regione con l'id [content]. Il risultato sarà simile a questo:
![]() |
7.6.6. Il pulsante [Clear]
Il link JavaScript [Clear] riporta il modulo allo stato iniziale:


Nel modulo, il link JS [Cancella] è definito come segue:
<a href="javascript:effacer()">Effacer</a>
La funzione JS [clear] è definita nel file [myScripts-05.js] come segue:
// global data
var content;
var loading;
function calculer() {
...
}
function retourSaisies() {
...
}
function effacer() {
// first the references on DOM
var formulaire = $("#formulaire");
var A = $("#A");
var B = $("#B");
// valid values are assigned to entries
A.val("0");
B.val("0");
// then validate the form to make
// any error msg
formulaire.validate().form();
// then assign empty strings to the input fields
A.val("");
B.val("");
}
// document loading
$(document).ready(function () {
// retrieve the references of the page's various components
loading = $("#loading");
content = $("#content");
// we hide the moving image
loading.hide();
});
- righe 15-17: recuperiamo i riferimenti a vari elementi del DOM (Document Object Model);
- righe 19-20: impostiamo valori validi nei campi di immissione per i numeri A e B;
- riga 23: esegue i validatori lato client. Poiché i valori di A e B sono validi, questo rimuoverà eventuali messaggi di errore che potrebbero essere visualizzati;
- righe 25–26: vengono inserite stringhe vuote nei campi di immissione per i numeri A e B;
7.6.7. L'azione lato client [Torna ai campi di immissione]
Il link JavaScript [Torna ai campi di immissione] consente di tornare al modulo dopo aver ottenuto i risultati:


Nel modulo, il link JS [Torna all'input] è definito come segue:
<a href="javascript:retourSaisies()">Retour aux saisies</a>
La funzione JS [retourSaisies] è definita nel file [myScripts-05.js] come segue:
// global data
var content;
var loading;
function calculer() {
...
}
function retourSaisies() {
// make a manual Ajax call
$.ajax({
url: '/Premier/Action05RetourSaisies',
type: 'POST',
dataType: 'html',
beforeSend: function () {
loading.show();
},
success: function (data) {
content.html(data);
},
complete: function () {
loading.hide();
// IMPORTANT!!! validation
$.validator.unobtrusive.parse($("#formulaire"));
},
error: function (jqXHR) {
content.html(jqXHR.responseText);
}
})
}
function effacer() {
...
}
// document loading
$(document).ready(function () {
// retrieve the references of the page's various components
loading = $("#loading");
content = $("#content");
// we hide the moving image
loading.hide();
});
- righe 11–29: una chiamata Ajax;
- riga 12: l'URL di destinazione;
- riga 13: verrà richiesta tramite una richiesta HTTP POST. Si tratta di una richiesta POST senza parametri. Ecco perché non c'è una riga del tipo:
nella chiamata Ajax;
- riga 14: la risposta prevista dal server è un flusso HTML;
- righe 18–20: questo flusso HTML verrà utilizzato per aggiornare la regione con l'ID [content];
L'azione del server [Action05RetourSaisies] è la seguente:
[HttpPost]
public PartialViewResult Action05RetourSaisies(SessionModel session)
{
// view
return PartialView("Formulaire05", new ViewModel05() { A = session.A, B = session.B });
}
- riga 2: l'azione riceve come parametro il modello di sessione in cui abbiamo precedentemente memorizzato i valori inseriti di A e B;
- riga 5: restituiamo la vista parziale [Form05] con un modello di tipo [ViewModel05] in cui ci assicuriamo di inizializzare i campi A e B con i valori di A e B prelevati dalla sessione;
Torniamo ora al codice della funzione JavaScript [returnInput]:
function retourSaisies() {
// make a manual Ajax call
$.ajax({
url: '/Premier/Action05RetourSaisies',
type: 'POST',
dataType: 'html',
beforeSend: function () {
loading.show();
},
success: function (data) {
content.html(data);
},
complete: function () {
loading.hide();
// IMPORTANT!!! validation
$.validator.unobtrusive.parse($("#formulaire"));
},
error: function (jqXHR) {
content.html(jqXHR.responseText);
}
})
}
- riga 13: il metodo eseguito al completamento della chiamata Ajax;
- riga 14: l'immagine di caricamento animata viene nascosta;
- riga 16: un'istruzione un po' criptica che ho trovato online per risolvere il seguente problema: nel modulo visualizzato dal link [Torna all'input], i validatori lato client non funzionavano più. Mentre cercavo informazioni sulla libreria JS [jquery.unobtrusive-ajax], ho trovato la soluzione alla riga 16. Analizza il modulo, forse per attivare i validatori lato client.
7.7. Rendere accessibile su Internet un'applicazione ASP.NET
Vedi la sezione 9.26.
7.8. Generazione di un'applicazione Android nativa da un'applicazione APU a pagina singola
Vedi sezione 9.27.








































