Version imprimable       Envoyer     
Cliquez pour évaluer et commenter
MSDN
MSDN Library
Articles Techniques
Développement .NET
Articles et Vue d'ensemble
Application ASP.NET
ASP.NET
ASP.NET 2.0
Using Controls
 ASP.NET 2.0 et les contrôles liés a...
ASP.NET 2.0 et les contrôles liés aux données : une nouvelle perspective et de nouvelles pratiques
Paru le 01 mars 2005 | Dernière mise à jour le 10 octobre 2005
Par Dino Esposito

Produits concernés :
Microsoft ASP.NET 1.x
Microsoft ASP.NET 2.0

Résumé : présentation de l’évolution des outils de création des contrôles liés aux données dans ASP.NET 2.0. (19 pages imprimées)

Lire l'article en anglais 

Sur cette page

1. Utilité d’un nouveau modèle de source de données 1. Utilité d’un nouveau modèle de source de données
2. Contrôles liés aux données dans ASP.NET 2.0 2. Contrôles liés aux données dans ASP.NET 2.0
3. Points d’analyse 3. Points d’analyse
4. Mécanisme de liaison de données 4. Mécanisme de liaison de données
5. Contrôles de liste 5. Contrôles de liste
6. Exemple de contrôle HeadlineList 6. Exemple de contrôle HeadlineList
7. Gestion des collections personnalisées 7. Gestion des collections personnalisées
8. Un mot sur les contrôles composites 8. Un mot sur les contrôles composites
Conclusion Conclusion
Bibliographie Bibliographie
À propos de l’auteur À propos de l’auteur

1. Utilité d’un nouveau modèle de source de données

La liaison de données est l’une des surprises les plus agréables que les développeurs ont trouvé dans leur coffret ASP.NET 1.x. Comparée à la prise en charge d’Active Server Pages pour l’accès aux données, la liaison de données était un mélange incroyable de simplicité et d’efficacité. Une perfection quelque peu nuancée par la confrontation aux besoins des vrais développeurs. Les limites ne se situent pas au niveau de la fonctionnalité générale, mais plutôt dans le fait que les développeurs doivent écrire beaucoup de code pour gérer des opérations simples et courantes telles que la pagination, le tri ou la suppression. Pour combler cette lacune, ASP.NET 2.0 s’est doté d’un nouveau modèle de source de données (voir mon article  More Load, Less Code with the Data Enhancements of ASP.NET 2.0 ). Ce modèle se compose de nombreux nouveaux contrôles sans interface utilisateur qui font la connexion entre les parties visuelles des contrôles liés aux données et les conteneurs de données. En résumé, la quasi-totalité du code que les développeurs devaient écrire dans ASP.NET 1.x (dûment créé et formé) est désormais intégrée dans une nouvelle famille de contrôles : les composants de sources de données.

L’utilisation de ces composants présente de nombreux avantages, dont le plus important est la possibilité d’utiliser un modèle de liaison de données entièrement déclaratif. Le nouveau modèle réduit la quantité de code libre inséré en ligne dans les ressources ASPX ou éparpillé dans de nombreuses classes. La nouvelle architecture de liaison de données oblige les développeurs à se plier à des règles strictes. De plus, elle modifie intrinsèquement la qualité du code. Les longs blocs de code tendent à disparaître au profit de composants qui se greffent tout simplement dans le cadre existant. Les composants de sources de données proviennent de classes abstraites, implémentent des interfaces familières et, de manière générale, sont synonymes de réutilisabilité accrue.

L’excellent ouvrage de Nikhil Kothari sur le développement de contrôles, intitulé  Developing Microsoft ASP.NET Server Controls and Components, a aidé des milliers de développeurs à créer des contrôles sur mesure tout en illustrant les meilleures pratiques de conception et d’implémentation. Toutefois, un livre, aussi excellent soit-il, ne peut jamais remplacer un meilleur cadre système. Avec ASP.NET 2.0, vous bénéficiez aussi d’un graphe de classes entièrement remodelé truffé d’options de liaison de données qui gagnent en spécificité au fur et à mesure que vous développez l’arborescence. La nouvelle hiérarchie de contrôles liés aux données facilite le choix de la bonne classe dans laquelle puiser les ressources pour la création de leurs propres contrôles liés aux données.

Cet article vous révèle, en primeur, toutes les nouveautés du modèle de liaison de données d'ASP.NET 2.0 qui touchent aux contrôles personnalisés. Au fil des pages, vous découvrirez les nouvelles classes disponibles et les nouvelles règles pour la création de contrôles personnalisés de haute qualité.


2. Contrôles liés aux données dans ASP.NET 2.0

