Contrôles web de données imbriquées (C#)
par Scott Mitchell
Dans ce tutoriel, nous allons découvrir comment utiliser un répéteur imbriqué dans un autre répéteur. Les exemples illustrent comment remplir le répéteur interne de manière déclarative et programmatique.
Introduction
En plus du code HTML statique et de la syntaxe de liaison de données, les modèles peuvent également inclure des contrôles web et des contrôles utilisateur. Ces contrôles Web peuvent avoir leurs propriétés affectées via la syntaxe déclarative de liaison de données, ou sont accessibles par programmation dans les gestionnaires d’événements côté serveur appropriés.
En incorporant des contrôles dans un modèle, l’apparence et l’expérience utilisateur peuvent être personnalisées et améliorées. Par exemple, dans le didacticiel Utilisation de TemplateFields dans le contrôle GridView , nous avons vu comment personnaliser l’affichage de GridView en ajoutant un contrôle Calendar dans un TemplateField pour afficher la date d’embauche d’un employé ; Dans les didacticiels Ajout de contrôles de validation aux interfaces d’édition et d’insertion et Personnalisation de l’interface de modification de données , nous avons vu comment personnaliser les interfaces d’édition et d’insertion en ajoutant des contrôles de validation, des TextBoxes, des DropDownLists et d’autres contrôles Web.
Les modèles peuvent également contenir d’autres contrôles web de données. Autrement dit, nous pouvons avoir une DataList qui contient un autre DataList (ou Repeater, GridView ou DetailsView, etc.) dans ses modèles. Le défi avec une telle interface est de lier les données appropriées au contrôle web de données interne. Il existe plusieurs approches différentes, allant des options déclaratives utilisant ObjectDataSource aux options programmatiques.
Dans ce tutoriel, nous allons découvrir comment utiliser un répéteur imbriqué dans un autre répéteur. Le répéteur externe contient un élément pour chaque catégorie de la base de données, affichant le nom et la description de la catégorie. Chaque répéteur interne d’élément de catégorie affiche des informations pour chaque produit appartenant à cette catégorie (voir la figure 1) dans une liste à puces. Nos exemples illustrent comment remplir le répéteur interne de manière déclarative et programmatique.
Figure 1 : Chaque catégorie, ainsi que ses produits, sont répertoriés (Cliquez pour afficher l’image en taille réelle)
Étape 1 : Création de la liste des catégories
Lors de la création d’une page qui utilise des contrôles Web de données imbriqués, je trouve utile de concevoir, créer et tester d’abord le contrôle Web de données le plus externe, sans même me soucier du contrôle imbriqué interne. Par conséquent, commençons par parcourir les étapes nécessaires pour ajouter un répéteur à la page qui répertorie le nom et la description de chaque catégorie.
Commencez par ouvrir la NestedControls.aspx
page dans le DataListRepeaterBasics
dossier et ajoutez un contrôle Repeater à la page, en définissant sa ID
propriété sur CategoryList
. À partir de la balise active du répéteur, choisissez de créer un objet ObjectDataSource nommé CategoriesDataSource
.
Figure 2 : Nommez le nouvel objetDataSource CategoriesDataSource
(cliquez pour afficher l’image en taille réelle)
Configurez ObjectDataSource pour qu’il extrait ses données de la méthode s de GetCategories
la CategoriesBLL
classe.
Figure 3 : Configurer ObjectDataSource pour utiliser la CategoriesBLL
méthode Class s GetCategories
(Cliquer pour afficher l’image en taille réelle)
Pour spécifier le contenu du modèle du répéteur, nous devons accéder à la vue Source et entrer manuellement la syntaxe déclarative. Ajoutez un ItemTemplate
qui affiche le nom de catégorie dans un <h4>
élément et la description de catégorie dans un élément paragraph (<p>
). En outre, nous allons séparer chaque catégorie avec une règle horizontale (<hr>
). Après avoir apporté ces modifications, votre page doit contenir une syntaxe déclarative pour repeater et ObjectDataSource similaire à ce qui suit :
<asp:Repeater ID="CategoryList" DataSourceID="CategoriesDataSource"
EnableViewState="False" runat="server">
<ItemTemplate>
<h4><%# Eval("CategoryName") %></h4>
<p><%# Eval("Description") %></p>
</ItemTemplate>
<SeparatorTemplate>
<hr />
</SeparatorTemplate>
</asp:Repeater>
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
OldValuesParameterFormatString="original_{0}"
SelectMethod="GetCategories" TypeName="CategoriesBLL">
</asp:ObjectDataSource>
La figure 4 montre notre progression lorsqu’elle est consultée via un navigateur.
Figure 4 : Le nom et la description de chaque catégorie sont répertoriés, séparés par une règle horizontale (cliquez pour afficher l’image en taille réelle)
Étape 2 : Ajout du répéteur de produit imbriqué
Une fois la liste de catégorie terminée, notre tâche suivante consiste à ajouter un répéteur aux CategoryList
s ItemTemplate
qui affiche des informations sur ces produits appartenant à la catégorie appropriée. Il existe plusieurs façons de récupérer les données de ce répéteur interne, dont deux que nous allons explorer sous peu. Pour l’instant, nous allons simplement créer le répéteur de produits dans le CategoryList
répéteur .ItemTemplate
Plus précisément, laissez le répétiteur de produit afficher chaque produit dans une liste à puces avec chaque élément de liste, y compris le nom et le prix du produit.
Pour créer ce répéteur, nous devons entrer manuellement la syntaxe déclarative et les modèles du répéteur interne dans le CategoryList
s ItemTemplate
. Ajoutez le balisage suivant dans les CategoryList
répétitifs ItemTemplate
:
<asp:Repeater ID="ProductsByCategoryList" EnableViewState="False"
runat="server">
<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li><strong><%# Eval("ProductName") %></strong>
(<%# Eval("UnitPrice", "{0:C}") %>)</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
Étape 3 : Liaison du Category-Specific Products au répéteur ProductsByCategoryList
Si vous visitez la page via un navigateur à ce stade, votre écran ressemblera à celui de la figure 4, car nous n’avons pas encore lié les données au répéteur. Il existe plusieurs façons de récupérer les enregistrements de produits appropriés et de les lier au répéteur, certains plus efficaces que d’autres. Le main défi ici est de récupérer les produits appropriés pour la catégorie spécifiée.
Les données à lier au contrôle Repeater interne sont accessibles de manière déclarative, par le biais d’un ObjectDataSource dans le CategoryList
repeater s ItemTemplate
, ou par programmation, à partir de la page code-behind ASP.NET page. De même, ces données peuvent être liées au répéteur interne soit de manière déclarative , par le biais de la propriété du DataSourceID
répéteur interne, soit par le biais de la syntaxe de liaison de données déclarative, soit par programmation en référençant le répéteur interne dans le CategoryList
gestionnaire d’événements du ItemDataBound
répéteur, en définissant sa DataSource
propriété par programmation et en appelant sa DataBind()
méthode. Explorons chacune de ces approches.
Accès déclaratif aux données avec un contrôle ObjectDataSource et le gestionnaire d’événementsItemDataBound
Étant donné que nous avons largement utilisé ObjectDataSource tout au long de cette série de tutoriels, le choix le plus naturel pour accéder aux données pour cet exemple est de s’en tenir à ObjectDataSource. La ProductsBLL
classe a une GetProductsByCategoryID(categoryID)
méthode qui retourne des informations sur les produits qui appartiennent au spécifié categoryID
. Par conséquent, nous pouvons ajouter un ObjectDataSource aux répéteurs et le CategoryList
ItemTemplate
configurer pour accéder à ses données à partir de cette méthode de classe.
Malheureusement, le répétiteur n’autorise pas la modification de ses modèles via le mode Création. Nous devons donc ajouter manuellement la syntaxe déclarative pour ce contrôle ObjectDataSource. La syntaxe suivante montre les CategoryList
répéteurs après l’ajout de ItemTemplate
ce nouvel ObjetDataSource (ProductsByCategoryDataSource
) :
<h4><%# Eval("CategoryName") %></h4>
<p><%# Eval("Description") %></p>
<asp:Repeater ID="ProductsByCategoryList" EnableViewState="False"
DataSourceID="ProductsByCategoryDataSource" runat="server">
<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li><strong><%# Eval("ProductName") %></strong> -
sold as <%# Eval("QuantityPerUnit") %> at
<%# Eval("UnitPrice", "{0:C}") %></li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
<asp:ObjectDataSource ID="ProductsByCategoryDataSource" runat="server"
SelectMethod="GetProductsByCategoryID" TypeName="ProductsBLL">
<SelectParameters>
<asp:Parameter Name="CategoryID" Type="Int32" />
</SelectParameters>
</asp:ObjectDataSource>
Lorsque vous utilisez l’approche ObjectDataSource, nous devons affecter à la ProductsByCategoryList
propriété Repeater la DataSourceID
ID
valeur de l’ObjetDataSource (ProductsByCategoryDataSource
). Notez également que notre ObjectDataSource a un <asp:Parameter>
élément qui spécifie la categoryID
valeur qui sera passée à la GetProductsByCategoryID(categoryID)
méthode . Mais comment spécifier cette valeur ? Dans l’idéal, nous pourrions simplement définir la DefaultValue
propriété de l’élément à l’aide de la <asp:Parameter>
syntaxe de liaison de données, comme suit :
<asp:Parameter Name="CategoryID" Type="Int32"
DefaultValue='<%# Eval("CategoryID")' />
Malheureusement, la syntaxe de liaison de données n’est valide que dans les contrôles qui ont un DataBinding
événement. La Parameter
classe ne dispose pas d’un tel événement et, par conséquent, la syntaxe ci-dessus est illégale et génère une erreur d’exécution.
Pour définir cette valeur, nous devons créer un gestionnaire d’événements pour l’événement CategoryList
repeater.ItemDataBound
Rappelez-vous que l’événement ItemDataBound
se déclenche une fois pour chaque élément lié au répéteur. Par conséquent, chaque fois que cet événement se déclenche pour le répéteur externe, nous pouvons affecter la valeur actuelle CategoryID
au ProductsByCategoryDataSource
paramètre de CategoryID
ObjectDataSource.
Créez un gestionnaire d’événements pour l’événement CategoryList
repeater avec ItemDataBound
le code suivant :
protected void CategoryList_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.AlternatingItem ||
e.Item.ItemType == ListItemType.Item)
{
// Reference the CategoriesRow object being bound to this RepeaterItem
Northwind.CategoriesRow category =
(Northwind.CategoriesRow)((System.Data.DataRowView)e.Item.DataItem).Row;
// Reference the ProductsByCategoryDataSource ObjectDataSource
ObjectDataSource ProductsByCategoryDataSource =
(ObjectDataSource)e.Item.FindControl("ProductsByCategoryDataSource");
// Set the CategoryID Parameter value
ProductsByCategoryDataSource.SelectParameters["CategoryID"].DefaultValue =
category.CategoryID.ToString();
}
}
Ce gestionnaire d’événements commence par s’assurer que nous traitons un élément de données plutôt que l’en-tête, le pied de page ou l’élément de séparation. Ensuite, nous faisons référence au instance réel CategoriesRow
qui vient d’être lié au actuelRepeaterItem
. Enfin, nous faisons référence à ObjectDataSource dans et ItemTemplate
affectons sa CategoryID
valeur de paramètre au CategoryID
du actuel RepeaterItem
.
Avec ce gestionnaire d’événements, le ProductsByCategoryList
répéteur dans chaque RepeaterItem
est lié aux produits de la RepeaterItem
catégorie s. La figure 5 montre une capture d’écran de la sortie résultante.
Figure 5 : Répéteur externe Listes chaque catégorie ; l’élément interne Listes les produits de cette catégorie (cliquez pour afficher l’image en taille réelle)
Accès programmatique aux données Products by Category
Au lieu d’utiliser un ObjectDataSource pour récupérer les produits de la catégorie actuelle, nous pourrions créer une méthode dans notre classe code-behind ASP.NET page (ou dans le App_Code
dossier ou dans un projet bibliothèque de classes distinct) qui retourne l’ensemble approprié de produits lorsqu’il est passé dans un CategoryID
. Imaginez que nous avions une telle méthode dans notre ASP.NET classe code-behind de la page et qu’elle était nommée GetProductsInCategory(categoryID)
. Une fois cette méthode en place, nous pouvons lier les produits de la catégorie actuelle au répéteur interne à l’aide de la syntaxe déclarative suivante :
<asp:Repeater runat="server" ID="ProductsByCategoryList" EnableViewState="False"
DataSource='<%# GetProductsInCategory((int)(Eval("CategoryID"))) %>'>
...
</asp:Repeater>
La propriété repeater utilise la syntaxe de DataSource
liaison de données pour indiquer que ses données proviennent de la GetProductsInCategory(categoryID)
méthode . Étant donné que Eval("CategoryID")
retourne une valeur de type Object
, nous cassons l’objet en un Integer
avant de le transmettre à la GetProductsInCategory(categoryID)
méthode . Notez que le CategoryID
accessible ici via la syntaxe de liaison de données est dans CategoryID
le répéteur externe (CategoryList
), celui qui est lié aux enregistrements de la Categories
table. Par conséquent, nous savons que ne CategoryID
peut pas être une valeur de base de données NULL
, c’est pourquoi nous pouvons convertir aveuglément la Eval
méthode sans vérifier si nous traitons avec un DBNull
.
Avec cette approche, nous devons créer la GetProductsInCategory(categoryID)
méthode et lui faire récupérer l’ensemble de produits approprié en fonction du fourni categoryID
. Pour ce faire, nous pouvons simplement retourner le ProductsDataTable
retourné par la méthode s de GetProductsByCategoryID(categoryID)
la ProductsBLL
classe. Créons la GetProductsInCategory(categoryID)
méthode dans la classe code-behind de notre NestedControls.aspx
page. Pour ce faire, utilisez le code suivant :
protected Northwind.ProductsDataTable GetProductsInCategory(int categoryID)
{
// Create an instance of the ProductsBLL class
ProductsBLL productAPI = new ProductsBLL();
// Return the products in the category
return productAPI.GetProductsByCategoryID(categoryID);
}
Cette méthode crée simplement une instance de la ProductsBLL
méthode et retourne les résultats de la GetProductsByCategoryID(categoryID)
méthode. Notez que la méthode doit être marquée Public
ou Protected
; si la méthode est marquée Private
, elle ne sera pas accessible à partir du balisage déclaratif de la page ASP.NET.
Après avoir apporté ces modifications pour utiliser cette nouvelle technique, prenez un moment pour afficher la page via un navigateur. La sortie doit être identique à la sortie lors de l’utilisation de l’approche ObjectDataSource et ItemDataBound
du gestionnaire d’événements (reportez-vous à la figure 5 pour afficher une capture d’écran).
Notes
Il peut sembler occupé à créer la GetProductsInCategory(categoryID)
méthode dans la classe code-behind de la page ASP.NET. Après tout, cette méthode crée simplement un instance de la ProductsBLL
classe et retourne les résultats de sa GetProductsByCategoryID(categoryID)
méthode. Pourquoi ne pas simplement appeler cette méthode directement à partir de la syntaxe de liaison de données dans le répéteur interne, par exemple : DataSource='<%# ProductsBLL.GetProductsByCategoryID((int)(Eval("CategoryID"))) %>'
Bien que cette syntaxe ne fonctionne pas avec notre implémentation actuelle de la ProductsBLL
classe (étant donné que la GetProductsByCategoryID(categoryID)
méthode est une méthode instance), vous pouvez modifier ProductsBLL
pour inclure une méthode statique GetProductsByCategoryID(categoryID)
ou faire en sorte que la classe inclue une méthode statique Instance()
pour retourner une nouvelle instance de la ProductsBLL
classe.
Bien que de telles modifications éliminent le besoin de la GetProductsInCategory(categoryID)
méthode dans la classe code-behind de la page ASP.NET, la méthode de classe code-behind nous offre plus de flexibilité dans l’utilisation des données récupérées, comme nous le verrons sous peu.
Récupération de toutes les informations sur le produit en même temps
Les deux techniques pervious que nous avons examinées récupèrent ces produits pour la catégorie actuelle en effectuant un appel à la méthode s de la ProductsBLL
classe ( GetProductsByCategoryID(categoryID)
la première approche l’a fait par le biais d’un ObjectDataSource, la seconde par le biais de la GetProductsInCategory(categoryID)
méthode dans la classe code-behind). Chaque fois que cette méthode est appelée, la couche de logique métier appelle la couche d’accès aux données, qui interroge la base de données avec une instruction SQL qui retourne des lignes de la table dont CategoryID
le Products
champ correspond au paramètre d’entrée fourni.
Étant donné N catégories dans le système, cette approche appelle N + 1 à la requête de base de données 1 pour obtenir toutes les catégories, puis N appelle pour obtenir les produits spécifiques à chaque catégorie. Toutefois, nous pouvons récupérer toutes les données nécessaires en seulement deux appels de base de données pour obtenir toutes les catégories et un autre pour obtenir tous les produits. Une fois que nous avons tous les produits, nous pouvons filtrer ces produits afin que seuls les produits correspondant au actuel CategoryID
soient liés au répéteur interne de cette catégorie.
Pour fournir cette fonctionnalité, il nous suffit d’apporter une légère modification à la GetProductsInCategory(categoryID)
méthode dans notre ASP.NET classe code-behind de la page. Au lieu de retourner aveuglément les résultats de la ProductsBLL
méthode de classe s GetProductsByCategoryID(categoryID)
, nous pouvons d’abord accéder à tous les produits (s’ils n’ont pas déjà été consultés), puis retourner uniquement la vue filtrée des produits en CategoryID
fonction du passé.
private Northwind.ProductsDataTable allProducts = null;
protected Northwind.ProductsDataTable GetProductsInCategory(int categoryID)
{
// First, see if we've yet to have accessed all of the product information
if (allProducts == null)
{
ProductsBLL productAPI = new ProductsBLL();
allProducts = productAPI.GetProducts();
}
// Return the filtered view
allProducts.DefaultView.RowFilter = "CategoryID = " + categoryID;
return allProducts;
}
Notez l’ajout de la variable au niveau de la page, allProducts
. Cette méthode contient des informations sur tous les produits et est renseignée la première fois que la GetProductsInCategory(categoryID)
méthode est appelée. Après avoir vérifié que l’objet allProducts
a été créé et rempli, la méthode filtre les résultats de DataTable de sorte que seules les lignes dont CategoryID
la correspondance est spécifiée CategoryID
soient accessibles. Cette approche réduit le nombre d’accès à la base de données de N + 1 à deux.
Cette amélioration n’introduit aucune modification du balisage rendu de la page, et elle ne ramène pas moins d’enregistrements que l’autre approche. Il réduit simplement le nombre d’appels à la base de données.
Notes
On peut intuitivement raisonner que la réduction du nombre d’accès aux bases de données améliorerait assurément les performances. Toutefois, ce n’est peut-être pas le cas. Si vous avez un grand nombre de produits dont CategoryID
la valeur est NULL
, par exemple, l’appel à la GetProducts
méthode retourne un certain nombre de produits qui ne sont jamais affichés. En outre, le renvoi de tous les produits peut être inutile si vous affichez uniquement un sous-ensemble des catégories, ce qui peut être le cas si vous avez implémenté la pagination.
Comme toujours, lorsqu’il s’agit d’analyser les performances de deux techniques, la seule mesure surefire consiste à exécuter des tests contrôlés adaptés aux scénarios de cas courants de votre application.
Résumé
Dans ce tutoriel, nous avons vu comment imbriquer un contrôle Web de données dans un autre, en examinant plus précisément comment faire afficher un élément pour chaque catégorie avec un répéteur interne répertoriant les produits de chaque catégorie dans une liste à puces. Le main défi de la création d’une interface utilisateur imbriquée réside dans l’accès et la liaison des données correctes au contrôle Web de données interne. Il existe une variété de techniques disponibles, dont deux que nous avons examinées dans ce tutoriel. La première approche examinée utilisait un ObjetDataSource dans les contrôles Web de ItemTemplate
données externes qui était lié au contrôle Web de données interne par le biais de sa DataSourceID
propriété. La deuxième technique a accédé aux données via une méthode dans la classe code-behind de la page ASP.NET. Cette méthode peut ensuite être liée à la propriété du contrôle Web de DataSource
données interne par le biais de la syntaxe de liaison de données.
Bien que l’interface utilisateur imbriquée examinée dans ce didacticiel utilise un répéteur imbriqué dans un répéteur, ces techniques peuvent être étendues aux autres contrôles web de données. Vous pouvez imbriquer un répéteur dans un GridView, ou un GridView dans un DataList, et ainsi de suite.
Bonne programmation !
À propos de l’auteur
Scott Mitchell, auteur de sept livres ASP/ASP.NET et fondateur de 4GuysFromRolla.com, travaille avec les technologies Web Microsoft depuis 1998. Scott travaille comme consultant indépendant, formateur et écrivain. Son dernier livre est Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Il est accessible à l’adressemitchell@4GuysFromRolla.com . ou via son blog, qui se trouve à l’adresse http://ScottOnWriting.NET.
Remerciements spéciaux à
Cette série de tutoriels a été examinée par de nombreux réviseurs utiles. Les réviseurs principaux de ce tutoriel étaient Zack Jones et Liz Shulok. Vous souhaitez consulter mes prochains articles MSDN ? Si c’est le cas, déposez-moi une ligne à mitchell@4GuysFromRolla.com.