• 30 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_certifying

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 23/11/2017

Un soupçon d'Ajax

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

L’ajax, ce n’est pas une technique pour avoir un JavaScript qui est propre et qui sent bon. :p En fait, c’est l’acronyme d’Asynchronous JavaScript And XML. C’est une technique plutôt vieille qui est devenue à la mode dès qu’on a commencé à parler de Web 2.0. Le principe d’Ajax est de rendre les applications web plus modernes et réactives en évitant de recharger toute la page dès qu’il y a une modification.
Pour l’instant, nous n’avons pas fait d’Ajax dans notre application MVC. Lorsque nous avons réalisé des vues avec des formulaires par exemple, pour les soumettre nous avons été obligés d’envoyer tout sur le serveur, qui a ensuite réaffiché une page complète. C’est un processus relativement lent, dépendant de votre connexion Internet, qui n’est pas gênant quand la page est très légère et en local, mais qui peut vite poser un problème lorsqu’il y a un design très chargé avec plein d’images, etc.

L’idée d'Ajax c’est que plutôt que de recharger complètement une page complexe, on puisse recharger uniquement les petits bouts qui nous intéressent. Bon, dans une page de création de restaurant cela a peu d’intérêt, mais cela prend tout son sens par exemple dans une page qui agrège du contenu, comme un lecteur de flux RSS.

De même, nous avons fait une validation précédemment qui vérifiait qu’un nom de restaurant n’existait pas déjà lorsque nous tentions de le créer. C’est une vérification qui ne peut se faire que côté serveur et qui impose donc de soumettre la page complètement avec potentiellement un message d’erreur en retour.
Ça serait pas mal de pouvoir « ajaxifier » cette validation ?

Ajax avec jQuery

En tant que framework web moderne, ASP.NET MVC ne pouvait pas passer à côté de l’Ajax. Et c’est bien sûr jQuery qui a été choisi pour implémenter ceci côté navigateur.

Imaginons par exemple que nous souhaitions offrir à l’utilisateur la possibilité de rafraîchir une partie de la page des résultats, en l’occurrence les résultats en eux-mêmes ! C’est vrai que ce n’était pas pratique ce fonctionnement ; une fois arrivé sur la page de résultat, il fallait réafficher la page pour voir d’éventuels changements. Et si nous offrions la possibilité de recharger les changements en cliquant sur un lien ? Et uniquement les changements, pas le reste de la page bien sûr.

Une des premières choses à faire est d’inclure le script jQuery permettant de faire de l’Ajax. Il s’agit de ~/Scripts/jquery.unobtrusive-ajax.js, à mettre par exemple dans le layout :

<script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>

Ensuite, il vous suffit d’utiliser les helpers Ajax. Par exemple, nous allons ici nous servir du helper Ajax. ActionLink . Ce helper permet d’invoquer une action, comme son copain Html.ActionLink  mais la différence réside dans le fait que la requête est faite en Ajax et son résultat est réinjecté dans la page à l’emplacement de notre choix.

Pour l’illustrer, nous allons devoir refactoriser un peu notre code et notamment le contrôleur Vote et la vue AfficheResultat  afin d’utiliser une vue partielle. Donc, on va remplacer le tableau dans la vue AfficheResultat  par l’appel à une vue partielle :

<p>
    Résultats du sondage :</p>
<div id="tableauResultat">
    @{
    Html.RenderAction("AfficheTableau", new { id = ViewBag.Id });
    }
</div>
<p>Vue normale : @DateTime.Now.ToLongTimeString()</p>

Nous devons créer cette fameuse vue partielle AfficheTableau :

@model List<ChoixResto.Models.Resultats>

<table>
    <tr>
        <th>Nom</th>
        <th>Téléphone</th>
        <th>Nombre de votes</th>
    </tr>
        @foreach (var resto in Model)
        {
    <tr>
        <td>@resto.Nom</td>
        <td>@resto.Telephone</td>
        <td>@resto.NombreDeVotes</td>
    </tr>
}
</table>

<p>Vue partielle : @DateTime.Now.ToLongTimeString()</p>

Vous aurez noté que j’en ai profité pour rajouter l’heure dans chacune des vues.

Créons aussi l’action qui lui est associée :

public ActionResult AfficheResultat(int id)
{
    if (!dal.ADejaVote(id, HttpContext.User.Identity.Name))
    {
        return RedirectToAction("Index", new { id = id });
    }
    ViewBag.Id = id;
    return View();
}

