Sdílet prostřednictvím


Webové ovládací prvky vnořených dat (C#)

od Scotta Mitchella

Stáhnout PDF

V tomto kurzu se podíváme, jak používat repeater vnořený uvnitř jiného repeateru. Příklady budou ilustrovat, jak naplnit vnitřní repeater deklarativním i programovým způsobem.

Úvod

Kromě statické syntaxe HTML a vazby dat mohou šablony obsahovat také webové ovládací prvky a uživatelské ovládací prvky. Tyto webové ovládací prvky můžou mít přiřazené své vlastnosti prostřednictvím deklarativní syntaxe, syntaxe vazby dat nebo se k nim dá přistupovat prostřednictvím kódu programu v příslušných obslužných rutinách událostí na straně serveru.

Vložením ovládacích prvků do šablony je možné přizpůsobit a vylepšit vzhled a uživatelské prostředí. Například v kurzu Použití TemplateFields v ovládacím prvku GridView jsme viděli, jak přizpůsobit zobrazení GridView přidáním ovládacího prvku Kalendář do TemplateField, aby se zobrazilo datum přijetí zaměstnance; v Přidání ověřovacích ovládacích prvků do rozhraní pro úpravy a vkládání a Přizpůsobení rozhraní pro úpravu dat jsme viděli, jak přizpůsobit rozhraní pro úpravy a vkládání přidáním ověřovacích ovládacích prvků, textových polí, rozevíracích seznamů a dalších webových ovládacích prvků.

Šablony můžou obsahovat i jiné webové ovládací prvky dat. To znamená, že můžeme mít DataList, který obsahuje jiný Objekt DataList (nebo Repeater, GridView nebo DetailsView atd.) v rámci svých šablon. Výzvou takového rozhraní je propojení odpovídajících dat s vnitřním webovým datovým ovládacím prvkem. Existuje několik různých přístupů, od deklarativních možností pomocí ObjectDataSource až po programové.

V tomto kurzu se podíváme, jak používat repeater vnořený uvnitř jiného repeateru. Vnější repeater bude obsahovat položku pro každou kategorii v databázi a zobrazí název a popis kategorie. Každá položka kategorie s vlastním vnitřním opakovačem zobrazí informace o každém produktu patřícím do této kategorie v seznamu s odrážkami (viz obrázek 1). Naše příklady ilustrují, jak naplnit vnitřní repeater deklarativním i programovým způsobem.

Každá kategorie spolu se svými produkty je uvedena v seznamu

Obrázek 1: Každá kategorie spolu s produkty je uvedena (kliknutím zobrazíte obrázek s plnou velikostí)

Krok 1: Vytvoření výpisu kategorií

Při vytváření stránky, která používá vnořené datové webové ovládací prvky, považuji za užitečné navrhnout, vytvořit a otestovat nejprve vnější datový webový ovládací prvek, aniž bych se musel starat o vnitřní vnořené ovládací prvky. Začněme tím, že si projdeme kroky potřebné k přidání repeateru na stránku se seznamem názvů a popisů pro každou kategorii.

Začněte otevřením NestedControls.aspx stránky ve DataListRepeaterBasics složce a přidáním ovládacího prvku Repeater na stránku a nastavením jeho ID vlastnosti na CategoryList. Z inteligentní značky Repeater zvolte, že chcete vytvořit nový ObjectDataSource s názvem CategoriesDataSource.

Pojmenujte nový objekt ObjectDataSource jako CategoriesDataSource

Obrázek 2: Pojmenování nového objektu ObjectDataSource CategoriesDataSource (kliknutím zobrazíte obrázek s plnou velikostí)

Nakonfigurujte ObjectDataSource tak, aby načítá data z CategoriesBLL metody třídy GetCategories .

Konfigurace ObjectDataSource pro použití metody GetCategories třídy CategoriesBLL

Obrázek 3: Konfigurace ObjectDataSource tak, aby používala metodu CategoriesBLL třídy GetCategories (kliknutím zobrazíte obrázek s plnou velikostí)

Pokud chcete zadat obsah šablony Repeater s, musíme přejít do zobrazení Zdroj a ručně zadat deklarativní syntaxi. Přidejte název ItemTemplate kategorie v elementu <h4> a popis kategorie v elementu odstavce (<p>). Kromě toho pojďme jednotlivé kategorie oddělit vodorovným pravidlem (<hr>). Po provedení těchto změn by stránka měla obsahovat deklarativní syntaxi repeateru a ObjectDataSource, která je podobná následující:

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

Obrázek 4 ukazuje průběh zobrazení v prohlížeči.

Každý název a popis kategorie je uvedený oddělený vodorovným pravidlem.

Obrázek 4: V seznamu jsou uvedeny názvy a popisy jednotlivých kategorií oddělené vodorovným pravidlem (kliknutím zobrazíte obrázek s plnou velikostí).

Krok 2: Přidání vnořeného opakovače produktu

Po dokončení seznamu kategorií je naším dalším úkolem přidat opakovač do CategoryList s ItemTemplate , který zobrazuje informace o těchto produktech patřících do příslušné kategorie. Existuje několik způsobů, jak můžeme načíst data pro tento vnitřní repeater, z nichž dva brzy prozkoumáme. Prozatím pojďme jednoduše vytvořit produkty Repeater uvnitř CategoryList Repeateru ItemTemplate. Konkrétně umožněme, aby Repeater zobrazoval každý produkt v odrážkách, přičemž každá položka seznamu bude obsahovat název produktu a jeho cenu.

Pro vytvoření tohoto repeateru musíme ručně zadat deklarativní syntaxi a šablony vnitřního repeateru do CategoryList a ItemTemplate. Přidejte následující označení do CategoryListRepeateru 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>

Krok 3: Vytvoření vazby produktů Category-Specific s repeaterem ProductsByCategoryList

Pokud stránku nyní navštívíte v prohlížeči, vaše obrazovka bude vypadat stejně jako na obrázku 4, protože jsme zatím nesvázali žádná data s Repeaterem. Existuje několik způsobů, jak můžeme získat příslušné záznamy o produktech a svázat je s repeaterem, některé efektivnější než jiné. Hlavní výzvou je vrácení vhodných produktů pro zadanou kategorii.

Data, která mají být svázána s vnitřním ovládacím prvkem Repeater, mohou být přístupná buď deklarativně, prostřednictvím ObjectDataSource v CategoryList Repeater s ItemTemplate, nebo programově, ze stránky ASP.NET ze stránky pro zpracování kódu. Podobně mohou být tato data vázána na vnitřní Repeater buď deklarativně - prostřednictvím vlastnosti vnitřního Repeateru DataSourceID nebo pomocí deklarativní syntaxe pro datové vazby, nebo programově odkazováním na vnitřní Repeater v obslužné rutině události CategoryList Repeateru ItemDataBound, programově nastavením jeho vlastnosti DataSource a voláním jeho metody DataBind(). Pojďme se podívat na každý z těchto přístupů.

Přístup k datům deklarativně pomocí ovládacího prvku ObjectDataSource a obslužné rutiny událostiItemDataBound

Vzhledem k tomu, že jsme v této sérii kurzů používali ObjectDataSource, je nejpřirozenější volbou pro přístup k datům v tomto příkladu zůstat u ObjectDataSource. Třída ProductsBLL má metodu GetProductsByCategoryID(categoryID) , která vrací informace o produktech, které patří do zadaného categoryID. Proto můžeme přidat ObjectDataSource do CategoryList opakovače ItemTemplate a nakonfigurovat ho tak, aby měl přístup ke svým datům z metody této třídy.

Repeater bohužel neumožňuje úpravy šablon v návrhovém zobrazení, takže musíme přidat deklarativní syntaxi pro tento ovládací prvek ObjectDataSource ručně. Následující syntaxe ukazuje CategoryList Repeater s ItemTemplate po přidání tohoto nového 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>

Při použití přístupu ObjectDataSource potřebujeme nastavit ProductsByCategoryList vlastnost Repeateru DataSourceID na ID ObjectDataSource (ProductsByCategoryDataSource). Všimněte si také, že naše ObjectDataSource má <asp:Parameter> prvek, který určuje categoryID hodnotu, která bude předána do GetProductsByCategoryID(categoryID) metody. Jak ale tuto hodnotu určíme? V ideálním případě bychom mohli jednoduše nastavit DefaultValue vlastnost elementu <asp:Parameter> pomocí syntaxe vazby dat, například takto:

<asp:Parameter Name="CategoryID" Type="Int32"
     DefaultValue='<%# Eval("CategoryID")' />

Syntaxe vazby dat je bohužel platná pouze v ovládacích prvcích, které mají DataBinding událost. Třída Parameter postrádá takovou událost, a proto je výše uvedená syntaxe neplatná a vede k chybě za běhu programu.

K nastavení této hodnoty potřebujeme vytvořit obslužnou rutinu události pro CategoryList Repeater s ItemDataBound. Vzpomeňte si, že událost ItemDataBound se aktivuje jednou pro každou položku vázanou na repeater. Proto při každém spuštění této události pro vnější Repeater můžeme přiřadit aktuální CategoryID hodnotu k parametru ProductsByCategoryDataSource ObjectDataSource s CategoryID .

Vytvořte obslužnou rutinu události CategoryList Repeater s následujícím kódem ItemDataBound.

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();
    }
}