Le modèle de source de données ASP.NET 2.0 ne requiert pas nécessairement une nouvelle collection de contrôles (par exemple GridView et FormView). Il fonctionne toujours avec les anciens contrôles tels que DataGrid et CheckBoxList. Pour les développeurs, cela signifie qu’il existe désormais deux types de sources de données : les conteneurs et collections classiques de type IEnumerable comme DataView, et des contrôles de sources de données tels que SqlDataSource et ObjectDataSource. En définitive, les contrôles liés aux données ASP.NET 2.0 doivent permettre de traduire tous types de données entrantes en une collection énumérable indépendamment de la source (objet ADO.NET, collection personnalisée, composant de source de données).

Dans ASP.NET 1.x, la documentation est un peu en avance sur la réalité. En effet, elle identifie correctement et détaille les trois types de contrôles liés aux données : les contrôles standards, les contrôles de listes et les contrôles composites. La première catégorie regroupe les contrôles qui fournissent une implémentation non vide de la méthode DataBind et de la propriété DataSource. Les contrôles de listes constituent un mélange intéressant de propriétés de mise en page avancées (par exemple, RepeatColumns et RepeatLayout) et de modèles d’éléments fixes et incorporés qui se répètent pour chaque élément de données lié. Les contrôles composites, enfin, sont des contrôles qui assurent la conception de l’interface utilisateur finale en combinant un ou plusieurs contrôles existants. La documentation traite avec précision de chaque matière liée à la création de ces types de contrôles, mais le cadre ASP.NET, lui, offre peu de classes de base pour simplifier la tâche du développeur. La figure 1 illustre la nouvelle hiérarchie de contrôles liés aux données dans ASP.NET 2.0. Remarquez les classes de base en jaune, et leur distribution dans l’arborescence.

Figure 1. Hiérarchie des contrôles liés aux données dans ASP.NET 2.0

Le tableau ci-dessous regroupe les classes de base illustrées dans la figure 1.

Classe

Description

BaseDataBoundControl

Classe racine pour les contrôles liés aux données. Effectue la liaison de données et valide les données liées.

DataBoundControl

Contient la logique de communication avec les contrôles de sources de données et les conteneurs de données. Vous utilisez cette classe pour créer un contrôle lié aux données standard.

ListControl

Classe de base pour les contrôles de listes. Elle contient une collection Items et offre des possibilités avancées pour le rendu de mise en pages.

CompositeDataBoundControl

Implémente le code général requis par les contrôles hybrides, y compris le code qui restaure l’arborescence du contrôle à partir d’une certaine vue lorsqu’une publication est effectuée.

HierarchicalDataBoundControl

Classe racine pour les contrôles hiérarchiques basés sur arborescence.

Tableau 1 – Classes de base liées aux données dans ASP.NET 2.0

Ces classes seront particulièrement bien accueillies par les développeurs qui ont connu l’extrême complexité de créer un contrôle lié aux données polyvalent qui gère sa propre collection de données et se restaure correctement à partir du viewstate (état d’affichage). Vous voulez un exemple parlant ? Alors, lisez ce qui suit.


3. Points d’analyse

L'ASP.NET Developer Center (Centre de ressources pour les développeurs ASP.NET) a publié ces derniers mois des articles sur deux contrôles liés aux données ASP.NET 1.1 : RssFeed et DetailsView (  Building DataBound Templated Custom ASP.NET Server Controls et  A DetailsView Control for ASP.NET 1.x , respectivement). Si vous examinez en profondeur le code des deux contrôles, vous verrez qu’ils utilisent des techniques spéciales à différents niveaux. Par exemple, ils recréent l’arborescence du contrôle à partir du viewstate quand une publication (qui n’est pas sous la juridiction du contrôle) est faite vers la page ; ils exposent une collection d’éléments dans une classe de collections personnalisée ; ils permettent de styliser des éléments et ils prennent en charge un certain nombre de sources de saisie. Pour chacune de ces fonctionnalités, dans ASP.NET 1.1, vous devez écrire du code et, plus important encore, vous devez l’écrire dans un certain ordre, en écrasant des méthodes de base particulières et en observant à la lettre les instructions et les suggestions de la documentation et de l’excellent ouvrage cité ci-avant,  Developing Microsoft ASP.NET Server Controls and Components.

Dans ASP.NET 2.0, une grande partie du code de « plomberie » utilisé dans les deux exemples de contrôles est intégré dans les classes de base codées en dur du tableau 1. La comparaison et la mise en contraste des contrôles liés aux données dans ASP.NET 1.1 et 2.0 qui suivent sont axées sur les deux points suivants :

  • Le mécanisme général de liaisons des données et les différents types de sources de données

  • La gestion des collections et du viewstate

La liste n’est sans doute pas exhaustive, mais elle suffit à fournir une vue d’ensemble du développement des contrôles. Vous serez agréablement surpris de voir le peu de code nécessaire pour développer des contrôles personnalisés très riches.


4. Mécanisme de liaison de données