public ActionResult AfficheTableau(int id)
{
    List<Resultats> resultats = dal.ObtenirLesResultats(id);
    return PartialView(resultats.OrderByDescending(r => r.NombreDeVotes).ToList());
}

Il est important de faire transiter l’identifiant du sondage de la manière que vous voulez. J’ai choisi de le faire transiter en paramètre car je trouve que c’est plus propre, mais on aurait très bien pu garder juste le ViewBag .
Bon, pour l’instant, le résultat n’a pas changé. Nous avons toujours la même chose, on utilise simplement une vue partielle.

Là où ça va être intéressant, c’est que maintenant nous allons rajouter l’appel Ajax dans la vue AfficheResultat :

<p>Résultats du sondage :</p>
<div id="tableauResultat">
    @{
    Html.RenderAction("AfficheTableau", new { id = ViewBag.Id });
    }
</div>
@Ajax.ActionLink("Actualiser le résultat", "AfficheTableau", new { id = ViewBag.Id}, new AjaxOptions
{
    InsertionMode = InsertionMode.Replace,
    UpdateTargetId = "tableauResultat",
    HttpMethod = "GET"
})
<p>Vue normale : @DateTime.Now.ToLongTimeString()</p>

C’est tout simple avec ce Helper. Nous lui passons le titre du lien ainsi que l’action à appeler. Bien sûr, il y a une surcharge qui prend en paramètre le contrôleur si besoin. Il y a simplement un nouveau paramètre de type AjaxOptions . Nous pouvons lui dire comment gérer sa requête Ajax. Ici par exemple je lui dis que je souhaite que le résultat de l’appel vienne remplacer (InsertionMode.Replace ) le contenu de la balise dont l’identifiant est tableauResultat . L’appel sera fait en GET.

Et nous obtenons ceci, un simple lien dans la page :

Un lien dans la page, pour faire de l'AJAX
Un lien dans la page, pour faire de l'AJAX

Si vous cliquez dessus, vous pouvez voir le contenu qui se rafraîchit, notamment en observant le changement d’heure dans la vue partielle alors que l’heure n’a pas changé dans la vue normale. Ceci montre bien que le rafraîchissement n’a été que partiel et que ce n’est pas toute la page qui s’est rechargée.
Si vous possédez de quoi analyser les requêtes de votre navigateur, vous pourrez voir l’appel Ajax. Exemple avec IE 10 :

La requête AJAX dans le navigateur
La requête Ajax dans le navigateur

Pour voir ceci avec IE 10, il vous faut activer les outils développeur en appuyant sur F12 et cliquer sur Réseau pour capturer les trames qui passent. À noter que cette analyse est disponible sur presque tous les navigateurs et au pire vous pouvez rajouter des plug-ins pour le faire.

Vous pouvez cliquer sur la requête et obtenir plus d’infos dessus, comme par exemple les headers ou la réponse :

Les headers de la requête AJAX
La réponse de la requête Ajax

On voit bien que la réponse de la requête Ajax c’est un bout de HTML, en l’occurrence la balise <table>  et son contenu.

Si vous regardez le code HTML généré par le helper Ajax, il s’agit d’un simple lien <a> :

<a data-ajax="true" data-ajax-method="GET" data-ajax-mode="replace" data-ajax-update="#tableauResultat" href="/Vote/AfficheTableau/13">Actualiser le résultat</a>

Sauf que la balise <a> est décorée d’attributs qui vont permettre à jQuery de faire toute sa magie. D’ailleurs, si vous supprimez le fichier jQuery que je vous ai fait ajouter au début du chapitre, alors vous verrez que ce lien fonctionne comme un lien classique qui affiche la vue.

Et voilà notre bel Ajax tout propre et qui brille.

Modifiez donc le test AfficheResultat_AvecVote_RenvoieLesResultats  pour avoir :

[TestMethod]
public void AfficheResultat_AvecVote_RenvoieLaVueParDefaut()
{
    dal.AjouterUtilisateur("Nico", "1234");
    dal.AjouterUtilisateur("Jérémie", "1234");
    dal.AjouterVote(idSondage, 1, 1);

    ViewResult view = (ViewResult)controleur.AfficheResultat(idSondage);
    Assert.AreEqual(string.Empty, view.ViewName);
}