Tato obslužná rutina události začíná tím, že nejdříve ověřujeme, zda se jedná o datovou položku, nikoli o položku záhlaví, zápatí nebo oddělovače. Dále odkazujeme na skutečnou CategoriesRow instanci, která byla právě vázána na aktuální RepeaterItem. Nakonec odkazujeme na ObjectDataSource v ItemTemplate a přiřadíme jeho CategoryID hodnotu parametru CategoryID k aktuálnímu RepeaterItem.

S touto obslužnou rutinou pro události je ProductsByCategoryList Repeater v každém RepeaterItem vázán na tyto produkty v kategorii RepeaterItem. Obrázek 5 znázorňuje snímek obrazovky s výsledným výstupem.

Vnější opakovač uvádí každou kategorii; vnitřní uvádí produkty pro danou kategorii

Obrázek 5: Vnější opakovač uvádí každou kategorii; vnitřní opakovač uvádí produkty pro tuto kategorii (Klikněte pro zobrazení obrázku v plné velikosti)

Přístup k produktům podle kategorií prostřednictvím kódu programu

Místo použití ObjectDataSource k načtení produktů pro aktuální kategorii bychom mohli vytvořit metodu v naší ASP.NET stránce s kódem třídy (nebo ve App_Code složce nebo v samostatném projektu knihovny tříd), která vrátí příslušnou sadu produktů při předání do CategoryID. Představte si, že jsme takovou metodu měli v naší třídě kódu stránky ASP.NET a že byla pojmenována GetProductsInCategory(categoryID). S touto metodou bychom mohli přidružit produkty pro aktuální kategorii na vnitřní Repeater pomocí následující deklarativní syntaxe.

