L’ASP.NET MVC est un Framework de Microsoft complémentaire à ASP.NET et permettant de développer des sites web en appliquant le Pattern MVC. Il vient en complément des WebForms. Il est ainsi possible de choisir entre les deux en fonction des besoins, l’approche MVC étant conseillée pour la mise en place de sites complexes.
L’ASP.NET MVC permet d’avoir une maîtrise très précise du code HTML généré. En contrepartie, le temps de développement induit est important. Ce temps peut être réduit en utilisant les Helpers HTML, et encore plus en mettant en œuvre des composants prenant en entrée le modèle et générant l’élément HTML correspondant, tel un tableau, un formulaire, etc.
Qu’est ce qu’un « Helper » MVC ?
De base, le framework MVC contient un certain nombre de méthodes permettant de simplifier l’écriture des pages en leur déléguant l’écriture du code HTML.
Ces méthodes permettent entre autre de définir facilement les contrôles de base d’une page tout en spécifiant les éléments du modèle qui y sont rattachés.
Ainsi il est possible d’ajouter la ligne suivante dans une vue :
1 |
@Html.ActionLink("Sample Grid", "Grid", "Sample") |
Cette ligne permet d’ajouter un lien hypertexte en indiquant le contrôleur à appeler. Le code HTML généré est le suivant :
1 |
<a href="/Sample/Grid">Sample Grid</a> |
Pour la mise en place d’une zone de saisie d’un élément du modèle :
1 |
@Html.EditorFor(model => model.Value) |
Qui donne le code HTML suivant :
1 |
<input class="text-box single-line" id="Value" name="Value" type="text" value="" /> |
Description du composant
Le composant que nous allons définir est une grille simple permettant d’afficher le contenu d’une liste d’éléments. Une des implémentations possibles pour mettre en œuvre une telle grille sans composant serait :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<table class="table table-striped"> <tr> <th>@Html.DisplayNameFor(m => m.ProductList[0].ProductID)</th> <th>@Html.DisplayNameFor(m => m.ProductList[0].Name)</th> <th>@Html.DisplayNameFor(m => m.ProductList[0].ProductNumber)</th> </tr> @foreach (var item in Model.ProductList) { <tr> <td> @item.ProductID </td> <td> @item.Name </td> <td> @item.ProductNumber </td> </tr> } </table> |
Ce qui donne le tableau (Figure 1) sur la page générée :
Figure 1
Nous allons donc mettre en place un composant permettant de générer ce tableau automatiquement en analysant les propriétés du type des éléments qui seront passés en paramètres.
Mise en place du composant
La première étape consiste à générer une nouvelle « Application web ASP.NET MVC 4 » pour initialiser les vues et les modèles nécessaires au fonctionnement du composant.
Ainsi qu’un projet « bibliothèque de classe » qui abritera le composant et l’ensemble des sous éléments nécessaires à son fonctionnement. Voici l’arborescence de la solution (figure 2) :
Figure 2 : arborescence de la solution
Nous devons ensuite référencer « SampleControls » dans « SampleSite » et enregistrer le « namespace » dans le fichier de configuration du dossier Views (figure 3) :
Figure 3 : enregistrement du namespace contenant le composant
Ceci rend accessible les classes du namespace « SampleControls » dans toutes les vues.
Dans la foulée, nous pouvons utiliser une classe statique pour exposer nos composants pour les utiliser comme les HTML Helpers du namespace « System.Web.Mvc.Html ». La classe statique contient le code suivant :
1 2 3 4 5 6 7 8 9 10 |
namespace SampleControls { public static class CCPoc { public static MvcHtmlString Grid<T>(IEnumerable<T> val) where T : class { return new Grid().Generate(val); } } } |
L’utilisation des génériques va nous permettre de prendre n’importe quel type de classe en entrée. La méthode statique de la classe va lancer la génération de la grille avec en entrée la liste d’éléments à afficher.
Ajoutons la classe Grid qui abritera la logique de création du code HTML de génération du tableau :
1 2 3 4 5 6 7 8 9 10 11 |
internal class Grid { public Grid() { } public MvcHtmlString Generate<T>(IEnumerable<T> model) where T : class { return MvcHtmlString.Create("<div>Test</div>"); } } |
Pour le moment nous allons juste renvoyer une balise « div » contenant du texte. Créons une vue pour utiliser ce composant, celle-ci contient le code suivant :
1 2 3 4 5 6 7 |
@model SampleSite.Models.ProductModel @{ ViewBag.Title = "Sample Grid"; } @CCPoc.Grid(Model.Products) |
Le contrôleur associé ne fait que fournir le modèle « ProductModel », celui-ci contenant une liste « Products » de « ProductElement » ainsi définis :
1 2 3 4 5 6 7 8 |
public class ProductElement { public int Id { get; set; } public string Name { get; set; } public string Number { get; set; } } |
L’appel de la vue donne le résultat suivant (Figure 4) :
Figure 4
Du côté du contrôleur il suffit d’instancier le modèle et le transmettre à la vue :
1 2 3 4 |
public ActionResult SampleGrid() { return View(new ProductModel()); } |
De l’objet au tableau
Maintenant que nous avons le composant fonctionnel, il nous reste à ajouter la logique de création du tableau.
Nous avons en entrée du composant une liste d’objet, il est donc possible d’analyser la structure de la classe en utilisant la réflexion pour déduire la structure du tableau.
Mais comment faire passer de la logique d’affichage au composant pour définir les propriétés qui doivent être affichés, le libellé des en-têtes, et autres éléments non décrits par la structure de la classe ?
Pour cela nous pouvons ajouter un attribut personnalisé qui sera utilisé dans le modèle et interprété par le composant. Ajoutons donc la classe « GridColumnAttribute » dans le projet « SampleControls » et contenant le code suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
namespace SampleControls { public class GridColumnAttribute : Attribute { public GridColumnAttribute() { this.Display = true; } public string HeaderText { get; set; } public bool Display { get; set; } } } |
Nous pouvons alors décorer les propriétés de notre modèle avec cet attribut :
1 2 3 4 5 6 7 8 9 10 11 |
public class ProductElement { [GridColumn(HeaderText="Product Id")] public int Id { get; set; } [GridColumn(HeaderText = "Product Name", Display = true)] public string Name { get; set; } [GridColumn(HeaderText = "Product Number", Display = true)] public string Number { get; set; } } |
Il reste encore à analyser la classe dans le composant pour constituer le tableau. Pour ce faire nous devons utiliser la réflexion pour récupérer la liste des propriétés de la classe et pour chacune vérifier si elle est décorée avec notre attribut personnalisé :
1 2 3 4 5 6 7 8 9 10 |
PropertyInfo[] properties = type.GetProperties(); foreach (PropertyInfo property in properties) { var attributes = property.GetCustomAttributes(typeof(GridColumnAttribute), true); var attribute = new GridColumnAttribute(); if (attributes != null && attributes.Length > 0) { attribute = (GridColumnAttribute)attributes[0]; } |
Une fois ces informations obtenues, il faut ensuite parcourir la liste des éléments passés au composant pour construire le code HTML contenant les données.
Pour la génération du code HTML nous pouvons utiliser la classe « TagBuilder » qui est contenue dans l’assembly « System.Web.WebPages ». Celle-ci s’utilise de la manière suivante :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
TagBuilder table = new TagBuilder("table"); table.AddCssClass("table"); table.AddCssClass("table-striped"); TagBuilder thLine = new TagBuilder("tr"); foreach (GridColumnInfo colInfo in gridStruct.ColumnInfos) { if (!colInfo.Display) { continue; } TagBuilder currentCell = new TagBuilder("th"); currentCell.SetInnerText(colInfo.Header); thLine.InnerHtml += currentCell.ToString(); } table.InnerHtml += thLine.ToString(); foreach (List<string> lineValues in gridStruct.CellValues) { TagBuilder tr = new TagBuilder("tr"); foreach (string cellValue in lineValues) { TagBuilder currentCell = new TagBuilder("td"); currentCell.SetInnerText(cellValue); tr.InnerHtml += currentCell.ToString(); } table.InnerHtml += tr.ToString(); } return table.ToString(); |
Le tableau généré est le suivant (figure 5) :
Figure 5
Conclusion
L’écriture de tels composants a plusieurs avantages comme les possibilités de réutilisations, l’accélération du développement, la mise en commun de l’apparence des éléments mis en place, et surtout la simplification de la maintenance.
Mais il présente aussi des limites notamment sur le degré de personnalisation de celui-ci qui ne peut pas forcément couvrir tous les cas de présentation et d’organisation des données.
L’intérêt principal du développement de tels composants réside dans l’économie d’échelle en terme de temps de développement qui sera réalisée par leur réutilisation systématique dans différents projets.
Il est possible de retrouver l’intégralité de cet article dans le numéro 173 du mois d’Avril 2014 du magazine « Programmez ».