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é à l’intérieur d’un autre répéteur. Les exemples illustrent comment remplir le répéteur interne de manière déclarative et programmatique.

Présentation

En plus de la syntaxe HTML statique et 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 attribuées via une syntaxe de liaison de données déclarative ou accessible 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 didacticiel 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 de personnalisation des didacticiels sur 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, TextBoxes, 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 une autre DataList (ou Repeater, GridView ou DetailsView, etc.) dans ses modèles. Le défi avec une telle interface consiste à lier les données appropriées au contrôle Web de données internes. Il existe quelques approches différentes, allant des options déclaratives à l’aide de ObjectDataSource à des approches programmatiques.

Dans ce tutoriel, nous allons découvrir comment utiliser un répéteur imbriqué à l’intérieur d’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 de chaque é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ées

Figure 1 : Chaque catégorie, ainsi que ses produits, est répertoriée (Cliquez pour afficher l’image de taille complète)

É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 qu’il est utile de concevoir, de créer et de tester d’abord le contrôle Web de données les plus externes, sans même vous 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 ObjectDataSource nommé CategoriesDataSource.

Nommez la nouvelle source de données des catégories ObjectDataSource

Figure 2 : Nommer la Nouvelle ObjectDataSource CategoriesDataSource (cliquez pour afficher l’image de taille complète)

Configurez ObjectDataSource afin qu’il extrait ses données de la méthode CategoriesBLL de la classe GetCategories.

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

Figure 3 : Configurer ObjectDataSource pour utiliser la CategoriesBLL méthode de classe GetCategories (Click pour afficher l’image de taille complète)

Pour spécifier le contenu du modèle repeater, nous devons accéder à la vue Source et entrer manuellement la syntaxe déclarative. Ajoutez un ItemTemplate élément qui affiche le nom de la catégorie dans un <h4> élément et la description de la catégorie dans un élément de paragraphe (<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 le répéteur et ObjectDataSource semblable à 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.

Chaque nom et description de catégorie est répertorié, 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 de taille complète)

Étape 2 : Ajout du répéteur de produit imbriqué

Une fois la liste des catégories terminée, notre prochaine tâche 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 nous allons explorer prochainement. Pour l’instant, nous allons simplement créer le répéteur de produits dans le CategoryList répéteur s ItemTemplate. Plus précisément, laissez le répéteur 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 les CategoryList s ItemTemplate. Ajoutez le balisage suivant dans les CategoryList Repeater 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 des produits Category-Specific au répéteur ProductsByCategoryList

Si vous visitez la page via un navigateur à ce stade, votre écran se présente comme dans la figure 4, car nous n’avons pas encore lié des données au répéteur. Il existe quelques 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 principal 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 interne Repeater peuvent être accessibles de manière déclarative, via un ObjectDataSource dans le CategoryList Repeater ItemTemplate, ou par programmation, à partir de la page code-behind de l'ASP.NET. De même, ces données peuvent être liées au répéteur interne de manière déclarative , par le biais de la propriété du DataSourceID répéteur interne ou par le biais d’une syntaxe de liaison de données déclarative ou par programmation en référençant le répéteur interne dans le CategoryList gestionnaire d’événements repeater ItemDataBound , en définissant par programme sa DataSource propriété et en appelant sa DataBind() méthode. Examinons chacune de ces approches.

Accès aux données de manière déclarative 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 de cet exemple consiste à rester avec ObjectDataSource. La ProductsBLL classe a une GetProductsByCategoryID(categoryID) méthode qui retourne des informations sur les produits qui appartiennent à l’objet spécifié categoryID. Par conséquent, nous pouvons ajouter un ObjectDataSource aux répéteurs CategoryList et les configurer pour accéder à leurs données à partir de la méthode de cette classe.