Et ajoutez le test suivant :

[TestMethod]
public void AfficheTableau_RenvoieLeViewModel()
{
    dal.AjouterUtilisateur("Nico", "1234");
    dal.AjouterUtilisateur("Jérémie", "1234");
    dal.AjouterVote(idSondage, 1, 1);

    PartialViewResult view = (PartialViewResult)controleur.AfficheTableau(idSondage);

    List<Resultats> model = (List<Resultats>)view.Model;
    Assert.AreEqual(1, model.Count);
    Assert.AreEqual("Resto pinambour", model[0].Nom);
    Assert.AreEqual(1, model[0].NombreDeVotes);
    Assert.AreEqual("0102030405", model[0].Telephone);
}

Remarquez que nous obtenons en réponse de l’action du contrôleur un PartialViewResult .

Gérer les erreurs

Toutes les requêtes asynchrones Ajax peuvent potentiellement échouer : erreur de transport, erreur sur la requête, indisponibilité temporaire, etc. Sauf qu’actuellement, elles échouent en silence. Prenez par exemple l’attribut ChildActionOnly . Vous avez sans doute tenté de l’ajouter à votre action de contrôleur car vous ne vouliez pas qu’on puisse accéder à cette route sans passer par la vue principale. Et vous n’avez rien vu à part potentiellement une erreur si vous avez analysé les traces générées :

Erreur de la requête AJAX
Erreur de la requête Ajax

Il est possible de faire quelque chose si jamais la requête Ajax échoue. Tous les helpers Ajax permettent de définir le nom d’une méthode JavaScript à appeler en cas d’erreur :

@Ajax.ActionLink("Actualiser le résultat", "AfficheTableau", new { id = ViewBag.Id}, new AjaxOptions
{
    InsertionMode = InsertionMode.Replace,
    UpdateTargetId = "tableauResultat",
    HttpMethod = "GET",
    OnFailure = "ErreurAfficheTableau"
})

Ici par exemple, je souhaite appeler la méthode ErreurAfficheTableau  si jamais la requête Ajax échoue. Bien sûr, il me faut définir cette méthode JavaScript, par exemple :

<script type="text/javascript">
    function ErreurAfficheTableau() {
        $("#tableauResultat").html("Une erreur s'est produite lors de la mise à jour, veuillez réessayer ...");
    }
</script>

Ainsi, l’appel en erreur nous affichera :

Gestion de l'erreur de la requête AJAX
Gestion de l'erreur de la requête AJAX

Mise à jour périodique

Plutôt que de forcer l’utilisateur à cliquer sur un lien pour rafraîchir les votes, pourquoi ne pas lui simplifier la vie et lui proposer une mise à jour périodique ?
Ici, c’est vraiment de l’utilisation pure et dure de JavaScript et de jQuery, mais comme ça vous verrez comment faire un appel Ajax standard avec jQuery et comment démarrer une tâche périodique, qui tourne disons toutes les 10 secondes.
Le principe est de faire en sorte que toutes les 10 secondes, il y ait un appel Ajax qui se fasse et qui mette à jour notre vue partielle. Et en fait, c’est vraiment tout simple.
Supprimez la partie avec Ajax.ActionLink .
Et maintenant, nous allons écrire du JavaScript. Et pour commencer, nous allons mettre en place la tâche périodique :

<script type="text/javascript">
    var timer;
    function ChargeVuePartielle() {
    }

    $(function () {
        timer = window.setInterval("ChargeVuePartielle()", 10000);
    });
</script>

Avec ceci, la méthode JavaScript ChargeVuePartielle  sera appelée toutes les 10 secondes (10000 millisecondes). Il ne reste plus qu’à faire un appel Ajax qui mette à jour la balise <div>  contenant les résultats. Cela se fait ainsi avec jQuery :

function ChargeVuePartielle() {
    $.ajax({
        url: '@Url.Action("AfficheTableau", new {id = ViewBag.Id  })',
        type: 'GET',
        dataType: 'html',
        success: function (result) {
            $('#tableauResultat').html(result);
        }
    });
}

Ici, j’indique que ma requête Ajax est de type GET et renvoie du HTML. On utilise bien sûr le helper Url.Action  pour générer l’URL de la vue partielle qui renverra le HTML. En cas de succès de l’appel (paramètre success), il n’y aura plus qu’à remplacer le résultat de la balise <div> par ce que nous renvoie la méthode. Remarquez qu’il est également possible de faire quelque chose lorsque la méthode échoue, via le paramètre error.