Pour créer un nouveau contrôle dépendant dans ASP.NET 2.0, vous devez commencer par choisir la classe qui vous convient le mieux. Ce choix ne se limite pas aux classes relativement vides comme Control et WebControl, voire ListControl. Examinons les nouvelles classes qui nous sont proposées. La classe BaseDataBoundControl est la racine de toutes les classes de contrôles liés aux données. Elle définit les propriétés DataSource et DataSourceID et valide le contenu qui leur est affecté. La propriété DataSource accepte des objets énumérables obtenus et affectés selon la méthode d'ASP.NET 1.x.

Mycontrol1.DataSource = dataSet; Mycontrol1.DataBind(); 

La propriété DataSourceID est une chaîne qui fait référence à l’identificateur d’un composant de source de données liée. Une fois qu’un contrôle est lié à une source de données, toute interaction ultérieure entre les deux (lecture et écriture) est gérée en dehors du contrôle et de l’affichage. C’est à la fois une bonne et une mauvaise chose. Une bonne (même excellente) chose, parce que cela vous permet de supprimer une grande quantité de code. La structure ASP.NET garantit l’exécution de code de bonne qualité, écrit suivant des meilleures pratiques reconnues. Vous êtes plus efficace parce que vous créez des pages plus rapidement tout en ayant la certitude qu’aucun bogue ne peut venir se glisser dans votre travail. Si vous n’aimez pas cette assistance (celle dont beaucoup de développeurs ASP.NET 1.x se sont plaints), vous pouvez toujours programmer à l’ancienne, c’est-à-dire en passant par la propriété DataSource et la méthode DataBind. Mais même dans ce cas de figure, la classe de base vous simplifie certaines pratiques, bien que l’économie de code soit moins intéressante.

La classe DataBoundControl est celle que vous utiliserez pour créer des contrôles liés aux données personnalisés standards qui n’ont pas grand-chose en commun avec les contrôles existants. Si vous devez gérer votre propre collection d’éléments de données, gérer un viewstate et des styles, et créer une interface utilisateur simple mais sur mesure, cette classe est un bon point de départ. Plus intéressant encore, la classe DataBoundControl connecte le contrôle aux composants de source de données et masque toute différence entre les sources de données énumérables et les composants spécifiques au niveau de l’API. En résumé, lorsque vous héritez de cette classe, vous devez uniquement surcharger une méthode qui reçoit une collection de données indépendamment de leur origine : objet DataSet ou composant de source de données plus récent.

Ce point représente un changement clé dans l’architecture, c’est pourquoi il convient de s’y attarder quelque peu.

La classe BaseDataBoundControl se substitue à la méthode DataBind (définie à l’origine sur Control) et lui fait appeler la méthode PerformSelect, marquée comme protégée et abstraite. Comme son nom l’indique, la méthode PerformSelect sert à extraire une collection valide de données afin de permettre la liaison. La méthode est protégée car elle contient des détails d’implémentation, et abstraite (MustInherit, en jargon Visual Basic) car son comportement ne peut être précisé que par des classes dérivées telles que DataBoundControl.

Mais que fait DataBoundControl pour se substituer à PerformSelect ?

Elle se connecte à l’objet de source de données pour obtenir la vue par défaut. Un objet de source de données (par exemple, un contrôle comme SqlDataSource ou ObjectDataSource) exécute sa commande select et renvoie la collection qui en résulte. La méthode protégée qui effectue l’extraction de données, GetData, est suffisamment intelligente pour vérifier aussi la propriété DataSource. Si elle n’est pas vide, l’objet lié est intégré dans un objet d’affichage (ou de vue) de source de données créé dynamiquement et renvoyé. C’est seulement à l’étape suivante que vous intervenez en tant que développeur de contrôles. En effet, jusqu’ici, les classes de base ont automatiquement extrait des données soit d’objets ADO.NET, soit de composants de source de données. Cette étape dépend de la finalité du contrôle. C’est ici que la méthode substituable PerformDataBinding entre en scène. L’extrait de code suivant montre la méthode telle qu’elle est implémentée dans la classe DataBoundControl. Notez que le paramètre IEnumerable que la structure transmet à la méthode contient uniquement les données à lier, indépendamment de leur origine.

protected virtual void PerformDataBinding(IEnumerable data) { } 

Dans un contrôle lié aux données personnalisé, il vous suffit de remplacer cette méthode et de compléter toute collection spécifique au contrôle, comme la collection Items de nombreux contrôles de liste (CheckBoxList, par exemple). Le rendu de l’interface utilisateur du contrôle a lieu dans la méthode Render ou dans CreateChildControls, selon la nature du contrôle. La méthode Render convient aux contrôles de liste, tandis que CreateChildControls est la solution idéale pour les contrôles composites.

Une chose reste à expliquer : qui démarre le processus de liaison de données ? Dans ASP.NET 1.x, la liaison de données nécessite un appel explicite à la méthode DataBind. Dans ASP.NET 2.0, il en va de même si vous liez des données à des contrôles au moyen de la propriété DataSource. Par contre, si vous utilisez des composants de source de données en passant par la propriété DataSourceID, mieux vaut éviter cette méthode. Le processus de liaison de données sera automatiquement déclenché par le gestionnaire d’événements interne OnLoad, défini dans la classe DataBoundControl, comme l’illustre le pseudo-code suivant.