Malheureusement, le répéteur n’autorise pas la modification de ses modèles par le biais de l’affichage Création. Nous devons donc ajouter la syntaxe déclarative pour ce contrôle ObjectDataSource manuellement. La syntaxe suivante montre le CategoryList Répéteur ItemTemplate après l’ajout de ce nouvel ObjectDataSource (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>

Lors de l’utilisation de l’approche ObjectDataSource, nous devons définir la ProductsByCategoryList propriété du Repeater DataSourceID sur l’objet ID ObjectDataSource (ProductsByCategoryDataSource). Notez également que notre ObjectDataSource a un <asp:Parameter> élément qui spécifie la categoryID valeur qui sera transmise à la GetProductsByCategoryID(categoryID) méthode. Mais comment spécifier cette valeur ? Dans l’idéal, nous pourrions simplement définir la propriété DefaultValue de l’élément <asp:Parameter> à l’aide de la 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 ObjectDataSource.CategoryID

Créez un gestionnaire d'événements pour l'événement du Repeater : CategoryListItemDataBound, avec 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 séparateur. Ensuite, nous référencerons l’instance réelle CategoriesRow qui vient d’être liée à l’actuel RepeaterItem. Enfin, nous référençons ObjectDataSource dans l’objet ItemTemplate et affectons sa CategoryID valeur de paramètre à l’objet CategoryID actif RepeaterItem.

Avec ce gestionnaire d’événements, le ProductsByCategoryList répéteur dans chaque RepeaterItem est lié à des produits de la catégorie RepeaterItem. La figure 5 montre une capture d’écran de la sortie résultante.

Le répéteur externe répertorie chaque catégorie ; l’intérieur répertorie les produits de cette catégorie

Figure 5 : Le répéteur externe répertorie chaque catégorie ; le répéteur interne répertorie les produits pour cette catégorie (Cliquez pour voir l'image en taille réelle)

Accès aux produits par catégorie par programmation

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 de page ASP.NET (ou dans le App_Code dossier ou dans un projet de bibliothèque de classes distinct) qui retourne l’ensemble approprié de produits lorsqu’ils sont passés dans un CategoryID. Imaginez que nous avions une telle méthode dans notre classe code-behind de page ASP.NET et qu’elle était nommée GetProductsInCategory(categoryID). Avec cette méthode en place, nous pourrions 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 DataSource utilise la syntaxe de 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 passer dans la GetProductsInCategory(categoryID) méthode. Notez que, ici l'CategoryID est accédé via la syntaxe de liaison de données, il s'agit du CategoryID dans le Répéteur externe (CategoryList), celui qui est lié aux enregistrements de la table Categories. Par conséquent, nous savons qu’il CategoryID ne peut pas être une valeur de base de données NULL, c’est pourquoi nous pouvons effectuer un cast aveugle de la méthode Eval sans vérifier si nous avons affaire à un DBNull.

Avec cette approche, nous devons créer la GetProductsInCategory(categoryID) méthode et récupérer l’ensemble approprié de produits en fonction de l’offre categoryID. Nous pouvons le faire en retournant simplement le ProductsDataTable retourné par la méthode ProductsBLL de la classe GetProductsByCategoryID(categoryID). 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).

Remarque

Cela peut sembler être un travail inutile que de créer la méthode GetProductsInCategory(categoryID) dans la classe de code-behind de la page ASP.NET. Après tout, cette méthode crée simplement une 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, comme : 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 d’instance), vous pouvez modifier ProductsBLL pour inclure une méthode statique GetProductsByCategoryID(categoryID) ou que la classe inclut une méthode statique Instance() pour retourner une nouvelle instance de la ProductsBLL classe.

Bien que ces modifications éliminent la nécessité de la GetProductsInCategory(categoryID) méthode dans la classe code-behind de la page ASP.NET, la méthode de classe code-behind nous donne plus de flexibilité en travaillant avec les données récupérées, comme nous le verrons bientôt.

Récupération de toutes les informations sur le produit à la fois

Les deux techniques précédentes que nous avons examinées saisissent ces produits pour la catégorie actuelle en faisant un appel à la méthode ProductsBLL de la classe (la première approche l’a fait par le biais d’un ObjectDataSource, la deuxième à travers la méthode GetProductsByCategoryID(categoryID) dans la classe code-behind). Chaque fois que cette méthode est appelée, la couche logique métier appelle vers le bas 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 Products le CategoryID champ correspond au paramètre d’entrée fourni.

Étant donné N catégories dans le système, cette approche nécessite N + 1 appels à la base de données : une requête pour obtenir toutes les catégories, puis N appels pour obtenir les produits spécifiques à chaque catégorie. Toutefois, nous pouvons récupérer toutes les données nécessaires dans seulement deux appels de base de données pour obtenir toutes les catégories et une 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 à l'actuel CategoryID soient affectés au répéteur interne de la catégorie.

Pour fournir cette fonctionnalité, nous n'avons besoin que d'apporter une légère modification à la méthode GetProductsInCategory(categoryID) dans la classe de code-behind de notre page ASP.NET. Plutôt que de retourner aveuglément les résultats de la méthode ProductsBLL de la classe 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 fonction du fourni.

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 Cela 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 du DataTable de sorte que seules les lignes dont CategoryID correspond à CategoryID soient accessibles. Cette approche réduit le nombre de fois où la base de données est accessible de N + 1 à deux.

Cette amélioration n’introduit aucune modification du balisage rendu de la page, ni ne renvoie-t-elle moins d’enregistrements que l’autre approche. Cela réduit simplement le nombre d'appels vers la base de données.

Remarque

On pourrait raisonnablement penser que réduire le nombre d'accès aux bases de données améliorerait les performances. Toutefois, ce n’est peut-être pas le cas. Si vous avez un grand nombre de produits dont CategoryID est NULL, l'appel à la méthode GetProducts retourne un certain nombre de produits qui ne sont jamais affichés, par exemple. De plus, renvoyer tous les produits peut être un gaspillage 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, quand 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 spécifiquement comment un répéteur externe affiche un élément pour chaque catégorie avec un répéteur interne répertoriant les produits pour chaque catégorie dans une liste à puces. Le principal défi de la création d’une interface utilisateur imbriquée consiste à accéder aux données appropriées et à les lier au contrôle Web de données interne. Il existe une variété de techniques disponibles, dont deux sont examinées dans ce tutoriel. La première approche examinée a utilisé un ObjectDataSource dans le contrôle Web de données externe qui était lié au contrôle Web de données interne par le biais de sa propriété ItemTemplate. 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 DataSource contrôle Web de données interne via 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 au sein d’un GridView ou d’un GridView dans une liste de données, 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 en tant que consultant indépendant, formateur et écrivain. Son dernier livre est Sams Teach Yourself ASP.NET 2.0 en 24 heures. On peut le joindre à mitchell@4GuysFromRolla.com.

Merci spécial à

Cette série de tutoriels a été examinée par de nombreux réviseurs utiles. Les réviseurs principaux de ce didacticiel é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.