Les formulaires asynchrones

Je vous en ai parlé un peu dans l’introduction, il est également possible de rendre facilement un formulaire asynchrone. Nous avons déjà pratiqué les formulaires ; ceux-ci nous permettent de soumettre des informations au serveur via des contrôles HTML. Le serveur nous renvoie une nouvelle page en réponse, qui est affichée par le navigateur.
Eh bien il est possible de soumettre des formulaires de manière asynchrone et de recevoir une réponse que nous pouvons afficher sans avoir besoin de recharger toute la page. Merci Ajax.

Imaginons par exemple que nous ayons beaucoup de restaurants, ce qui rend compliqué leurs administrations. Nous pourrions envisager de faire une vue qui permette de rechercher un restaurant. Jusqu’à présent, nous aurions dans un premier temps créé une action de recherche dans le contrôleur Restaurant  :

public ActionResult Recherche(RechercheViewModel rechercheViewModel)
{
    if (!string.IsNullOrWhiteSpace(rechercheViewModel.Recherche))
        rechercheViewModel.ListeDesRestos = dal.ObtientTousLesRestaurants().Where(r => r.Nom.IndexOf(rechercheViewModel.Recherche, StringComparison.CurrentCultureIgnoreCase) >= 0).ToList();
    else
        rechercheViewModel.ListeDesRestos = new List<Resto>();
    return View(rechercheViewModel);
}

avec un view-model qui serait :

public class RechercheViewModel
{
    public string Recherche { get; set; }
    public List<Resto> ListeDesRestos { get; set; }
}

et une vue :

@model ChoixResto.ViewModels.RechercheViewModel
@{
    ViewBag.Title = "Chercher un restaurant";
}

@using (Html.BeginForm("Recherche", "Restaurant", FormMethod.Get))
{
    @Html.TextBoxFor(m => m.Recherche);
    <input type="submit" value="Rechercher" />
    
    <p>Résultats de la recherche</p>
    if (Model.ListeDesRestos.Count == 0)
    {
        <p>Pas de résultat</p>
    }
    else
    {
        <table>
            <tr>
                <th>Nom</th>
                <th>Téléphone</th>
            </tr>
            @foreach (var resto in Model.ListeDesRestos)
            {
                <tr>
                    <td>@resto.Nom</td>
                    <td>@resto.Telephone</td>
                </tr>
            }
        </table>
    }
}

Notons au passage qu’étant donné qu’il n’y a pas de modification mais simplement un accès à des ressources, il est plus logique de faire un formulaire utilisant la méthode GET. Ainsi, lorsque nous arrivons sur la vue ou s’il n’y a pas de résultats de recherche, nous aurons :

Résultat de recherche normale
Résultat de recherche sans résultat

Et s’il y a des résultats de recherche alors nous aurons :

Résultat de recherche avec résultats
Résultat de recherche avec résultats

Bref, rien de sorcier que vous ne sachiez faire déjà…
Cependant, vous vous êtes rendu compte qu’à chaque recherche, nous rechargions toute la page. Et si nous ajaxifions tout cela ?
Pour cela, nous disposons d’un helper : Ajax.BeginForm qui vient remplacer le bien connu Html.BeginForm . Mais avant ça, nous devons d’abord utiliser une vue partielle. Créez l’action ResultatsRecherche  et modifiez l’action Recherche  pour avoir :

public ActionResult Recherche(RechercheViewModel rechercheViewModel)
{
    return View(rechercheViewModel);
}

public ActionResult ResultatsRecherche(RechercheViewModel rechercheViewModel)
{
    if (!string.IsNullOrWhiteSpace(rechercheViewModel.Recherche))
        rechercheViewModel.ListeDesRestos = dal.ObtientTousLesRestaurants().Where(r => r.Nom.IndexOf(rechercheViewModel.Recherche, StringComparison.CurrentCultureIgnoreCase) >= 0).ToList();
    else
        rechercheViewModel.ListeDesRestos = new List<Resto>();
    return PartialView(rechercheViewModel);
}

Place à la vue partielle ResultatsRecherche :