protected override void OnLoad(EventArgs e) 
{ this.ConnectToDataSourceView(); if (!Page.IsPostBack) 
base.RequiresDataBinding = true; base.OnLoad(e); } 

À chaque fois que le contrôle est chargé dans la page (publication ou première fois), les données sont extraites et liées. C’est la source de données qui décide de redemander une requête ou d’utiliser les données mises en cache.

Si la page est affichée pour la première fois, la propriété RequiresDataBinding est également activée pour demander la liaison de données. Lorsque la valeur assignée est « true », le définisseur de la propriété invoque DataBind en interne. Le pseudo-code ci-dessous illustre l’implémentation interne qui définit RequiresDataBinding.

protected void set_RequiresDataBinding(bool value) { 
if (value && (DataSourceID.Length > 0)) DataBind(); 
else _requiresDataBinding = value; 
} 

Comme vous pouvez le constater, l’appel automatique de la méthode DataBind à des fins de compatibilité ascendante n’a lieu que si la propriété DataSourceID n’est pas vide, c’est-à-dire si vous dépendez d’un contrôle de source de données ASP.NET 2.0. Donc, si vous appelez aussi DataBind explicitement, vous aurez une double liaison de données.

Remarque : les deux propriétés, DataSource et DataSourceID, ne peuvent pas être définies toutes les deux. Si c’est le cas, ASP.NET 2.0 renvoie une exception d’opération non valide.

Pour terminer, la méthode protégée EnsureDataBound mérite notre attention. Définie sur la classe BaseDataBoundControl, cette méthode vérifie que le contrôle a été lié aux bonnes données. Si RequiresDataBinding a la valeur « true », la méthode invoque DataBind, comme dans l’exemple de code suivant.

protected void EnsureDataBound() { 
if (RequiresDataBinding && (DataSourceID.Length > 0)) DataBind(); 
} 

Si vous avez déjà écrit des contrôles liés aux données complexes et sophistiqués, vous aurez sans doute deviné ce dont je veux parler. Dans ASP.NET 1.x, l’architecture d’un contrôle lié aux données est normalement conçue pour que le contrôle crée sa propre interface utilisateur avec accès total à la source de données ou sur la base du viewstate. Lorsque le contrôle doit pouvoir gérer ses propres événements de publication (par exemple, imaginez un contrôle DataGrid avec pagination), les deux options précédemment citées sont opposées. Dans ASP.NET 1.x, ces contrôles (repensez au DataGrid) n’avaient qu’une seule issue : envoyer les événements à la page d’accueil pour actualisation. Cette approche mène au surplus de code bien connu dans les pages ASP.NET 1.x, un problème résolu par l’arrivée des composants de source de données.

Dans ASP.NET 2.0, vous donnez à la propriété RequiresDataBinding la valeur « true » lorsqu’un événement exigeant une liaison de données se produit dans le cycle de vie d’un contrôle. Cette même propriété déclenche le mécanisme de liaison de données qui recrée une version mise à jour de l’infrastructure interne du contrôle. Le gestionnaire d’événement intégré OnLoad établit également la connexion entre le contrôle et la source de données. Pour être réellement efficace, cette technique est tributaire de contrôles de source de données intelligents capables de mettre leurs données en cache quelque part. Le contrôle SqlDataSource, par exemple, s’accompagne de plusieurs propriétés qui peuvent stocker tout résultat dépendant affecté au cache ASP.NET pour une période déterminée.


5. Contrôles de liste

Les contrôles liés aux données sont souvent des contrôles de liste. Un contrôle de liste crée sa propre interface utilisateur en répétant le même modèle pour chaque élément de données liées dans les limites du cadre principal du contrôle. Par exemple, un contrôle CheckBoxList répète simplement un contrôle CheckBox pour chaque élément de données liées. De même, un contrôle DropDownList se répète par le biais de sa source de données et crée un nouvel élément <option> au sein d’une balise <select>. Parallèlement aux contrôles de liste, ASP.NET intègre également des contrôles itératifs. En quoi sont-ils différents ?

Les contrôles de liste et les contrôles itératifs diffèrent sur le plan du niveau de personnalisation autorisé sur le modèle répété appliqué à chaque élément de données. À l’instar d’un contrôle CheckBoxList, un contrôle Repeater s’immisce dans les éléments de données liées et y applique le modèle défini par l’utilisateur. Le contrôle Repeater (et le contrôle DataList, encore plus sophistiqué) est extrêmement flexible, mais ne favorise pas vraiment l’écriture de code modulaire et structuré en couches. Pour utiliser un contrôle Repeater, vous devez définir des modèles dans la page (ou dans un contrôle utilisateur externe) et consommer des propriétés liées aux données dans la source ASPX. C’est rapide, efficace et parfois nécessaire, mais c’est loin d’être propre et élégant.

