Accueil Nos publications Blog [ASP.NET MVC] Ces petites choses de Razor que l’on ignore … (4/4)

[ASP.NET MVC] Ces petites choses de Razor que l’on ignore … (4/4)

Razor Dans le dernier billet de cette série dédiée à Razor, je vais revenir sur les différentes manières de créer des méthodes utilitaires pour factoriser du code dans des vues ou pour y utiliser du code métier.

Création de Helpers pour Razor

Les HTML Helpers

Un HTML Helper est simplement une méthode d’extension sur la classe HtmlHelper et qui retourne une chaîne de caractères. Cette chaîne peut contenir un simple tag HTML comme input, mais aussi tout une grappe de HTML plus complexe, sous la forme d’un div par exemple. Les HTML Helpers ne sont pas propres à Razor, ils sont communs à tous les moteurs de vues utilisables par ASP.NET MVC. En fait, les méthodes Html.BeginForm et autres que vous utilisez lorsque vous développez une page sont utilisables de la même manière avec Razor qu’avec le moteur WebForms.

La manière la plus simple de créer un nouvel HTML Helper est d’enrichir la classe HtmlHelper avec une méthode d’extension. Prenons l’exemple ci-dessous d’une méthode chargée de rendre une alerte Bootstrap. L’extrait de code suivant représente le squelette de la méthode, nous allons voir comment l’implémenter par la suite.

    public enum AlertType
    {
    Success,
    Info,
    Warning,
    Danger
    }
    public static class BootstrapExtensions
    {
    public static MvcHtmlString Alert(this HtmlHelper htmlHelper, AlertType alertType, string content)
    {
    // ...
    }
    }
    

Pour implémenter cette méthode, il est nécessaire de construire du HTML, celui que la méthode doit retourner et qui est automatiquement inséré dans les réponses retournées aux clients. Manipuler du HTML directement à base de chaines de caractères (ou de StringBuilder) peut être une tâche extrêmement fastidieuse et source d’erreur. Pour faciliter le développement de ce genre de méthodes, les développeurs d’ASP.NET MVC nous fournissent une classe utilitaire baptisée TagBuilder.

En instanciant la classe TagBuilder, nous devons lui préciser le nom du tag HTML associé. Ensuite, via la méthode GenerateId, nous pouvons définir l’id qui sera utilisé par la balise dans le contenu généré. La méthode AddCssClass permet de gérer facilement la collection de classes utilisées par un élément HTML.

Dans le cas de notre exemple, l’usage que nous pouvons faire du TagBuilder est le suivant. Notez que pour récupérer la représentation finale du contenu HTML, l’usage est très similaire à une instance de StringBuilder puisqu’il suffit d’appeler la méthode ToString. Cette dernière peut éventuellement prendre un paramètre afin d’influencer le HTML généré (dans le cas de tag HTML auto-fermants par exemple).

    public static class BootstrapExtensions
    {
    public static MvcHtmlString Alert(this HtmlHelper htmlHelper, AlertType alertType, string content)
    {
    var modal = new TagBuilder("div");
    modal.GenerateId("mon-id");
    modal.AddCssClass("alert");
    switch (alertType)
    {
    case AlertType.Success:
    modal.AddCssClass("alert-success");
    break;
    case AlertType.Info:
    modal.AddCssClass("alert-info");
    break;
    case AlertType.Warning:
    modal.AddCssClass("alert-warning");
    break;
    case AlertType.Danger:
    modal.AddCssClass("alert-danger");
    break;
    }
    modal.SetInnerText(content);
    return new MvcHtmlString(modal.ToString());
    }
    }
    

