Nous allons voir dans ce billet le meilleur support Javascript et Ajax apporté par ASP.NET MVC 3 notamment l'Unobtrusive Javascript et la nouvelle classe JsonValueProviderFactory. Ce billet fait suite à la série sur les nouveautés d'ASP.NET MVC 3 écrit conjointement avec Philippe Viallate. Vous pouvez consulter les précédents billets :
- [ASP.NET MVC] Nouveautés MVC 3 Part 1 - Améliorations dans Visual Studio 2010
- [ASP.NET MVC] Nouveautés MVC 3 Part 2 - Améliorations pour la Validation
- [ASP.NET MVC] Nouveautés MVC 3 Part 3 - Global Action FIlters
- [ASP.NET MVC] Nouveautés MVC 3 Part 4 - Meilleur support de l'injection de dépendances
- [ASP.NET MVC] Nouveautés MVC 3 Part 5 - Meilleur gestion de la génération et du rendu HTML
- [ASP.NET MVC] Nouveautés MVC 3 Part 6 - Modifications dans les types d'Action Result
Unobtrusive JavaScript (ou Javascript discret) pour Ajax
Le JavaScript a une réputation de langage de script limité et mal fait, inadapté à des développements massifs. Cette réputation est due à une longue accumulation de mauvaises pratiques mais aussi à une implémentation différente d'un navigateur à l'autre. La récente émergence de standards appliqués aux navigateurs, de Framework JavaScript (jQuery, Prototype, Archetype...) et les premiers débogueurs de bonne qualité rendent possible la production d'un JavaScript organisé et évolutif. Le JavaScript discret peut être vu comme une partie du mouvement des standards Web. Il est courant d'observer un code présenté ainsi :
<input type="text" name="date" onchange="validateDate(this);" />
L'appel JavaScript est inclus au sein du HTML. Cela rend plus difficile la lecture et la maintenance du code (ce qui vaut au JavaScript sa mauvaise réputation de code instable et difficile à maintenir), mais surtout, cela mélange la couche relevant proprement des données du document et la couche relevant du comportement de l'interface. Il est pourtant possible de faire la même chose de la manière suivante : on réalise du pur HTML d'une part, et on attache un événement depuis un fichier JavaScript d'autre part. Ce qui donne, en HTML :
<input type="text" name="date" id="datefield" />
Et dans le fichier JavaScript :
document.getElementById( "datefield" ).addEventListener( 'change', function(){ do_something(); }, false );
Cette manière de procéder est appelée discrète, on parle alors de JavaScript discret ou d'Unobtrusive JavaScript en anglais, car l'association avec l'action JavaScript n'est plus incluse dans le HTML mais se réalise par l'assignation d'un événement sur un élément du DOM. Le code JavaScript se rattache alors sur le DOM par la sélection de l'attribut id de l'élément HTML.
Avec ASP.NET MVC 1.0, les helpers Ajax étaient implémentés comme des méthodes d'extension de la classe AjaxHelper (et disponibles via la propriété Ajax de votre vue). Deux catégories d'aides helpers Ajax étaient fournies : Ajax links et Ajax forms. Ces deux catégories font fondamentalement la même chose : faire une demande asynchrone, et faire quelque chose avec le résultat lorsque vous avez terminé (y compris savoir si vous avez reçu une réponse de type succès ou échec en provenance du serveur).
Avec ASP.NET MVC 3, le moteur d'exécution a été mis à jour pour supporter JavaScript discret. Un consommateur a également été créé pour ces attributs JavaScript discret qui utilisent jQuery pour effectuer les requêtes Ajax.
Les méthodes d'extension AjaxHelper (ActionLink et RouteLink pour les liens Ajax, BeginForm et BeginRouteForm pour les formulaires Ajax) ont des API qui sont très similaires à leurs homologues HtmlHelper. La principale différence est que les versions AjaxHelper prennent toutes un paramètre supplémentaire sous la forme de la classe AjaxOptions :
public class AjaxOptions {
public string Confirm { get; set; }
public string HttpMethod { get; set; }
public InsertionMode InsertionMode { get; set; }
public int LoadingElementDuration { get; set; }
public string LoadingElementId { get; set; }
public string OnBegin { get; set; }
public string OnComplete { get; set; }
public string OnFailure { get; set; }
public string OnSuccess { get; set; }
public string UpdateTargetId { get; set; }
public string Url { get; set; }}
Les propriétés ci-dessus sont toutes utilisées pour stipuler exactement ce que vous voulez que votre appel Ajax fasse. Lorsque le mode discret est désactivé, MVC génèrera le JavaScript en mode inline sur vos balises a et form qui dépendent de la bibliothèque ASP.NET Ajax Library (MicrosoftAjax.js) et la bibliothèque MVC Ajax qui utilise ASP.NET Ajax (MicrosoftMvcAjax.js) :
<form
action="/ajax/callback"
id="form0"
method="post"
onclick="Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));"
onsubmit="Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), { insertionMode: Sys.Mvc.InsertionMode.replace, loadingElementId: 'loading', updateTargetId: 'updateme' });">
Ce comportement est identique à celui des versions 1 et 2 d'ASP.NET MVC. Lorsque le mode discret est activé pour le javascirpt avec ASP.NET MVC 3, le code HTML qui sera généré lors du rendu sera sensiblement différent, comme on peut le voir ci-dessous :
<form
action="/ajax/callback"
data-ajax="true"
data-ajax-loading="#loading"
data-ajax-mode="replace"
data-ajax-update="#updateme"
method="post">
La première chose que vous remarquerez est que le code HTML est très lisible (ce qui est très utile pour le débogage). L'utilisation de noms et de valeurs simples augmente la lisibilité. Il est également important de souligner la taille réduite du code HTML généré en mode JavaScript discret. Le code est compatible avec HTML 5, en utilisant la syntaxe d'attribut extensible, préfixé par "data-". Les sélecteurs CSS sont également utilisés pour une flexibilité accrue côté client pour le choix du chargement et de la mise à jour des éléments.
Avec ASP.NET MVC 3, un seul flag est nécessaire pour activer le mode discret du JavaScript, ce qui active à la fois le JavaScript discret et la validation discrète côté client. Le mode discret du JavaScript est désactivé par défaut afin de fournir une compatibilité ascendante avec les projets mis à niveau à partir des anciennes versions d'ASP.NET MVC. Cependant, pour les projets créés directement avec la version 3, donc via le template de projet, le mode discret est activé par défaut.
Pour activer ou non le mode discret du JavaScript pour une application ASP.NET MVC, on peut utiliser le fichier Web.config :
<configuration>
<appSettings>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>
</appSettings>
</configuration>
L'activation ou la désactivation sont également possibles via le code-behind :
HtmlHelper.UnobtrusiveJavaScriptEnabled = true;
L'utilisation du code-behind pour activer ou désactiver le mode discret est contextuel. Plus précisément si vous réalisez ceci dans votre fichier Global.asax, le changement de mode sera appliqué à l'ensemble de l'application. A contrario si vous l'utilisez dans un contrôleur, le changement de mode ne concernera que l'action en cours.
En plus d'activer le mode discret, vous aurez également besoin d'inclure deux fichiers de script : la bibliothèque jQuery (~/Scripts/jquery-1.4.1.js) et le plugin pour MVC d'Ajax discret avec jQuery (~/Scripts/jquery.unobtrusive-ajax.js). Une remarque importante : si vous oubliez d'inclure l'un ou l'autre script, vous ne verrez pas d'erreur lors de l'utilisation de la requête Ajax, celle-ci va tout simplement se comporter comme une requête non-Ajax, donc en synchrone.
Le tableau ci-dessous liste la correspondance entre les membres de la classe AjaxOptions et les attributs de donnée HTML 5 :
Membre de AjaxOptions Attribut HTML 5
Confirm data-ajax-confirm
HttpMethod data-ajax-method
InsertionMode data-ajax-mode
LoadingElementDuration data-ajax-loading-duration
LoadingElementId data-ajax-loading
OnBegin data-ajax-begin
OnComplete data-ajax-complete
OnFailure data-ajax-failure
OnSuccess data-ajax-success
UpdateTargetId data-ajax-update
Url data-ajax-url
jQuery discret pour la validation
Pour rappel le système de validation côté client depuis ASP.NET MVC 2.0 a été découplé du système de validation côté serveur par l'utilisation de JSON. Par rapport à la validation côté serveur, la validation côté client nécessite une meilleure compréhension de la programmation JavaScript.
Comme nous venons de le voir, une des améliorations ASP.NET MVC V3 concerne les helpers Ajax et Validation qui permettent une approche discrète du JavaScript. Le JavaScript discret permet d'éviter l'injection inline de code JavaScript dans une balise HTML, et permet ainsi une séparation plus nette en utilisant la convention "data" d'HTML 5. Cela rend votre code HTML plus court et plus propre, et rend plus facile la permutation ou la personnalisation des librairies JavaScript. Les Helpers ASP.NET MVC 3 pour la validation utilisent maintenant le plugin jQueryValidate par défaut. Dans les précédentes versions vous aviez besoin d'appeler explicitement Html.EnableClientValidation() afin de permettre la validation côté client. Ce qui n'est plus nécessaire avec l'arrivée de MVC 3 : la validation côté client, en utilisant une approche discrète, est maintenant activée par défaut.
Pour plus de détails à ce sujet, nous vous recommandons la lecture de ces très bons articles :
- Experience ASP.NET MVC 3 - the Unobtrusive jQuery-Based Ajax Support
- ASP.NET MVC 3 unobtrusive validation
Précisions sur l'activation du JavaScript discret et la validation côté client
Vous pouvez activer ou désactiver la validation client et le JavaScript discret globalement à l'aide des membres statiques de la classe HtmlHelper, comme dans l'exemple suivant :
HtmlHelper.ClientValidationEnabled = true;
HtmlHelper.UnobtrusiveJavaScriptEnabled = true;
Comme indiqué précédemment, les nouveaux projets ASP.NET MVC 3 ont le mode discret du JavaScript activé par défaut. Vous pouvez également activer ou désactiver ces fonctionnalités dans le fichier Web.config en utilisant les paramètres suivants :
<configuration>
<appSettings>
<add key="ClientValidationEnabled" value="true"/>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>
</appSettings>
</configuration>
Parce que vous pouvez activer ces fonctionnalités par défaut, de nouvelles surcharges ont été introduites à la classe HtmlHelper qui vous permettent de remplacer les paramètres par défaut, comme illustré dans les exemples suivants :
public void EnableClientValidation();
public void EnableClientValidation(bool enabled);
public void EnableUnobtrusiveJavaScript();
public void EnableUnobtrusiveJavaScript(bool enabled);
Pour la compatibilité ascendante, ces deux fonctionnalités sont désactivées par défaut.
Nouvelle classe JsonValueProviderFactory
Quiconque a été impliqué dans un projet ASP.NET MVC avec utilisation importante d'Ajax aura probablement remarqué que quelque chose manquait toujours. Imaginez le scénario suivant :
<% using (Html.BeginForm()) { %>
<p>
<%=Html.LabelFor(m => m.Username)%>
<%=Html.TextBoxFor(m => m.Username)%>
</p>
<p>
<%=Html.LabelFor(m => m.Password)%>
<%=Html.TextBoxFor(m => m.Password)%>
</p>
<%} %>
<script type="text/JavaScript">
$("form").submit(function (evt) {
// extract values to submit
var form = $(this),
username = form.find("[name=Username]").val(),
password = form.find("[name=Password]").val(),
json = JSON.stringify({
Username: username,
Password: password
});
$.ajax({
url: form.attr("action"),
type: 'POST',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
data: json,
success: handleLogin
});
// stop form submitting
evt.preventDefault();
});
</script>
Nous utilisons donc JSON pour envoyer le username et le password au serveur. Ce qui exécute un POST à l'action suivante :
[HttpPost]
public ActionResult Index(LoginRequest request)
{
// do login
return Json(new
{
Success = true
});
}
Avec ASP.NET MVC 2 le code suivant ne fonctionnerait pas immédiatement. Le binder du modèle par défaut en MVC 2 utilise les paramètres de la requête pour binder les propriétés du modèle, mais dans ce cas, il n'y en a pas puisque le contenu Ajax est le corps de la requête. Pour répondre à ce genre de problème dans MVC 2, nous devons fournir un binder personnalisé pour le modèle qui sait comment traiter les requêtes JSON :
public class JsonModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
HttpRequestBase request = controllerContext.HttpContext.Request;
string jsonStringData = new StreamReader(request.InputStream).ReadToEnd();
return JsonConvert.DeserializeObject(jsonStringData, bindingContext.ModelType);
}
}
Puis nous modifions notre action pour lui indiquer d'utiliser ce binder personnalisé :
[HttpPost]
public ActionResult Index([ModelBinder(typeof(JsonModelBinder))]LoginRequest request)
{
// do login
return Json(new
{
Success = true
});
}
ASP.NET MCV3 comble désormais cette lacune grâce à la nouvelle classe JsonValueProviderFactory. Celle-ci opère à un niveau supérieur à celui du binder du modèle. Cette classe, lorsqu'une requête JSON est reçue, tire les valeurs hors du corps JSON comme des paires clé/valeur. Ce qui signifie qu'elles sont à la disposition du binder du modèle dont le binder par défaut. Bref, plus besoin comme en MVC 2 d'un binder personnalisé !
Grâce à JsonValueProviderFactory fournie par MVC 3, le code de notre action sera simplifié comme ceci :
[HttpPost]
public ActionResult Index(LoginRequest request)
{
// do login
return Json(new
{
Success = true
});
}
Pour vérifier simplement la différence de traitement de la requête JSON par MVC 3 avec ou sans JsonValueProviderFactory, vous pouvez utiliser ce code dans l'Application_Start du Global.asx :
var jsonValueProviderFactory = ValueProviderFactories.Factories.OfType<JsonValueProviderFactory>().First();
ValueProviderFactories.Factories.Remove(jsonValueProviderFactory);
Vous remarquez alors que le paramètre request de type LoginRequest verra toutes ses propriétés non renseignées avec le JsonValueProviderFactory désactivé.