Dela via


Kapslade datawebbkontroller (VB)

av Scott Mitchell

Ladda ned PDF

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.

Varje kategori, tillsammans med dess produkter, visas

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.

Namnge de nya ObjectDataSource-kategoriernaDataSource

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.

Konfigurera ObjectDataSource att använda metoden CategoriesBLL Class s GetCategories

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.

Varje kategoris namn och beskrivning visas, avgränsad med en vågrät regel

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 ochItemDataBoundhä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 RepeaterItems. Bild 5 visar en skärmbild av resultatet.

Den yttre repeatern visar varje kategori. den inre listar produkterna för den kategorin

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 Objectomvandlar 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 Privatekommer 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.