@model ChoixResto.ViewModels.RechercheViewModel
<p>Vue partielle : @DateTime.Now.ToLongTimeString()</p>
<p>Résultats de la recherche</p>
@if (Model.ListeDesRestos.Count == 0)
{
    <p>Pas de résultat</p>
}
else
{
    <table>
        <tr>
            <th>Nom</th>
            <th>Téléphone</th>
        </tr>
        @foreach (var resto in Model.ListeDesRestos)
        {
            <tr>
                <td>@resto.Nom</td>
                <td>@resto.Telephone</td>
            </tr>
        }
    </table>
}

Et la vue principale de recherche :

@model ChoixResto.ViewModels.RechercheViewModel
@{
    ViewBag.Title = "Chercher un restaurant";
}

@using (Html.BeginForm("Recherche", "Restaurant", FormMethod.Get))
{
    @Html.TextBoxFor(m => m.Recherche);
    <input type="submit" value="Rechercher" />
    <p>Vue principale : @DateTime.Now.ToLongTimeString()</p>

    <div id="resultats">
        @{Html.RenderAction("ResultatsRecherche", new { rechercheViewModel = Model });
        }
    </div>
}

Et oui, toujours la petite heure pour bien voir que le rechargement se faire uniquement dans la vue partielle. ^^

Sauf que pour l’instant, nous n’avons rien ajaxifié. Utilisons donc le fameux helper :

@using (Ajax.BeginForm("ResultatsRecherche", new AjaxOptions
{
    HttpMethod = "GET",
    InsertionMode = InsertionMode.Replace,
    UpdateTargetId = "resultats",
}))
{
    @Html.TextBoxFor(m => m.Recherche);
    <input type="submit" value="Rechercher" />
    <p>Vue principale : @DateTime.Now.ToLongTimeString()</p>

    <div id="resultats">
        @{Html.RenderAction("ResultatsRecherche", new { rechercheViewModel = Model });
        }
    </div>
}

Et voilà, c’est tout ce qu’il y a à faire. Vous pouvez constater que la méthode GET est passée dans les AjaxOptions .
N’oubliez pas que le fichier jquery.unobtrusive-ajax.js doit être présent, mais vu que nous l’avons mis dans le layout, il n’y a pas de soucis.

Vous voulez rajouter un message d’erreur lorsque la requête échoue ? C’est le même principe que précédemment, une petite méthode javaScript :

<script type="text/javascript">
    function ErreurRecherche() {
        $("#resultats").html("Une erreur s'est produite lors de la recherche, veuillez réessayer ...");
    }
</script>

avec dans le helper le nom de la méthode JavaScript :

@using (Ajax.BeginForm("ResultatsRecherche", new AjaxOptions
{
    HttpMethod = "GET",
    InsertionMode = InsertionMode.Replace,
    UpdateTargetId = "resultats",
    OnFailure = "ErreurRecherche",
}))

Et le tour est joué.
Il manque cependant un petit quelque chose… Une animation pour nous faire patienter ! Eh oui, imaginons que la recherche soit longue ; il est de bon ton d’indiquer à l’utilisateur que la requête est prise en compte et qu’il faut qu’il patiente. En général, on utilise un petit gif animé qui tourne, ou une barre de progression. Je vous encourage à vous créer votre propre animation en allant sur le site http://www.ajaxload.info/ qui s’avère très pratique pour cela. Ajoutez votre image dans votre solution, par exemple dans /Content/Images/ajax-loader.gif et ajoutez une image cachée à côté de votre bouton. Il suffit ensuite d’indiquer son id dans les options ajax du helper pour qu’il gère tout seul l’animation :

@using (Ajax.BeginForm("ResultatsRecherche", new AjaxOptions
{
    HttpMethod = "GET",
    InsertionMode = InsertionMode.Replace,
    UpdateTargetId = "resultats",
    OnFailure = "ErreurRecherche",
    LoadingElementId = "chargement"
}))
{
    @Html.TextBoxFor(m => m.Recherche);
    <img id="chargement" src="~/Content/Images/ajax-loader.gif" style="display: none" alt="Chargement en cours..." />
    <input type="submit" value="Rechercher" />
    <p>Vue principale : @DateTime.Now.ToLongTimeString()</p>

    <div id="resultats">
        @{Html.RenderAction("ResultatsRecherche", new { rechercheViewModel = Model });
        }
    </div>
}

