Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
I den här självstudien kommer vi att utforska hur man använder en Repeater som är kapslad i en annan Repeater. Exemplen visar hur du fyller i den inre repeatern både deklarativt och programmatiskt.
Inledning
Förutom statisk HTML- och databindningssyntax kan mallar även innehålla webbkontroller och användarkontroller. Dessa webbkontroller kan tilldelas sina egenskaper via deklarativ syntax, databindningssyntax eller kan nås programmatiskt i lämpliga händelsehanterare på serversidan.
Genom att bädda in kontroller i en mall kan utseendet och användarupplevelsen anpassas och förbättras. I självstudien Använda TemplateFields i GridView Control såg vi till exempel hur du anpassar GridView-visningen genom att lägga till en kalenderkontroll i ett TemplateField för att visa en anställds anställningsdatum. I självstudierna Lägga till verifieringskontroller i självstudierna Redigera och Infoga gränssnitt och Anpassa datamodifieringsgränssnittet såg vi hur du anpassar redigerings- och infogningsgränssnitten genom att lägga till verifieringskontroller, textrutor, listrutor och andra webbkontroller.
Mallar kan också innehålla andra datawebbkontroller. Det vill säga vi kan ha en DataList som innehåller en annan DataList (eller Repeater eller GridView eller DetailsView, och så vidare) i dess mallar. Utmaningen med ett sådant gränssnitt är att binda lämpliga data till den inre datawebbkontrollen. Det finns några olika metoder som är tillgängliga, allt från deklarativa alternativ som använder ObjectDataSource till programmatiska.
I den här självstudien kommer vi att utforska hur man använder en Repeater som är kapslad i en annan Repeater. Den yttre repeatern innehåller ett objekt för varje kategori i databasen som visar kategorins namn och beskrivning. Varje kategoriobjekts inre Repeater visar information för varje produkt som tillhör den kategorin (se bild 1) i en punktlista. Våra exempel visar hur du fyller i den inre repeatern både deklarativt och programmatiskt.
Bild 1: Varje kategori, tillsammans med dess produkter, visas (Klicka om du vill visa en bild i full storlek)
Steg 1: Skapa kategorilistan
När jag skapar en sida som använder kapslade datawebbkontroller tycker jag att det är bra att utforma, skapa och testa den yttersta datawebbkontrollen först, utan att ens bekymra mig om den inre kapslade kontrollen. Därför börjar vi med att gå igenom de steg som krävs för att lägga till en Repeater på sidan som visar namnet och beskrivningen för varje kategori.
Öppna först NestedControls.aspx
sidan i DataListRepeaterBasics
mappen och lägg till en Repeater-kontroll och ställ in dess ID
egenskap till CategoryList
. Från den smarta taggen Repeater väljer du att skapa en ny ObjectDataSource med namnet CategoriesDataSource
.
Bild 2: Ge det nya objektet namnet ObjectDataSource CategoriesDataSource
(klicka om du vill visa en bild i full storlek)
Konfigurera ObjectDataSource så att den hämtar sina data från CategoriesBLL
klassens GetCategories
-metod.
Bild 3: Konfigurera ObjectDataSource att använda CategoriesBLL
klassens GetCategories
metod (Klicka om du vill visa en bild i full storlek)
Om du vill ange Repeater-mallinnehållet måste vi gå till källvyn och ange den deklarativa syntaxen manuellt. Lägg till ett ItemTemplate
som visar kategorins namn i ett <h4>
element och kategoribeskrivningen i ett styckeelement (<p>
). Dessutom ska vi avgränsa varje kategori med en vågrät regel (<hr>
). När du har gjort dessa ändringar bör sidan innehålla deklarativ syntax för Repeater och ObjectDataSource som liknar följande:
<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>
Bild 4 visar våra framsteg när det ses genom en webbläsare.
Bild 4: Varje kategoris namn och beskrivning visas, avgränsad med en vågrät regel (Klicka om du vill visa en bild i full storlek)
Steg 2: Lägga till den kapslade produktrepeteraren
När kategorilistan är klar är nästa uppgift att lägga till en Repeater till CategoryList
s ItemTemplate
som visar information om de produkter som tillhör lämplig kategori. Det finns ett antal sätt att hämta data för den här inre repeatern, varav två vi kommer att utforska inom kort. Nu ska vi bara skapa produkterna Repeater i CategoryList
Repeater s ItemTemplate
. Mer specifikt ska vi låta produkten Repeater visa varje produkt i en punktlista med varje listobjekt, inklusive produktens namn och pris.
För att skapa den här repeatern måste vi manuellt ange den inre repeaterns deklarativa syntax och mallar i CategoryList
s ItemTemplate
. Lägg till följande markering i 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>
Steg 3: Binda Category-Specific produkter till ProductsByCategoryList Repeater
Om du besöker sidan via en webbläsare i det här läget ser skärmen likadan ut som i bild 4 eftersom vi ännu inte har bundet några data till Repeater. Det finns några sätt att hämta lämpliga produktrader och binda dem till Repeatern, vissa mer effektiva än andra alternativ. Den största utmaningen här är att få tillbaka lämpliga produkter för den angivna kategorin.
Data som ska bindas till den inre Repeater-kontrollen kan antingen nås deklarativt, via en ObjectDataSource i CategoryList
Repeater s ItemTemplate
, eller programmatiskt, från ASP.NET sidans kod bakom sida. På samma sätt kan dessa data bindas till den inre Repeater antingen deklarativt – via den inre Repeater-egenskapen DataSourceID
eller genom deklarativ databindningssyntax eller programmatiskt genom att referera till den inre repeatern i CategoryList
Repeater-händelsehanteraren ItemDataBound
, programmatiskt ange dess DataSource
egenskap och anropa dess DataBind()
metod. Låt oss utforska var och en av dessa metoder.
Få åtkomst till data deklarativt med en ObjectDataSource-kontroll ochItemDataBound
händelsehanteraren
Eftersom vi har använt ObjectDataSource i stor utsträckning i den här självstudieserien är det mest naturliga valet för att komma åt data för det här exemplet att hålla sig till ObjectDataSource. Klassen ProductsBLL
har en GetProductsByCategoryID(categoryID)
metod som returnerar information om de produkter som tillhör den angivna categoryID
. Därför kan vi lägga till en ObjectDataSource till CategoryList
Repeater s ItemTemplate
och konfigurera den för att komma åt dess data från den här klassens metod.
Repeater tillåter tyvärr inte att dess mallar redigeras via designvyn, så vi måste lägga till deklarativ syntax för den här ObjectDataSource-kontrollen för hand. Följande syntax visar CategoryList
Repeater s ItemTemplate
när du har lagt till den här nya 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>
När du använder Metoden ObjectDataSource måste vi ange ProductsByCategoryList
egenskapen Repeater s DataSourceID
till ID
egenskapen ObjectDataSource (ProductsByCategoryDataSource
). Observera också att vår ObjectDataSource har ett <asp:Parameter>
element som anger det categoryID
värde som ska skickas till GetProductsByCategoryID(categoryID)
metoden. Men hur anger vi det här värdet? Helst skulle vi bara kunna ange DefaultValue
egenskapen för elementet <asp:Parameter>
med hjälp av databindningssyntax, så här:
<asp:Parameter Name="CategoryID" Type="Int32"
DefaultValue='<%# Eval("CategoryID")' />
Tyvärr är databindningssyntaxen endast giltig i kontroller som har en DataBinding
händelse. Klassen Parameter
saknar en sådan händelse och därför är syntaxen ovan ogiltig och resulterar i ett körningsfel.
För att ange det här värdet måste vi skapa en händelsehanterare för CategoryList
Repeater-händelsen ItemDataBound
. Kom ihåg att händelsen ItemDataBound
utlöses en gång för varje objekt som är bundet till Repeater. Varje gång den här händelsen utlöses för den yttre repeatern kan vi därför tilldela det aktuella CategoryID
värdet till parametern ProductsByCategoryDataSource
ObjectDataSource s CategoryID
.
Skapa en händelsehanterare för CategoryList
Repeater-händelsen ItemDataBound
med följande kod:
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
Den här händelsehanteraren börjar med att se till att vi hanterar ett dataobjekt i stället för sidhuvud, sidfot eller avgränsarobjekt. Därefter refererar vi till den faktiska CategoriesRow
instansen som just har bundits till den aktuella RepeaterItem
. Slutligen refererar vi till ObjectDataSource i ItemTemplate
och tilldelar dess CategoryID
-parametervärde till CategoryID
för den aktuella RepeaterItem
.
Med den här händelsehanteraren är ProductsByCategoryList
Repeater i var och en av RepeaterItem
bunden till dessa produkter i kategorin RepeaterItem
s. Bild 5 visar en skärmbild av resultatet.
Bild 5: Den yttre repeatern visar varje kategori; Den inre visar en lista över produkter för den kategorin (Klicka om du vill visa en bild i full storlek)
Komma åt produkterna efter kategoridata programmatiskt
I stället för att använda en ObjectDataSource för att hämta produkterna för den aktuella kategorin kan vi skapa en metod i vår ASP.NET sidans kod-behind-klass (eller i App_Code
mappen eller i ett separat klassbiblioteksprojekt) som returnerar rätt uppsättning produkter när den får ett CategoryID
som argument. Anta att vi hade en sådan metod i vår ASP.NET-sidans kod bakom-klass och att den hette GetProductsInCategory(categoryID)
. Med den här metoden på plats kan vi binda produkterna för den aktuella kategorin till den inre Repeater med hjälp av följande deklarativ syntax:
<asp:Repeater runat="server" ID="ProductsByCategoryList" EnableViewState="False"
DataSource='<%# GetProductsInCategory(CType(Eval("CategoryID"), Integer)) %>'>
...
</asp:Repeater>
Repeaters DataSource
-egenskap använder databindningssyntaxen för att indikera att dess data kommer från GetProductsInCategory(categoryID)
-metoden. Eftersom Eval("CategoryID")
returnerar ett värde av typen Object
omvandlar vi objektet till ett Integer
innan det skickas GetProductsInCategory(categoryID)
till metoden. Observera att den CategoryID
som används här via databindningssyntaxen CategoryID
är i den yttre repeatern (CategoryList
), den som är bunden till posterna Categories
i tabellen. Därför vet vi att det CategoryID
inte kan vara ett databasvärde NULL
, vilket är anledningen till att vi kan omvandla Eval
metoden blint utan att kontrollera om vi har att göra med en DBNull
.
Med den här metoden måste vi skapa GetProductsInCategory(categoryID)
metoden och få den att hämta rätt uppsättning produkter med tanke på den angivna categoryID
. Vi kan göra detta genom att helt enkelt returnera den ProductsDataTable
som returneras av ProductsBLL
metoden class s GetProductsByCategoryID(categoryID)
. Nu ska vi skapa GetProductsInCategory(categoryID)
metoden i klassen code-behind för vår NestedControls.aspx
sida. Gör det med hjälp av följande kod:
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
Den här metoden skapar helt enkelt en instans av ProductsBLL
metoden och returnerar resultatet av GetProductsByCategoryID(categoryID)
metoden. Observera att metoden måste markeras Public
eller Protected
. Om metoden är markerad Private
kommer den inte att vara tillgänglig från ASP.NET sidans deklarativa markering.
När du har gjort dessa ändringar för att använda den här nya tekniken kan du ta en stund att visa sidan via en webbläsare. Utdata ska vara identiska med utdata när du använder metoden ObjectDataSource och ItemDataBound
händelsehanteraren (se bild 5 för att se en skärmbild).
Anmärkning
Det kan verka som ett meningslöst arbete att skapa GetProductsInCategory(categoryID)
metoden i ASP.NET-sidans bakomliggande kodklass. Den här metoden skapar trots allt bara en instans av ProductsBLL
klassen och returnerar resultatet av metoden GetProductsByCategoryID(categoryID)
. Varför inte bara anropa den här metoden direkt från syntaxen för databindning i den inre repeatern, till exempel: DataSource='<%# ProductsBLL.GetProductsByCategoryID(CType(Eval("CategoryID"), Integer)) %>'
? Även om den här syntaxen inte fungerar med vår aktuella implementering av ProductsBLL
klassen (eftersom GetProductsByCategoryID(categoryID)
metoden är en instansmetod), kan du ändra ProductsBLL
för att inkludera en statisk GetProductsByCategoryID(categoryID)
metod eller låta klassen inkludera en statisk Instance()
metod för att returnera en ny instans av ProductsBLL
klassen.
Även om sådana ändringar skulle eliminera behovet av GetProductsInCategory(categoryID)
metoden i ASP.NET-sidans kod-bakom-klass, ger oss mer flexibilitet med code-behind-klassen när det gäller att arbeta med de data som hämtas, vilket vi snart kommer att se.
Hämtar all produktinformation på en gång
De två pervious-teknikerna som vi har undersökt hämtar dessa produkter för den aktuella kategorin genom att göra ett anrop till ProductsBLL
klassens GetProductsByCategoryID(categoryID)
-metod (den första metoden gjorde det via en ObjectDataSource, den andra via GetProductsInCategory(categoryID)
metoden i klassen code-behind). Varje gång den här metoden anropas anropar Affärslogiklagret ned till dataåtkomstskiktet, som frågar databasen med en SQL-instruktion som returnerar rader från Products
tabellen vars CategoryID
fält matchar den angivna indataparametern.
Givet N kategorier i systemet medför denna metod N + 1 anrop till databasen: en databasfråga för att hämta alla kategorier och sedan N anrop för att få de produkter som är specifika för varje kategori. Vi kan dock hämta alla data som behövs i bara två databasanrop ett anrop för att hämta alla kategorier och en annan för att hämta alla produkter. När vi har alla produkter kan vi filtrera dessa produkter så att endast de produkter som matchar den aktuella CategoryID
är bundna till den kategorins inre Repeater.
För att tillhandahålla den här funktionen behöver vi bara göra en liten ändring av metoden GetProductsInCategory(categoryID)
i kodbakomklassen på vår ASP.NET-sida. I stället för att okritiskt returnera resultatet av ProductsBLL
-klassens GetProductsByCategoryID(categoryID)
-metod kan vi först kontrollera alla produkter (om de inte redan har kontrollerats) och sedan endast returnera den filtrerade vyn av produkterna baserat på den angivna CategoryID
.
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
Observera tillägget av variabeln på sidnivå, allProducts
. Detta innehåller information om alla produkter och fylls i första gången GetProductsInCategory(categoryID)
metoden anropas. När du har kontrollerat att allProducts
objektet har skapats och fyllts i filtrerar metoden datatabellens resultat så att endast de rader vars CategoryID
matchar det angivna CategoryID
är tillgängliga. Den här metoden minskar antalet gånger databasen nås från N + 1 ned till två.
Den här förbättringen medför inte någon ändring av den renderade markeringen på sidan, och den tar inte heller tillbaka färre poster än den andra metoden. Det minskar helt enkelt antalet anrop till databasen.
Anmärkning
Det kan intuitivt bero på att en minskning av antalet databasåtkomster säkert skulle förbättra prestandan. Detta kanske dock inte är fallet. Om du har ett stort antal produkter vars CategoryID
är NULL
, till exempel, returnerar anropet GetProducts
till metoden ett antal produkter som aldrig visas. Dessutom kan det vara slöseri att returnera samtliga produkter om du bara visar en del av kategorierna, vilket kan hända om du har implementerat sidhantering.
Som alltid när det gäller att analysera prestanda för två tekniker är det enda säkra måttet att köra kontrollerade tester som är skräddarsydda för ditt programs vanliga scenarier.
Sammanfattning
I den här handledningen såg vi hur du kapslade en webbdatakontroll i en annan och specifikt hur en yttre Repeater visar en post för varje kategori, med en inre Repeater som listar produkterna i en punktlista för varje kategori. Den största utmaningen med att skapa ett kapslat användargränssnitt är att komma åt och binda rätt data till den inre datawebbkontrollen. Det finns en mängd olika tekniker tillgängliga, varav två vi undersökte i den här självstudien. Den första metoden som undersöktes använde en ObjectDataSource i den yttre datawebbkontrollen som ItemTemplate
var bunden till den inre datawebbkontrollen via dess DataSourceID
egenskap. Den andra tekniken kom åt data via en metod i ASP.NET-sidans code-behind-klass. Den här metoden kan sedan bindas till egenskapen för den inre datawebbkontrollen DataSource
via databindningssyntax.
Även om det kapslade användargränssnittet som undersöktes i denna handledning använde en Repeater kapslad i en Repeater, kan dessa tekniker utökas till de andra data-webbkontrollerna. Du kan kapsla en Repeater i en GridView eller en GridView i en DataList, och så vidare.
Lycka till med programmerandet!
Om författaren
Scott Mitchell, författare till sju ASP/ASP.NET-böcker och grundare av 4GuysFromRolla.com, har arbetat med Microsofts webbtekniker sedan 1998. Scott arbetar som oberoende konsult, tränare och författare. Hans senaste bok är Sams Teach Yourself ASP.NET 2.0 på 24 timmar. Han kan nås på mitchell@4GuysFromRolla.com.
Särskilt tack till
Den här självstudieserien granskades av många användbara granskare. Huvudgranskare för den här handledningen var Zack Jones och Liz Shulok. Vill du granska mina kommande MSDN-artiklar? Om så är fallet, hör av dig på mitchell@4GuysFromRolla.com.