Partager via


Contrôles web de données imbriquées (C#)

par Scott Mitchell

Télécharger le PDF

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.

Chaque catégorie, ainsi que ses produits, sont répertoriés

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.

Nommez le nouvel objetDataSource 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.

Configurer objectDataSource pour utiliser la méthode GetCategories de la classe CategoriesBLL

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.

Le nom et la description de chaque catégorie sont répertoriés, séparés par une règle horizontale

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 CategoryListItemTemplate 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 DataSourceIDID 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.

Le répéteur externe Listes chaque catégorie ; l’élément interne Listes les produits de cette catégorie

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 CategoryIDfonction 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.