L’appel à la méthode GenerateId directement dans le Helper n’est pas très propre et rend l’implémentation de la méthode Alert peu souple. Il est préférable de ne pas donner d’Id au tag et de laisser au développeur qui utilise la méthode le soin de fournir cette valeur et éventuellement d’autres attributs. La méthode MergeAttribute qui permet d’ajouter un attribut à la collection d’attributs déjà présents sur un tag, il existe également une variante permettant de prendre en paramètre un dictionnaire d’attributs. Je peux changer la signature de la méthode afin d’inclure un paramètre htmlAttributes, sous la forme d’un object, utilisable dans les vues Razor comme un type anonyme. La méthode AnonymousObjectToHtmlAttributes de la classe HtmlHelper m’aide à transformer cet objet vers un dictionnaire utilisable par MergeAttributes.

    public static class BootstrapExtensions
    {
    public static MvcHtmlString Alert(this HtmlHelper htmlHelper, AlertType alertType, string content, object htmlAttributes)
    {
    var modal = new TagBuilder("div");
    var dictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
    modal.MergeAttributes(dictionary);
    modal.AddCssClass("alert");
    switch (alertType)
    {
    case AlertType.Success:
    modal.AddCssClass("alert-success");
    break;
    case AlertType.Info:
    modal.AddCssClass("alert-info");
    break;
    case AlertType.Warning:
    modal.AddCssClass("alert-warning");
    break;
    case AlertType.Danger:
    modal.AddCssClass("alert-danger");
    break;
    }
    modal.SetInnerText(content);
    return new MvcHtmlString(modal.ToString());
    }
    }
    

A l’usage, un appel au helper a simplement la forme suivante. Comme vu dans les précédents billets, attention à bien importer le bon espace de nom ! Sans quoi la compilation de la vue échouera.

    @Html.Alert(AlertType.Danger, "Il y a un problème", new
    {
    id = "mon-id-de-test"
    })
    

Les Razor Helper

Je vais continuer avec un autre type de helper, propre à Razor cette fois ci : les Razor Helper. Peu connu des développeurs ASP.NET MVC, ils permettent de factoriser du markup et de le réutiliser à plusieurs endroits dans votre application, un peu comme des contrôles ASCX par exemple.

Conceptuellement parlant, ils sont assez proches du mécanisme de vues partielles, sauf qu’ils ont pour vocation à factoriser du markup qui n’est pas lié à un contexte applicatif précis, afin de pouvoir être réutilisés d’applications en applications. L’exemple le plus courant de Razor Helper est la factorisation de l’appel à un bouton « +1 » de Facebook, c’est l’exemple que nous allons utiliser ici. Enfin, il est intéressant de savoir que si nous ne rencontrons pas souvent ce genre de helper dans des applications ASP.NET MVC, ils sont plus largement utilisés du côté de WebMatrix.

Un Razor Helper, s’écrit comme une fonction C#, directement dans le code d’une vue Razor en remplaçant le type de retour de la méthode par l’instruction @helper. Le corps de la méthode lui contient alors du markup et éventuellement du code C#.

Après avoir récupéré le code correspondant sur le portail développeur de Facebook, je peux donc écrire le helper suivant. Notez que j’ai choisi de créer une méthode avec deux paramètres, que je peux ensuite utiliser de manière assez classiquement au milieu du markup qui compose le helper.

    @helper Like(string appId, string urlToLike)
    {
    <div id="fb-root"></div>
    (function (d, s, id) {
    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)) return;
    js = d.createElement(s); js.id = id;
    js.src = "//connect.facebook.net/en_US/all.js#xfbml=1&amp;appId=@appId";
    fjs.parentNode.insertBefore(js, fjs);
    }(document, 'script', 'facebook-jssdk'));
    <div class="fb-like"></div>
    }
    

L’appel au helper se fait ensuite de la façon suivante.

    @Like("appid", "https://google.fr")
    

Le helper que je viens de créer ne peut pas encore être utilisé dans d’autres vues car j’ai placé son code directement dans une page de mon application.