Notez que l’image possède l’id  « chargement » et que c’est ce même id qui est utilisé dans la propriété LoadingElementId  du helper. Et voilà ce que ça donne :

Image d'attente
Image d'attente lors de la requête AJAX

Note : étant donné que notre recherche est ultra-performante :p, pour voir proprement l’animation, rajoutez-vous un petit Thread.Sleep(…)  dans votre action :

public ActionResult ResultatsRecherche(RechercheViewModel rechercheViewModel)
{
    if (!string.IsNullOrWhiteSpace(rechercheViewModel.Recherche))
    {
        rechercheViewModel.ListeDesRestos = dal.ObtientTousLesRestaurants().Where(r => r.Nom.IndexOf(rechercheViewModel.Recherche, StringComparison.CurrentCultureIgnoreCase) >= 0).ToList();
        Thread.Sleep(1500);
    }
    else
        rechercheViewModel.ListeDesRestos = new List<Resto>();
    return PartialView(rechercheViewModel);
}

Une fois que vous avez pu constater que cette belle animation est fonctionnelle, n’oubliez pas de supprimer le Thread.Sleep  qui ne sert à rien. ;)

Ajax et JSON

Il est très fréquent que les requêtes Ajax travaillent avec du JSON plutôt qu’avec du HTML. Ce que je vous ai montré pour l’instant, ce sont des rafraîchissements de zones HTML. Grosso modo, j’appelle une action de contrôleur qui me renvoie du HTML grâce à une vue partielle et c’est ce HTML que j’affiche directement dans une balise <div>  ou autre.
Ceci fonctionne bien mais a l’inconvénient de potentiellement renvoyer beaucoup d’informations. La mise en page HTML/CSS peut vite devenir lourde et donc il peut y avoir beaucoup de données qui vont transiter ce qui risque d’alourdir le contenu du retour HTML ; et plus c’est lourd, plus ça risque d’être long… et plus c’est long… plus ça risque de ralentir votre navigateur et dégrader l’utilisation du site pour vos utilisateurs.

Suivant les besoins, il peut être utile de travailler avec du JSON, format qui fonctionne particulièrement bien avec le JavaScript. Ainsi, ce ne sont que des données qui transitent et c’est le JavaScript qui s’occupe de les traiter et de les mettre au bon endroit.
Prenez par exemple la recherche que nous venons de faire. C’est assez anecdotique, mais le fait de renvoyer tout le tableau avec trois restaurants prend quand même 509 octets alors que juste les données en JSON prendraient 186 octets :

{"Recherche":"rest","ListeDesRestos":[{"Id":1,"Nom":"Resto pinambour","Telephone":"123"},{"Id":2,"Nom":"Resto pinière","Telephone":"456"},{"Id":3,"Nom":"Resto toro","Telephone":"789"}]}

Bien sûr, il manque la présentation et ici c’est un peu bête de devoir se refaire toute la présentation avec du JavaScript.
Il y a cependant certaines fois où c’est particulièrement pertinent de renvoyer du JSON. Nous n’allons pas le faire, mais imaginons que nous souhaitions rajouter de l’auto-complétion sur note textbox de recherche. Cela veut dire qu’à chaque fois que l’on ajoute une lettre dans la zone de texte, nous effectuons une requête pour nous renvoyer la liste de tous les restaurants qui correspondent à cette recherche. jQuery sait très bien faire de l’auto-complétion et utilise justement un format de données JSON.

Validation asynchrone

Et c’est aussi le cas de la validation asynchrone. Nous avons déjà vu plein de validations côté client mais il y en a une que nous pouvons améliorer. Vous vous rappelez ? Quand nous tentons de créer un nouveau restaurant, il y a un test dans le contrôleur qui vérifie que le nom du restaurant n’existe pas déjà :

[HttpPost]
public ActionResult CreerRestaurant(Resto resto)
{
    if (dal.RestaurantExiste(resto.Nom))
    {
        ModelState.AddModelError("Nom", "Ce nom de restaurant existe déjà");
        return View(resto);
    }
    if (!ModelState.IsValid)
        return View(resto);
    dal.CreerRestaurant(resto.Nom, resto.Telephone);
    return RedirectToAction("Index");
}