Dans ASP.NET 1.x, tous les contrôles de liste héritent de la classe ListControl, la seule du tableau 1 déjà définie dans 1.x. Passons en mode code et commençons à nous entraîner à créer des contrôles liés aux données dans ASP.NET 2.0. Je commencerai par créer un contrôle HeadlineList qui assure le rendu de deux lignes de texte liées à des données pour chaque élément de données. Mon contrôle sera également doté de quelques options de mise en forme telles que le rendu vertical ou horizontal.


6. Exemple de contrôle HeadlineList

Comme indiqué précédemment, la classe ListControl est la classe de base pour tous les contrôles de liste dans ASP.NET 1.x et 2.0. Fort heureusement, le contrôle HeadlineList, écrit ici pour ASP.NET 2.0, peut être très facilement transposé dans ASP.NET 1.x. Je ne sais pas pourquoi, mais lorsqu’il s’agit de créer une liste de titres, la première idée qui me vient à l’esprit est d’utiliser un Repeater. En effet, ce contrôle facilite vraiment les choses.

<asp:Repeater runat="server"> 
	<HeaderTemplate> <table> </HeaderTemplate> 
	<ItemTemplate> 
		<tr>
			<td> <%# DataBinder.Eval(Container.DataItem, "Title") %> 
				<hr> <%# DataBinder.Eval(Container.DataItem, "Abstract") %> 
			</td>
		</tr> 
	</ItemTemplate> 
	<FooterTemplate> </table> </FooterTemplate> 
</asp:Repeater>

Qu’est-ce qui ne va pas dans ce code ? Ou plus précisément, que peut-on améliorer ?

Remarque : dans ASP.NET 2.0, vous pouvez remplacer DataBinder.Eval(Container.DataItem, champ) par une expression plus courte qui bénéficie d’une nouvelle méthode publique, Eval, sur la classe Page. La nouvelle expression ressemble à Eval(champ). En interne, Eval appelle la méthode Eval statique sur la classe DataBinder et détermine le contexte de liaison qu’il convient d’utiliser.

Les noms des champs sont codés en dur dans la page ASPX. Ils sont réutilisables, mais seulement par copier-coller. Plus vous ajoutez du code pour étoffer le comportement du Repeater, plus vous mettez en danger la solution et sa réutilisabilité dans d’autres pages ou projets. Si un contrôle de liste de titres est vraiment ce qu’il vous faut, essayez plutôt l’approche suivante à la place.

public class HeadlineList : ListControl, IrepeatInfoUser { : } 

ListControl est la classe de base pour les contrôles de liste (même famille que CheckBoxList, DropDownList, et consorts). IRepeatInfoUser est l’interface peu connue que la plupart de ces contrôles implémentent pour assurer le rendu des colonnes et des lignes, horizontalement ou verticalement. Notez que ListControl et IRepeatInfoUser existent aussi dans ASP.NET 1.x, et fonctionnement pratiquement de la même manière que dans 2.0.

Un contrôle de liste est construit autour d’un contrôle à répéter. Ce contrôle (ou graphe de contrôles) est une propriété de classe qui est instanciée lors du chargement pour économiser des ressources système. Voici l’implémentation de la propriété ControlToRepeat privée.

private Label _controlToRepeat;

private Label ControlToRepeat

{ get { 
	if (_controlToRepeat == null) { 
		_controlToRepeat = new Label(); 
		_controlToRepeat.EnableViewState = false; Controls.Add(_controlToRepeat); 
	} 
	return _controlToRepeat; 
      } 
} 

Dans ce cas, le contrôle à répéter - à savoir le titre - est un Label instancié à la première lecture. Le contrôle HeadlineList devrait aussi offrir aux utilisateurs une manière d’influencer l’apparence au moyen de plusieurs propriétés RepeatLayout, RepeatColumns et RepeatDirection. Ces propriétés sont définies sur plusieurs contrôles de liste standards, et en cela, elles ne représentent aucune nouveauté pour les développeurs. Leur implémentation est similaire, comme l’illustre le code ci-dessous.

public virtual RepeatDirection RepeatDirection { 
	get { 
		object o = ViewState["RepeatDirection"]; 
		if (o != null) return (RepeatDirection) o; 
		return RepeatDirection.Vertical; 
	} 
	set { 
		ViewState["RepeatDirection"] = value; 
	} 
} 

Le dernier pan de code à écrire pour achever le contrôle HeadlineList concerne le rendu. L’interface IRepeatInfoUser compte plusieurs propriétés qui vous permettent de contrôler le processus de rendu, comme les propriétés booléennes HasHeader, HasFooter et HasSeparator. Elles s’implémentent comme les propriétés ordinaires et vous les utilisez, au besoin, dans la méthode d’interface RenderItem.