<asp:Repeater runat="server" ID="ProductsByCategoryList" EnableViewState="False"
      DataSource='<%# GetProductsInCategory((int)(Eval("CategoryID"))) %>'>
  ...
</asp:Repeater>

Vlastnost Repeateru DataSource používá syntaxi vazby dat, aby označila, že data pocházejí z metody GetProductsInCategory(categoryID). Vzhledem k tomu, že Eval("CategoryID") vrátí hodnotu typu Object, přetypujeme objekt na Integer před tím, než ho předáme do metody GetProductsInCategory(categoryID). Všimněte si, že CategoryID se zde přistupuje prostřednictvím syntaxe vazby na data jako CategoryID ve vnějším repeateru (CategoryList), který je spojený se záznamy v tabulce Categories. Proto víme, že CategoryID nemůže být hodnota databáze NULL, což je důvod, proč můžeme metodu Eval převést bez kontroly, zda pracujeme s DBNull.

"S tímto přístupem musíme vytvořit metodu GetProductsInCategory(categoryID) a ta by měla načíst odpovídající sadu produktů na základě daného categoryID." Můžeme to provést jednoduše vrácením ProductsDataTable, které vrací metoda třídy ProductsBLLGetProductsByCategoryID(categoryID). Pojďme vytvořit metodu GetProductsInCategory(categoryID) ve třídě kódu pro naši NestedControls.aspx stránku. Použijte k tomu následující kód:

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);
}