Cette validation est forcément une validation serveur car nous recherchons en base de données les restaurants existants. Nous ne pouvons pas faire cette validation côté client car cela imposerait de stocker tous les restaurants quelque part dans le JavaScript, ce qui n’est pas envisageable.
Vous vous doutez bien qu’avec tout l’Ajax que nous avons vu, il y a une solution pour faire cette validation de manière asynchrone… Et je vous dis oui !

Nous pourrions très bien écrire notre validateur personnalisé, comme nous l’avons déjà fait, qui ferait une requête Ajax avec jQuery pour interroger un contrôleur nous indiquant si le restaurant existe déjà ou pas. Décrit ainsi, ça n’a pas l’air trop compliqué. Si vous avez envie de le faire, ne vous privez pas. :)
Sauf que… ASP.NET MVC le propose déjà en standard.

Il suffit d’utiliser l’attribut Remote  sur la donnée à valider, en l’occurrence le nom de notre restaurant :

[Table("Restos")]
public class Resto
{
    public int Id { get; set; }
    [Required(ErrorMessage = "Le nom du restaurant doit être saisi")]
    [Remote("VerifNomResto", "Restaurant", ErrorMessage="Ce nom de restaurant existe déjà")]
    public string Nom { get; set; }
    [Display(Name = "Téléphone")]
    [RegularExpression(@"^0[0-9]{9}$", ErrorMessage = "Le numéro de téléphone est incorrect")]
    public string Telephone { get; set; }
}

Pour l’utiliser, je dois indiquer le nom d’une action (VerifNomResto ) et de son contrôleur (Restaurant ) qui servira de méthode de validation. Cette méthode doit renvoyer du JSON :

public JsonResult VerifNomResto(string Nom)
{
    bool resultat = !dal.RestaurantExiste(Nom);
    return Json(resultat, JsonRequestBehavior.AllowGet);
}

Je renvoie donc vrai si le nom n’existe pas et faux s’il existe.
Et c’est tout. ;)
Il ne reste plus qu’à tester si cela fonctionne ; allez sur la page d’ajout d’un restaurant et saisissez un nom de restaurant qui existe déjà. Nul besoin de soumettre le formulaire pour vérifier que cela fonctionne, vous avez juste à faire perdre le focus au champ (par exemple en appuyant sur la touche de tabulation) et vous pouvez voir la magie opérer. Et pour être bien sûr qu’il s’agit bien d’une requête Ajax, il suffit de regarder les requêtes :

Validation serveur via AJAX
Validation serveur via Ajax

Vous pouvez voir la requête qui part en GET, avec notamment son résultat si vous cliquez dessus :

Détails de la validation AJAX asynchrone
Détails de la validation Ajax asynchrone

Et voilà, ce n’est pas formidable tout ça ?

[HttpPost]
public ActionResult CreerRestaurant(Resto resto)
{
    if (dal.RestaurantExiste(resto.Nom))
    {
        ModelState.AddModelError("Nom", "Ce nom de restaurant existe déjà");
        return View(resto);
    }
    if (!ModelState.IsValid)
        return View(resto);
    dal.CreerRestaurant(resto.Nom, resto.Telephone);
    return RedirectToAction("Index");
}

Simplifier la déclaration du Javascript avec les bundles

Pour utiliser tout notre Ajax et toutes nos validations, il faut en inclure du JavaScript… Vous êtes d’accord avec moi, ce n’est pas très pratique tout ça, sans compter qu’on risque d’en oublier. Alors OK, grâce aux layout nous pouvons faire en sorte d’inclure tout en une seule fois mais si jamais il y a certains scripts que nous ne souhaitons pas inclure partout ?
C’est typiquement le cas pour la mise à jour périodique de la vue qui affiche le résultat du vote. Si vous déclarez ce script dans le layout, alors il va tenter de mettre à jour la vue partielle sur toutes les vues où nous passons, ce qui n’est pas terrible.

Heureusement, nous avons la possibilité de faire des groupes de scripts, appelés Bundles dans le jargon ASP.NET MVC. Par exemple, je peux faire un bundle qui inclue tous les scripts jQuery en ajoutant cette ligne de code :

BundleTable.Bundles.Add(new ScriptBundle("~/toutjquery").Include("~/Scripts/jquery*"));

Je donne le nom ~/toutjquery au bundle qui correspond à l’inclusion de tout ce qui est dans le répertoire Scripts et qui commence par jquery.
Ensuite, je pourrais utiliser ce bundle dans la vue grâce à la commande suivante :

@Scripts.Render("~/toutjquery")