public void RenderItem(ListItemType itemType, int repeatIndex, RepeatInfo repeatInfo, 
HtmlTextWriter writer) 
{ 
	string format = "<b>{0}</b><hr style='solid 1px black'>{1}"; 
	Label lbl = ControlToRepeat; 
	int i = repeatIndex; 
	lbl.ID = i.ToString(); string text = String.Format(format, Items[i].Text, Items[i].Value); 
	lbl.Text = text; lbl.RenderControl(writer); 
} 

La méthode RenderItem a la responsabilité finale sur le résultat envoyé à la page. Elle saisit le contrôle à répéter et génère les balises. RenderItem est appelée à partir de Render.

protected override void Render(HtmlTextWriter writer) { 
	if (Items.Count >0) { 
		RepeatInfo ri = new RepeatInfo(); 
		Style controlStyle = (base.ControlStyleCreated ? base.ControlStyle : null); 
		ri.RepeatColumns = RepeatColumns; 
		ri.RepeatDirection = RepeatDirection; 
		ri.RepeatLayout = RepeatLayout; 
		ri.RenderRepeater(writer, this, controlStyle, this); 
	} 
} 

RepeatInfo est un objet d’assistance spécifiquement conçu pour créer de nouveaux contrôles en répétant des graphes de contrôles existants. Et c’est tout ce qu’il vous faut. Arrangeons un exemple de page et testons le contrôle.

<expo:headlinelist id="HeadlineList1" runat="server" repeatlayout="Table" 
repeatdirection="Vertical" repeatcolumns="2" datatextfield="LastName" 
datavaluefield="Notes" /> 

La figure 2 montre le contrôle en action.


Figure 2. Contrôle lié aux données HeadlineList

Le contrôle se comporte bien au stade de conception sans nouvelle injection de code. Mais le meilleur effet secondaire n’est pas une simple aide au stade de la conception. Personnellement, je trouve tout simplement génial qu’il fonctionne avec les objets de source de données ADO.NET (par exemple DataTable ou DataSet) et les composants de source de données tels que SqlDataSource. Vous prenez ce code, vous le compilez dans un projet ASP.NET 1.x, et il fonctionne avec des sources de données de type IEnumerable. Si vous le compilez dans un projet ASP.NET 2.0, il fonctionnera aussi, de manière identique, avec des objets de source de données.

Que peut-on conclure de tout cela ?

Dans ASP.NET 1.x, la classe ListControl est une exception, certes agréable, mais une exception quand même. Dans ASP.NET 2.0, vous pouvez créer n’importe quel contrôle lié aux données en suivant une approche similaire, à la fois simple et efficace. Ce faisant, vous tirez profit des nouvelles classes de base qui intègrent une grande partie de la complexité et du code des meilleures pratiques connues.


7. Gestion des collections personnalisées

La classe ListControl est une classe beaucoup trop spécialisée qui effectue la liaison de données selon une méthode fixe sur laquelle vous n’exercez aucun contrôle, sauf si vous écrasez des méthodes telles que PerformSelect, OnDataBinding et PerformDataBinding. Elle fournit également une propriété de collection Items prédéfinie. Penchons-nous sur la liaison de données dans ASP.NET 2.0 à un niveau inférieur en créant un contrôle ButtonList qui :

  • utilise une classe de collection personnalisée pour stocker les éléments constituants ;

  • gère le viewstate de manière personnalisée.

Le contrôle ButtonList est un autre contrôle de liste qui crée un bouton de commande pour chaque élément de données liées. Vous pouvez le faire hériter de ListControl. Vous pouvez même prendre le code source de HeadlineList, remplacer Label par Button et ça fonctionnera aussi. J’utilise une approche différente cette fois-ci pour illustrer le comportement de DataBoundControl. Pour plus de simplicité, je vais aussi « ignorer » l’interface IrepeatInfoUser.

public class ButtonList : System.Web.UI.WebControls.DataBoundControl { : } 

Chaque bouton est caractérisé par une légende et un nom de commande puisés, dans la source de données liée, au moyen de deux propriétés personnalisées, nommées DataTextField et DataCommandField. Vous pouvez facilement ajouter des propriétés du même genre pour prévoir des info-bulles liées aux données, voire des URL.

public virtual string DataCommandField { 
	get { object o = ViewState["DataCommandField"]; 
		if (o == null) return ""; return (string)o; 
	} 
	set { 
		ViewState["DataCommandField"] = value; 
	} 
}

Toutes les informations retrouvées sur chaque bouton lié aux données sont placées dans une collection d’objets personnalisés, exposés au moyen de la propriété Items (nom standard et conventionnel, mais tout à fait arbitraire).

[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] 
[PersistenceMode(PersistenceMode.InnerProperty)] 
public virtual ButtonItemCollection Items { 
	get { 
		if (_items == null) { 
			_items = new ButtonItemCollection(); 
			if (base.IsTrackingViewState) _items.TrackViewState(); 
		} 
		return _items; 
	} 
} 