Tato metoda jednoduše vytvoří instanci ProductsBLL metody a vrátí výsledky GetProductsByCategoryID(categoryID) metody. Všimněte si, že metoda musí být označena Public nebo Protected; pokud je metoda označena Private, nebude přístupná z deklarativní značky ASP.NET stránky.

Po provedení těchto změn pro použití této nové techniky si chvíli prohlédněte stránku v prohlížeči. Výstup by měl být shodný s výstupem při použití přístupu komponenty ObjectDataSource a obslužné rutiny události ItemDataBound (viz obrázek 5 a podívejte se na snímek obrazovky).

Poznámka:

Může to vypadat jako zbytečná práce vytvořit GetProductsInCategory(categoryID) metodu v třídě code-behind stránky ASP.NET. Koneckonců tato metoda jednoduše vytvoří instanci ProductsBLL třídy a vrátí výsledky své GetProductsByCategoryID(categoryID) metody. Proč tuto metodu nevolejte přímo ze syntaxe vazby dat ve vnitřní repeateru, například: DataSource='<%# ProductsBLL.GetProductsByCategoryID((int)(Eval("CategoryID"))) %>' I když tato syntaxe nebude fungovat s naší aktuální implementací ProductsBLL třídy (protože GetProductsByCategoryID(categoryID) metoda je metoda instance), můžete upravit ProductsBLL tak, aby zahrnovala statickou GetProductsByCategoryID(categoryID) metodu nebo aby třída obsahovala statickou Instance() metodu pro vrácení nové instance ProductsBLL třídy.

I když by takové úpravy odstranily potřebu metody v třídě code-behind na ASP.NET stránce, metoda v této třídě nám poskytuje větší flexibilitu při práci s načtenými daty, jak uvidíme za okamžik.

Načtení všech informací o produktu najednou

Dvě předchozí techniky, které jsme prozkoumali, získávají tyto produkty pro aktuální kategorii voláním metody ProductsBLL třídy GetProductsByCategoryID(categoryID). (První přístup to udělal prostřednictvím ObjectDataSource, druhý prostřednictvím metody GetProductsInCategory(categoryID) ve třídě pro pozadí kódu.) Pokaždé, když je tato metoda vyvolána, vrstva obchodní logiky volá vrstvu přístupu k datům, která dotazuje databázi pomocí příkazu SQL, který vrací řádky z Products tabulky, jejichž CategoryID pole odpovídá zadanému vstupnímu parametru.

Vzhledem k N kategoriím v systému tento přístup vede k celkovým N + 1 voláním do databáze, s jedním dotazem pro získání všech kategorií, a následně N voláními pro získání produktů specifických pro každou kategorii. Můžeme ale načíst všechna potřebná data pouhými dvěma databázovými voláními: jedno, abychom získali všechny kategorie, a druhé, abychom získali všechny produkty. Jakmile máme všechny produkty, můžeme tyto produkty filtrovat tak, aby pouze produkty odpovídající aktuálnímu stavu CategoryID byly vázány na vnitřní repeater této kategorie.