Et derrière, la vue va inclure tout le JavaScript suivant :

<script src="/Scripts/jquery-1.10.2.js"></script>
<script src="/Scripts/jquery-ui-1.10.24.js"></script>
<script src="/Scripts/jquery.unobtrusive-ajax.js"></script>
<script src="/Scripts/jquery.validate.js"></script>
<script src="/Scripts/jquery.validate.unobtrusive.js"></script>

Ce qui est fort, c’est que le bundle ne m’a inclus que les versions non minimifiées de jQuery mais également il ne m’a pas inclus les fichiers nécessaires à l’auto-complétion (qui contiennent intellisense dedans).

Lorsque je déploierai en production, non seulement le bundle me mettra uniquement les fichiers minimifiés, mais il fera également en sorte de ne créer qu’un unique fichier avec tout le javaScript de tous les fichiers de manière à optimiser le chargement côté client (en effet, un unique fichier contenant tout est plus rapide à charger que X petits fichiers séparés). Ce fichier pourra également être mis en cache par le navigateur.

Pour le voir, allez chercher la ligne suivante dans votre web.config :

<compilation debug="true" targetFramework="4.5" />

et passez debug  à false  :

<compilation debug="false" targetFramework="4.5" />

Vous pouvez voir maintenant qu’il vous a généré une seule ligne pour inclure tous les scripts, du genre :

<script src="/toutjquery?v=dqaNVLUlc90HzXHC-4_8M298sQDRyzNY3B4cOzQAAh41"></script>

Si vous tentez de l’afficher, vous aurez un unique fichier javaScript minimifié incompréhensible :

(function(n,t){function yu(n){var t=wt[n]={};return i.each(n.split(h),function(n,i){t[i]=!0}),t}function ui[...],n(function(){r.unobtrusive.parse(document)})}(jQuery)

Je ne vous affiche pas tout heureusement parce qu’il y a en a 300 Ko chez moi :p.

Vous pouvez maintenant revenir en mode debug dans le web.config.

Si vous avez tenté de générer une application MVC qui n'est pas empty, alors vous avez pu voir que Visual Studio générait plein de choses pour nous. Et parmi tous ces fichiers, il y en a un qui s'appelle BundleConfig.cs et qui a été généré par Visual Studio dans le répertoire App_Start. Si vous fouillez un peu dedans, vous pouvez vous rendre compte qu’il y a déjà plein de bundles configurés et que vous allez pouvoir réutiliser :

public static void RegisterBundles(BundleCollection bundles)
{
    bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                "~/Scripts/jquery-{version}.js"));

    bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
                "~/Scripts/jquery-ui-{version}.js"));

    bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                "~/Scripts/jquery.unobtrusive*",
                "~/Scripts/jquery.validate*"));

 
   bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
                "~/Scripts/modernizr-*"));

    bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css"));

    bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
                "~/Content/themes/base/jquery.ui.core.css",
                "~/Content/themes/base/jquery.ui.resizable.css",
                "~/Content/themes/base/jquery.ui.selectable.css",
                "~/Content/themes/base/jquery.ui.accordion.css",
                "~/Content/themes/base/jquery.ui.autocomplete.css",
                "~/Content/themes/base/jquery.ui.button.css",
                "~/Content/themes/base/jquery.ui.dialog.css",
                "~/Content/themes/base/jquery.ui.slider.css",
                "~/Content/themes/base/jquery.ui.tabs.css",
                "~/Content/themes/base/jquery.ui.datepicker.css",
                "~/Content/themes/base/jquery.ui.progressbar.css",
                "~/Content/themes/base/jquery.ui.theme.css"));
}

À noter que vous pouvez même faire des bundles de fichiers CSS. À ce moment-là, il faudra utiliser le helper :

@Styles.Render("~/Content/css")

En résumé

  • Toute application web moderne qui se respecte doit tirer partir de la puissance d’Ajax.

  • Ajax permet d’effectuer des requêtes asynchrones pour récupérer des données, mettre à jour des portions de pages, soumettre des formulaires, etc.

  • Ajax fonctionne particulièrement bien avec le JSON.

  • Il est possible de faire des validations asynchrones avec l’attribut [Remote] .

  • Les bundles servent à simplifier la déclaration des fichiers JavaScript à inclure.

Exemple de certificat de réussite
Exemple de certificat de réussite