La collection Items est une instance de la classe personnalisée ButtonItemCollection, elle-même collection d’objets ButtonItem. La classe ButtonItem contient uniquement les informations clés concernant un bouton lié, à savoir les propriétés Text et CommandName, plus deux constructeurs et la méthode ToString. La classe ButtonItem est l’équivalent de la classe ListItem en tant que contrôle de liste générique. Voici un exemple.

public class ButtonItem { 
	private string _text; 
	private string _command; 
	public ButtonItem(string text, string command) { _text = text; _command = command; } 
	public string Text { 
		get {return _text;} 
		set {_text = value;}	
	} 
	public string CommandName { 
		get { return _command; } 
		set { _command = value; } 
	} 
	public override string ToString() { return "Button [" + Text + "]"; } 
} 

Alors, comment feriez-vous pour créer une collection d’objets ButtonItem ? Dans ASP.NET 1.x, vous devriez créer une collection personnalisée qui hérite de CollectionBase et se substitue à deux méthodes au moins. Cela dit, une collection personnalisée n’est que l’emballage d’un objet ArrayList qui ne présente aucun avantage réel en termes de vitesse d’accès. En fait, un transtypage (cast) est toujours nécessaire. Les génériques de .NET 2.0 constituent un vrai tournant à ce niveau. Pour créer des objets ButtonItem, vous avez besoin du code suivant :

public class ButtonItemCollection : Collection<ButtonItem> { } 

Ce code est plus performant en raison du travail que le compilateur fait à l’arrière-plan. Le contrôle ButtonList ne requiert que deux méthodes substituées : Render et PerformDataBinding. Render part du principe que la collection Items n’est pas vide, donc elle ne fait que répéter et produire le code de balisage.

protected override void Render(HtmlTextWriter writer) { 
	for(int i=0; i<Items.Count; i++) { 
		ButtonItem item = Items[i]; 
		Button btn = new Button(); 
		btn.Text = item.Text; 
		btn.CommandName = item.CommandName; 
		btn.RenderControl(writer); 
	} 
}

Pourquoi la collection Items est-elle si importante ? En fait, son action est double. Tout d’abord, elle vous permet de compléter le contrôle de liste au moyen d’éléments ajoutés manuellement. Ensuite, une fois que la collection est enregistrée dans le viewstate, vous pouvez régénérer l’interface utilisateur du contrôle lors de la publication sans liaison aux données. Où et par quoi la collection Items est-elle complétée lors de la liaison de données ? C’est ce que la méthode PerformDataBinding vous permet de déterminer. La méthode se saisit d’une liste énumérable (indépendamment de la source d’origine) et utilise les éléments qu’elle contient pour compléter la collection Items.

protected override void PerformDataBinding(IEnumerable dataSource) { 
	base.PerformDataBinding(dataSource); 
	string textField = DataTextField; 
	string commandField = DataCommandField; 
	if (dataSource != null) { 
		foreach (object o in dataSource) { 
			ButtonItem item = new ButtonItem(); 
			item.Text = DataBinder.GetPropertyValue(o, textField, null); 
			item.CommandName = DataBinder.GetPropertyValue(o, DataCommandField, null); 
			Items.Add(item); 
		} 
	} 
} 

Dès qu’une liaison de données est nécessaire, cette méthode fait en sorte que la collection Items soit complétée. Que se passe-t-il en cas de publication ? La collection Items doit être reformée à partir du viewstate. Pour doter la collection personnalisée de cette capacité, vous devez utiliser les méthodes sur l’interface IStateManager. Voici les méthodes clés de cette interface :

public void LoadViewState(object state) { 
	if (state != null) { 
		Pair p = (Pair) state; 
		Clear(); 
		string[] rgText = (string[])p.First; string[] rgCommand = (string[])p.Second; 
		for (int i = 0; i < rgText.Length; i++) Add(new ButtonItem(rgText[i], rgCommand[i])); 
	} 
} 
public object SaveViewState() { 
	int numOfItems = Count; 
	object[] rgText = new string[numOfItems]; 
	object[] rgCommand = new string[numOfItems]; 
	for (int i = 0; i < numOfItems; i++) { 
		rgText[i] = this[i].Text; 
		rgCommand[i] = this[i].CommandName; 
	} 
	return new Pair(rgText, rgCommand); 
} 

La classe se sérialise dans le viewstate au moyen de l’objet Pair, qui est une sorte de groupe à deux positions. Vous créez deux ensembles d’objets pour chaque bouton : un pour le texte, l’autre pour le nom de commande. Les deux ensembles sont ensuite compressés dans une paire qui est insérée dans le viewstate. Lorsque le viewstate est restauré, la paire est déballée et la collection Items est complétée à nouveau au moyen des informations stockées précédemment. Cette approche est préférable à la sérialisation de la classe ButtonItem en raison des moins bonnes performances (en espace et en temps) du formatage binaire classique