Pour le factoriser, il faut que je le déplace vers une page située dans un dossier App_Code, à la racine de mon site. C’est un fonctionnement purement par convention : créez un dossier App_Code à la racine de votre application ASP.NET MVC, créez une vue Razor dans ce dossier (dans mon exemple, j’ai choisi de l’appeler FacebookControls) et placez y le code du helper. Il est possible de placer plusieurs helpers dans la même vue.
L’usage du helper se fait alors de la manière suivante. Il est à présent utilisable depuis toutes les vues de l’application, et il est facilement portable d’une application à l’autre.

    @FacebookControls.Like("appid", "https://google.fr")
    

Les Razor Templated Delegate

Les Templated Delegates représentent un moyen simple d’appliquer « inline » un template à toute une collection. Ils s’articulent autour de bloc de code ayant une signature du type Func<dynamic, HelperResult>.

Admettons par exemple que dans une même vue, je dois travailler avec deux collections d’éléments de types différents et que pour ces deux collections, je dois effectuer un traitement similaire. Ce traitement pourrait être par exemple l’appel à l’HTML Helper créé plus tôt dans ce billet. Le code de ma vue ressemble alors au suivant.

    <div>
    <div>
    @foreach (var item in Model.Collection1)
    {
    Html.Alert(AlertType.Danger, item.Message)
    }
    </div>
    <div>
    @foreach (var item in Model.Collection2)
    {
    @Html.Alert(AlertType.Danger, item.Message)
    }
    </div>
    </div>
    

Pour les deux collections, j’ai dû copier deux fois le même template appliqué à chaque élément. S’il avait été plus complexe, la vue serait alors plus complexe à intégrer et plus complexe à maintenant. Avec ce que nous venons de voir dans le billet, nous savons que nous pouvons transformer ce code de la manière suivante.

    @helper danger(string message)
    {
    @Html.Alert(AlertType.Danger, message)
    }
    <div>
    <div>
    @foreach (var item in Model.Collection1)
    {
    danger(item.Message);
    }
    </div>
    <div>
    @foreach (var item in Model.Collection2)
    {
    danger(item.Message) ;
    }
    </div>
    </div>
    

En utilisant un Templated Delegate, le même exemple peut s’écrire de la manière suivante.

    @{
    Func danger = @Html.Alert(AlertType.Danger, @item); 
    }
    <div>
    <div>
    @foreach (var item in Model.Collection1)
    {
    danger(item.Message);
    }
    </div>
    <div>
    @foreach (var item in Model.Collection2)
    {
    danger(item.Message);
    }
    </div>
    </div>
    

Et là où les Templated Delegates sont relativement intéressants, c’est qu’ils peuvent être combinés avec des Html Helpers pour inverser l’usage du foreach. Ainsi, la méthode suivante est un Helper qui prend une collection et un Templated Delegate en paramètre, et elle applique ce template pour tous les éléments de la collection.

    public static HelperResult Apply(this IEnumerable items, Func propertySelector, Func<U> template)
    {
    return new HelperResult(writer =&gt;
    {
    foreach (var item in items)
    {
    template(propertySelector(item)).WriteTo(writer);
    }
    });
    }
    

Le code de la vue de l’exemple peut maintenant s’écrire de la façon suivante. Par rapport à la version initiale, la maintenance et la lecture sont grandement simplifiée. Et chose intéressante, on a changé la manière d’écrire notre code dans une vue et on se rapproche un peu d’un langage fonctionnel.

    @{
    Func danger = @Html.Alert(AlertType.Danger, @item);
    }
    <div>
    <div>
    @Model.Collection1.Apply(i =&gt; i.Message, danger)
    </div>
    <div>
    @Model.Collection2.Apply(i =&gt; i.Message, danger)
    </div>
    </div>
    

Voilà qui conclut cette série d’articles sur Razor. J’espère qu’elle vous aura permis d’apprendre des choses et éventuellement d’utiliser des fonctionnalités du moteur dont vous ignoriez jusqu’à présent l’existence. Encore une fois, même si je me suis concentré sur C#, tout ce qui s’est dit dans cette série d’articles reste valable et applicable en VB.NET.

A bientôt !