Abychom mohli tuto funkci poskytnout, musíme jen mírně upravit metodu GetProductsInCategory(categoryID) v naší třídě kódu stránky ASP.NET. Místo nevidomého vrácení výsledků ProductsBLL metody třídy GetProductsByCategoryID(categoryID) můžeme místo toho nejprve přistupovat ke všem produktům (pokud ještě nebyly přístupné) a pak vrátit pouze filtrované zobrazení produktů na základě předaného CategoryID.

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;
}

Všimněte si přidání proměnné na úrovni stránky , allProducts. Obsahuje informace o všech produktech a naplní se při GetProductsInCategory(categoryID) prvním vyvolání metody. Po ověření vytvoření a naplnění objektu allProducts metoda vyfiltruje výsledky tabulky DataTable tak, aby byly přístupné pouze řádky, jejichž CategoryID zadané hodnoty odpovídají zadanému CategoryID objektu. Tento přístup snižuje počet přístupů k databázi z N + 1 dolů na dvě.

Toto vylepšení neuvádí žádné změny vykresleného značkování stránky ani nepřináší méně výsledků než druhý přístup. Jednoduše snižuje počet volání databáze.

Poznámka:

Člověk by mohl intuitivně usoudit, že snížení počtu přístupů k databázi by jistě zlepšilo výkon. To ale nemusí být tento případ. Pokud máte velký počet produktů, jejichž CategoryID je NULL, například volání GetProducts metody vrátí počet produktů, které se nikdy nezobrazí. Vrácení všech produktů může být navíc plýtvání, pokud zobrazujete pouze podmnožinu kategorií, což může být případ, kdy jste implementovali stránkování.

Stejně jako vždy platí, že pokud jde o analýzu výkonu dvou technik, jediným měřítkem jistoty je spouštění kontrolovaných testů přizpůsobených běžným scénářům vaší aplikace.

Shrnutí

V tomto kurzu jsme viděli, jak vnořit jeden datový webový ovládací prvek do jiného, konkrétně jsme zkoumali, jak mít vnější Repeater zobrazující položku pro každou kategorii s vnitřním Repeaterem uvádějícím produkty pro každou kategorii v odrážkovém seznamu. Hlavní výzvou při vytváření vnořeného uživatelského rozhraní je přístup k datům a správná vazba těchto dat k vnitřnímu webovému ovládacímu prvku. K dispozici jsou různé techniky, z nichž dvě jsme prozkoumali v tomto kurzu. První přístup zkoumal použití ObjectDataSource ve vnějším webovém datovém ovládacím prvku ItemTemplate, který byl prostřednictvím své DataSourceID vlastnosti svázán s vnitřním webovým datovým ovládacím prvkem. Druhá technika přistupovala k datům prostřednictvím metody ve třídě kódu na stránce ASP.NET. Tato metoda pak může být vázána na vlastnost vnitřních dat webového ovládacího prvku DataSource prostřednictvím syntaxe datového připojení.

Zatímco vnořené uživatelské rozhraní prověřované v tomto kurzu používalo repeater vnořený do repeateru, tyto techniky lze rozšířit na ostatní datové webové ovládací prvky. Do objektu GridView můžete vnořit Repeater, nebo GridView do DataList a podobně.

Šťastné programování!

O autorovi

Scott Mitchell, autor sedmi knih ASP/ASP.NET a zakladatel 4GuysFromRolla.com, pracuje s webovými technologiemi Microsoftu od roku 1998. Scott pracuje jako nezávislý konzultant, trenér a spisovatel. Jeho nejnovější kniha je Sams: Nauč se ASP.NET 2.0 za 24 hodin. Může být dosažitelný na mitchell@4GuysFromRolla.comadrese .

Zvláštní díky

Tato série kurzů byla zkontrolována mnoha užitečnými recenzenty. Vedoucí hodnotící tohoto kurzu byli Zack Jones a Liz Shulok. Chcete si projít nadcházející články MSDN? Pokud ano, napište mi zprávu na mitchell@4GuysFromRolla.com.