Cependant, ajouter la prise en charge du viewstate par la collection n’est pas suffisant. Le contrôle ButtonList doit aussi être amélioré pour mettre à profit les capacités de sérialisation de la collection. Pour ce faire, substituez les méthodes LoadViewState et SaveViewState sur la classe du contrôle.

protected override void LoadViewState(object savedState) { 
	if (savedState != null) { 
		Pair p = (Pair) savedState; 
		base.LoadViewState(p.First); 
		Items.LoadViewState(p.Second); 
	} 
	else base.LoadViewState(null); 
} protected override object SaveViewState() { 
	object baseState = base.SaveViewState(); 
	object itemState = Items.SaveViewState(); 
	if ((baseState == null) && (itemState == null)) return null; 
		return new Pair(baseState, itemState); 
} 

Le viewstate du contrôle se compose de deux éléments : le viewstate du contrôle par défaut et la collection Items. Les deux objets sont associés dans un objet Pair. Vous pouvez aussi utiliser des objets Triplet (ensembles de trois objets) ou composer le nombre souhaité d’objets en combinant des Pair et des Triplet.

Les collections personnalisées conçues de cette façon donnent également satisfaction au moment de la conception. L’éditeur de collection par défaut intégré dans Visual Studio 2005 reconnaît la collection et affiche une boîte de dialogue similaire à celle de la figure 3.


Figure 3. Collection ButtonList au moment de la conception

Il est à noter que dans ASP.NET 2.0, certains contrôles liés aux données vous offrent la possibilité de séparer les éléments liés aux données des éléments ajoutés par programme dans la collection Items. La propriété booléenne AppendDataBoundItems contrôle cet aspect de l’interface de programmation du contrôle. La propriété est définie sur la classe ListControl (et non DataBoundControl) et a la valeur « false » par défaut.


8. Un mot sur les contrôles composites

La classe CompositeDataBoundControl est le point de départ pour la création de contrôles composites, qui je parie, sont ceux auxquels vous pensez lorsque vous pensez « contrôles liés aux données ». Un contrôle composite doit :

  • agir comme conteneur de dénomination ;

  • créer sa propre interface utilisateur au moyen de la méthode CreateChildControls;

  • implémenter une logique particulière pour restaurer sa hiérarchie d’éléments enfants après publication.

Ce dernier point est illustré à merveille dans l’  ouvrage de Nikhil Kothari et implémenté dans tous les contrôles embarqués de ASP.NET 1.x. Si vous n’aviez pas compris ce concept, bonne nouvelle : vous pouvez l’oublier complètement. En effet, tout est désormais codé en dur dans la classe CompositeDataBoundControl. Le principal aspect à prendre en compte est la conception des enfants de votre contrôle. Pour ce faire, vous devez surcharger une nouvelle méthode définie comme suit :

protected abstract int CreateChildControls( IEnumerable dataSource, bool dataBinding);

La classe CompositeDataBoundControl hérite de la classe DataBoundControl. Par conséquent, la majeure partie de ce qui est dit sur les collections, la liaison et le viewstate dans cet article s’applique aussi aux contrôles composites.


Conclusion

La liaison de données et les contrôles liés aux données représentaient un grand bond en avant dans ASP.NET 1.x. Malheureusement, un certain nombre de points restaient à éclaircir et plusieurs questions restaient sans réponse. Le livre de Nikhil Kothari est un excellent guide digne de foi pour tous les développeurs. ASP.NET 2.0 transforme une partie des meilleures pratiques dans le livre (déjà en grande partie implémentées sous le capot d’ASP.NET 1.x) en classes réutilisables et en un nouveau modèle objet pour les contrôles liés aux données.

Cet article met en lumière les principales différences entre ASP.NET 1.x et ASP.NET 2.0, et décrit la manière dont elles vont influencer le développement au moyen de quelques exemples pratiques. En regardant plus loin, je dirais qu’il faudrait aussi examiner les styles et les thèmes dans le développement de contrôles dans ASP.NET 2.0. Mais ce point fera peut-être l’objet d’un autre article dans un futur proche. Tenez-vous au courant.


Bibliographie

  • Nikhil Kothari et Vandana Datye,  Developing Microsoft ASP.NET Server Controls and Components

  • Dino Esposito,  Introducing Microsoft ASP.NET 2.0


À propos de l’auteur

Dino Esposito est un consultant italien qui travaille aussi comme instructeur pour  Wintellect. Auteur de  Programming Microsoft ASP.NET et, plus récemment, de  Introducing Microsoft ASP.NET 2.0 (Microsoft Press), il consacre la majeure partie de son temps à donner des cours sur ASP.NET et ADO.NET et à participer à des conférences en qualité d’orateur. Visitez le blog de Dino sur  http://weblogs.asp.net/despos.


© 2009 Microsoft Corporation. Tous droits réservés. Conditions d'utilisation | Marques | Confidentialité
Page view tracker