Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
door Scott Mitchell
In deze tutorial verkennen we hoe je een repeater gebruikt die genesteld is binnen een andere repeater. In de voorbeelden ziet u hoe u de binnenste Repeater zowel declaratief als programmatisch kunt vullen.
Introductie
Naast de syntaxis van statische HTML en databinding kunnen sjablonen ook webbesturingselementen en gebruikersbesturingselementen bevatten. Deze webbesturingselementen kunnen hun eigenschappen hebben toegewezen via declaratieve syntaxis of gegevensbindingssyntaxis, of kunnen programmatisch worden benaderd in de juiste gebeurtenishandlers aan de serverzijde.
Door besturingselementen in een sjabloon in te sluiten, kan het uiterlijk en de gebruikerservaring worden aangepast en verbeterd. In de zelfstudie Sjabloonvelden gebruiken in het GridView-besturingselement hebben we bijvoorbeeld gezien hoe we de weergave van de GridView kunnen aanpassen door een Agenda-besturingselement toe te voegen binnen een TemplateField om de datum van indiensttreding van een werknemer weer te geven. In de tutorials Validatiebesturingselementen toevoegen aan de bewerkings- en invoeginterfaces en Het aanpassen van de interface voor gegevenswijziging hebben we gezien hoe we de bewerkings- en invoeginterfaces kunnen aanpassen door validatiebesturingselementen, tekstvakken, keuzelijsten en andere webbesturingselementen toe te voegen.
Sjablonen kunnen ook andere besturingselementen voor gegevensweb bevatten. Dat wil zeggen, we kunnen een DataList hebben die binnen zijn sjablonen een andere DataList (of Repeater of GridView of DetailsView, enzovoort) bevat. De uitdaging met een dergelijke interface is dat de juiste gegevens aan de webcontrolegegevens kan binden. Er zijn verschillende benaderingen beschikbaar, variërend van declaratieve opties met behulp van ObjectDataSource tot programmatische methoden.
In deze tutorial verkennen we hoe je een repeater gebruikt die genesteld is binnen een andere repeater. De buitenste repeater bevat een item voor elke categorie in de database, waarin de naam en beschrijving van de categorie worden weergegeven. Elke categorie-item heeft een interne herhaler die informatie toont over elk product dat tot die categorie behoort (zie afbeelding 1) in een opsomming. In onze voorbeelden ziet u hoe u de binnenste Repeater zowel declaratief als programmatisch kunt vullen.
Afbeelding 1: Elke categorie, samen met de producten, wordt weergegeven (klik hier om de volledige afbeelding weer te geven)
Stap 1: De categorievermelding maken
Bij het bouwen van een pagina die gebruikmaakt van geneste besturingselementen voor gegevensweb, vind ik het handig om eerst het buitenste gegevenswebbesturingselement te ontwerpen, te maken en te testen, zonder dat ik me zorgen hoeft te maken over het binnenste geneste besturingselement. Laten we daarom beginnen met het doorlopen van de stappen die nodig zijn om een repeater toe te voegen aan de pagina met de naam en beschrijving voor elke categorie.
Begin met het openen van de NestedControls.aspx pagina in de DataListRepeaterBasics map en voeg een herhalingsbesturingselement toe aan de pagina, waarbij de eigenschap ID wordt ingesteld op CategoryList. Kies in de infotag van Repeater een nieuwe ObjectDataSource met de naam CategoriesDataSource.
Afbeelding 2: Geef de nieuwe objectdatabron CategoriesDataSource een naam (klik hier om de volledige afbeelding weer te geven)
Configureer de ObjectDataSource zodat deze de gegevens ophaalt uit de methode CategoriesBLL van de klasse GetCategories.
Afbeelding 3: Configureer de ObjectDataSource om de CategoriesBLL klasse's GetCategories methode te gebruiken (klik om de afbeelding op volledige grootte weer te geven)
Als u de sjablooninhoud van de repeater wilt opgeven, moet u naar de bronweergave gaan en handmatig de declaratieve syntaxis invoeren. Voeg een ItemTemplate naam toe waarmee de naam van de categorie in een <h4> element en de beschrijving van de categorie in een alinea-element (<p>) wordt weergegeven. Bovendien kunnen we elke categorie scheiden met een horizontale regel (<hr>). Nadat u deze wijzigingen hebt aangebracht, moet uw pagina declaratieve syntaxis bevatten voor de Repeater en ObjectDataSource die er ongeveer als volgt uitziet:
<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>
In afbeelding 4 ziet u de voortgang wanneer u deze bekijkt via een browser.
Afbeelding 4: De naam en beschrijving van elke categorie worden vermeld, gescheiden door een horizontale regel (klik om de afbeelding op volledige grootte weer te geven)
Stap 2: de geneste product repeater toevoegen
Nu de categorielijst is voltooid, is de volgende taak om een repeater toe te voegen aan de CategoryList s ItemTemplate die informatie weergeeft over die producten die tot de juiste categorie behoren. Er zijn een aantal manieren waarop we de gegevens voor deze interne repeater kunnen ophalen, waarvan we binnenkort twee verkennen. Laten we voorlopig alleen de producten Repeater binnen de CategoryList Repeater s ItemTemplatemaken. Laten we in het bijzonder het product Repeater elk product weergeven in een lijst met opsommingstekens met elk lijstitem, inclusief de naam en prijs van het product.
Als we deze Repeater willen maken, moeten we handmatig de declaratieve syntaxis en sjablonen van de innerlijke repeater invoeren in de CategoryList en ItemTemplate. Voeg de volgende markeringen toe binnen de CategoryList Repeater s 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>
Stap 3: de Category-Specific-producten binden aan de Repeater ProductsByCategoryList
Als u de pagina op dit moment via een browser bezoekt, ziet uw scherm er hetzelfde uit als in afbeelding 4, omdat we nog gegevens aan de repeater moeten binden. Er zijn een aantal manieren waarop we de juiste productrecords kunnen ophalen en deze kunnen binden aan de Repeater, wat efficiënter dan andere. De belangrijkste uitdaging hier is het terughalen van de juiste producten voor de opgegeven categorie.
De gegevens die moeten worden verbonden met het binnenste repeaterbesturingselement, kunnen declaratief worden geopend via een ObjectDataSource in de CategoryList Repeater ItemTemplateof programmatisch vanaf de code-achterpagina van de ASP.NET pagina. Op dezelfde manier kunnen deze gegevens aan de binnenste Repeater worden gebonden, hetzij declaratief via de eigenschap DataSourceID van de binnenste Repeater of via declaratieve gegevensbindingssyntaxis, of programmatisch door de binnenste Repeater te refereren in de evenement-handler van CategoryList, programmatisch zijn eigenschap ItemDataBound in te stellen en zijn methode DataSource aan te roepen. Laten we elk van deze benaderingen eens bekijken.
Toegang tot de gegevens declaratief met een ObjectDataSource-besturingselement en deItemDataBoundgebeurtenis-handler
Omdat we de ObjectDataSource uitgebreid in deze reeks zelfstudies hebben gebruikt, is de meest natuurlijke keuze voor het openen van gegevens voor dit voorbeeld het gebruik van ObjectDataSource. De ProductsBLL klasse heeft een GetProductsByCategoryID(categoryID) methode die informatie retourneert over die producten die tot de opgegeven categoryIDproducten behoren. Daarom kunnen we een ObjectDataSource toevoegen aan de CategoryList Repeater s ItemTemplate en deze configureren voor toegang tot de gegevens van deze klassemethode.
Helaas staat repeater niet toe dat de sjablonen worden bewerkt via de ontwerpweergave, dus we moeten de declaratieve syntaxis voor dit ObjectDataSource-besturingselement handmatig toevoegen. De volgende syntaxis toont de CategoryList Repeater s ItemTemplate na het toevoegen van deze nieuwe 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>
Wanneer u de ObjectDataSource-benadering gebruikt, moeten we de ProductsByCategoryList eigenschap van de Repeater DataSourceID instellen op de ID van de ObjectDataSource (ProductsByCategoryDataSource). U ziet ook dat onze ObjectDataSource een <asp:Parameter> element bevat dat de categoryID waarde aangeeft die in de GetProductsByCategoryID(categoryID) methode wordt doorgegeven. Maar hoe geven we deze waarde op? Idealiter kunnen we gewoon de DefaultValue eigenschap van het <asp:Parameter> element instellen met behulp van de syntaxis voor gegevensbinding, zoals:
<asp:Parameter Name="CategoryID" Type="Int32"
DefaultValue='<%# Eval("CategoryID")' />
Helaas is de syntaxis van gegevensbinding alleen geldig in besturingselementen met een DataBinding gebeurtenis. De Parameter klasse ontbreekt een dergelijke gebeurtenis en daarom is de bovenstaande syntaxis ongeldig en leidt dit tot een runtimefout.
Als u deze waarde wilt instellen, moet u een gebeurtenis-handler maken voor de CategoryList repeater-gebeurtenis ItemDataBound . Zoals u weet, wordt de ItemDataBound gebeurtenis eenmaal geactiveerd voor elk item dat is gebonden aan de Repeater. Daarom kunnen we telkens wanneer deze gebeurtenis wordt geactiveerd voor de buitenste Repeater de huidige CategoryID waarde toewijzen aan de ProductsByCategoryDataSource parameter van ObjectDataSource CategoryID.
Maak een gebeurtenis-handler voor de CategoryList repeater-gebeurtenis ItemDataBound met de volgende code:
Protected Sub CategoryList_ItemDataBound(sender As Object, e As RepeaterItemEventArgs) _
Handles CategoryList.ItemDataBound
If e.Item.ItemType = ListItemType.AlternatingItem _
OrElse e.Item.ItemType = ListItemType.Item Then
' Reference the CategoriesRow object being bound to this RepeaterItem
Dim category As Northwind.CategoriesRow = _
CType(CType(e.Item.DataItem, System.Data.DataRowView).Row, _
Northwind.CategoriesRow)
' Reference the ProductsByCategoryDataSource ObjectDataSource
Dim ProductsByCategoryDataSource As ObjectDataSource = _
CType(e.Item.FindControl("ProductsByCategoryDataSource"), _
ObjectDataSource)
' Set the CategoryID Parameter value
ProductsByCategoryDataSource.SelectParameters("CategoryID").DefaultValue = _
category.CategoryID.ToString()
End If
End Sub
Deze gebeurtenis-handler begint door ervoor te zorgen dat we te maken hebben met een gegevensitem in plaats van de koptekst, voettekst of scheidingstekenitem. Vervolgens verwijzen we naar het werkelijke CategoriesRow exemplaar dat zojuist is gebonden aan de huidige RepeaterItem. Ten slotte verwijzen we naar de ObjectDataSource in de ItemTemplate en wijzen we de parameterwaarde CategoryID toe aan de CategoryID van de huidige RepeaterItem.
Met deze eventhandler wordt de ProductsByCategoryList Repeater in elk RepeaterItem-item gekoppeld aan de producten in de categorie RepeaterItem. Afbeelding 5 laat een schermafbeelding van de resulterende uitvoer zien.
Afbeelding 5: De buitenste repeater vermeldt elke categorie; de binnenste lijst met de producten voor die categorie (klik om de volledige afbeelding weer te geven)
Toegang tot productgegevens op categorie programmatisch
In plaats van een ObjectDataSource te gebruiken om de producten voor de huidige categorie op te halen, kunnen we een methode maken in de code-behind-klasse van onze ASP.NET pagina (of in de App_Code map of in een afzonderlijk klassebibliotheekproject) die de juiste set producten retourneert wanneer een CategoryIDproduct wordt doorgegeven. Stel dat we zo'n methode hadden in onze ASP.NET code-behind-klasse en dat deze de naam GetProductsInCategory(categoryID)had. Met deze methode kunnen we de producten voor de huidige categorie binden aan de binnenste Repeater met behulp van de volgende declaratieve syntaxis:
<asp:Repeater runat="server" ID="ProductsByCategoryList" EnableViewState="False"
DataSource='<%# GetProductsInCategory(CType(Eval("CategoryID"), Integer)) %>'>
...
</asp:Repeater>
De DataSource eigenschap van de Repeater gebruikt de data-binding syntaxis om aan te geven dat de gegevens afkomstig zijn van de GetProductsInCategory(categoryID) methode. Omdat Eval("CategoryID") een waarde van het type Object retourneert, casten we het object naar een Integer voordat het in de GetProductsInCategory(categoryID) methode wordt doorgegeven. Houd er rekening mee dat de CategoryID die hier via de gegevensbinding-syntaxis toegankelijk is, de CategoryID in de buitenste Repeater (CategoryList) is, degene die is gekoppeld aan de records in de Categories tabel. Daarom weten we dat CategoryID geen databasewaarde NULL kan zijn. Daarom kunnen we de Eval-methode blind casten zonder te controleren of we te maken hebben met een DBNull.
Met deze aanpak moeten we de GetProductsInCategory(categoryID) methode maken en de juiste set producten laten ophalen op basis van de opgegeven categoryID. We kunnen dit doen door simpelweg de ProductsDataTable die door de ProductsBLL klasse GetProductsByCategoryID(categoryID) methode wordt geretourneerd, terug te geven. Laten we de GetProductsInCategory(categoryID) methode maken in de code-behind-klasse voor onze NestedControls.aspx pagina. Gebruik hiervoor de volgende code:
Protected Function GetProductsInCategory(ByVal categoryID As Integer) _
As Northwind.ProductsDataTable
' Create an instance of the ProductsBLL class
Dim productAPI As ProductsBLL = New ProductsBLL()
' Return the products in the category
Return productAPI.GetProductsByCategoryID(categoryID)
End Function
Met deze methode maakt u gewoon een exemplaar van de ProductsBLL methode en retourneert u de resultaten van de GetProductsByCategoryID(categoryID) methode. Houd er rekening mee dat de methode moet worden gemarkeerd Public of Protected; als de methode is gemarkeerd Private, deze niet toegankelijk is vanuit de declaratieve markeringen van de ASP.NET pagina.
Nadat u deze wijzigingen hebt aangebracht om deze nieuwe techniek te gebruiken, kunt u de pagina bekijken via een browser. De uitvoer moet identiek zijn aan de uitvoer wanneer u de ObjectDataSource- en ItemDataBound gebeurtenishandlerbenadering gebruikt (raadpleeg afbeelding 5 om een schermafbeelding te zien).
Opmerking
Het lijkt erop dat het druk is om de GetProductsInCategory(categoryID) methode te maken in de code-achterklasse van de ASP.NET pagina. Deze methode maakt immers gewoon een exemplaar van de ProductsBLL klassen en retourneert de resultaten van de GetProductsByCategoryID(categoryID) methode. Waarom roept u deze methode niet rechtstreeks aan vanuit de syntaxis van de gegevensbinding in de binnenste Repeater, zoals: DataSource='<%# ProductsBLL.GetProductsByCategoryID(CType(Eval("CategoryID"), Integer)) %>'? Hoewel deze syntaxis niet werkt met de huidige implementatie van de ProductsBLL klasse (omdat de GetProductsByCategoryID(categoryID) methode een instantiemethode is), kunt u een ProductsBLL statische GetProductsByCategoryID(categoryID) methode opnemen of de klasse een statische Instance() methode laten bevatten om een nieuw exemplaar van de ProductsBLL klasse te retourneren.
Hoewel dergelijke wijzigingen de noodzaak voor de GetProductsInCategory(categoryID) methode in de code-behind-klasse van de ASP.NET-pagina weglaten, biedt de code-behind-klassemethode ons meer flexibiliteit bij het werken met de opgehaalde gegevens, zoals we binnenkort zullen zien.
Alle productgegevens tegelijk ophalen
De twee vorige technieken die we hebben onderzocht, pakken die producten voor de huidige categorie door de methode van de ProductsBLL-klasse aan te roepen (de eerste benadering deed dit via een ObjectDataSource, de tweede via de methode in de GetProductsByCategoryID(categoryID) achterliggende code-klasse). Telkens wanneer deze methode wordt aangeroepen, roept de Business Logic Layer de Data Access-laag aan, die een query uitvoert op de database met een SQL-instructie die rijen retourneert uit de tabel waarvan Products het CategoryID veld overeenkomt met de opgegeven invoerparameter.
Uitgaande van N categorieën in het systeem, voert deze aanpak één databasequery uit om alle categorieën op te halen, gevolgd door N + 1 aanroepen naar de database om de producten specifiek voor elke categorie op te halen. We kunnen echter alle benodigde gegevens ophalen in slechts twee databaseoproepen: één om alle categorieën op te vragen en een andere om alle producten op te vragen. Zodra we alle producten hebben, kunnen we deze producten filteren, zodat alleen de producten die overeenkomen met de huidige CategoryID zijn gebonden aan de binnenste Repeater van die categorie.
Om deze functionaliteit te bieden, hoeven we alleen een kleine wijziging aan te brengen in de methode in de code-achterklasse van onze GetProductsInCategory(categoryID) ASP.NET pagina. In plaats van blind de resultaten van de ProductsBLL klassemethode GetProductsByCategoryID(categoryID) terug te geven, kunnen we eerst alle producten toegankelijk maken (als ze nog niet toegankelijk zijn gemaakt) en vervolgens alleen de gefilterde weergave van de producten retourneren op basis van de doorgegeven CategoryID-parameters.
Private allProducts As Northwind.ProductsDataTable = Nothing
Protected Function GetProductsInCategory(ByVal categoryID As Integer) _
As Northwind.ProductsDataTable
' First, see if we've yet to have accessed all of the product information
If allProducts Is Nothing Then
Dim productAPI As ProductsBLL = New ProductsBLL()
allProducts = productAPI.GetProducts()
End If
' Return the filtered view
allProducts.DefaultView.RowFilter = "CategoryID = " & categoryID
Return allProducts
End Function
Let op de toevoeging van de variabele op paginaniveau. allProducts Dit bevat informatie over alle producten en wordt ingevuld wanneer de methode voor GetProductsInCategory(categoryID) het eerst wordt aangeroepen. Nadat u ervoor hebt gezorgd dat het allProducts object is gemaakt en ingevuld, filtert de methode de resultaten van de gegevenstabel zodanig dat alleen de rijen waarvan CategoryID de opgegeven CategoryID overeenkomsten overeenkomen, toegankelijk zijn. Deze aanpak vermindert het aantal keren dat de database wordt geopend van N + 1 tot twee.
Deze verbetering introduceert geen wijzigingen in de weergegeven markeringen van de pagina en brengt ook geen minder records mee dan de andere benadering. Het vermindert gewoon het aantal aanroepen naar de database.
Opmerking
Men zou intuïtief kunnen redeneren dat het verminderen van het aantal databasetoegangen de prestaties zeker zou verbeteren. Dit kan echter niet het geval zijn. Als u bijvoorbeeld een groot aantal producten hebt waarvan CategoryIDNULL is, retourneert de GetProducts methode een aantal producten die nooit worden weergegeven. Bovendien kan het retourneren van alle producten verspilling zijn als u alleen een subset van de categorieën weergeeft. Dit kan het geval zijn als u paging hebt geïmplementeerd.
Zoals altijd geldt dat als het gaat om het analyseren van de prestaties van twee technieken, de enige surefire-meting is het uitvoeren van gecontroleerde tests die zijn afgestemd op de algemene scenario's van uw toepassing.
Samenvatting
In deze zelfstudie hebben we gezien hoe we de ene gegevenswebcontrole binnen de andere kunnen nesten, met name om te zorgen dat een buitenste repeater een item voor elke categorie weergeeft en een binnenste repeater de producten voor elke categorie in een opsommingstekenslijst laat zien. De belangrijkste uitdaging bij het bouwen van een geneste gebruikersinterface ligt in het openen en binden van de juiste gegevens aan de interne webgegevenscontrole. Er zijn verschillende technieken beschikbaar, waarvan we twee in deze zelfstudie hebben onderzocht. De eerste methode die werd onderzocht, gebruikte een ObjectDataSource in de webbesturingselementen ItemTemplate voor externe gegevens die via de eigenschap aan het binnenste gegevenswebbesturingselement DataSourceID waren gebonden. De tweede techniek heeft toegang tot de gegevens via een methode in de code-behind-klasse van de ASP.NET-pagina. Deze methode kan vervolgens worden gebonden aan de eigenschap van de interne gegevenswebcontrole DataSource via de syntaxis van de gegevensbinding.
Hoewel de geneste gebruikersinterface die in deze zelfstudie is onderzocht een Repeater binnen een Repeater gebruikte, kunnen deze technieken worden uitgebreid naar andere gegevenswebbesturingselementen. U kunt een Repeater in een GridView of een GridView in een DataList nesten, enzovoort.
Veel plezier met programmeren!
Over de auteur
Scott Mitchell, auteur van zeven ASP/ASP.NET-boeken en oprichter van 4GuysFromRolla.com, werkt sinds 1998 met Microsoft-webtechnologieën. Scott werkt als onafhankelijk consultant, trainer en schrijver. Zijn laatste boek is Sams Teach Yourself ASP.NET 2.0 in 24 uur. Hij kan worden bereikt op mitchell@4GuysFromRolla.com.
Speciale dank aan
Deze tutorialreeks is beoordeeld door veel behulpzame beoordelers. Hoofdrecensenten voor deze zelfstudie waren Zack Jones en Liz Shulok. Bent u geïnteresseerd in het bekijken van mijn aanstaande MSDN-artikelen? Zo ja, laat iets van je horen via mitchell@4GuysFromRolla.com.