Sdílet prostřednictvím


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

Scott Mitchell

Stáhnout PDF

V tomto kurzu prozkoumáme, jak používat opakovač vnořený do jiného repeateru. Příklady ukazují, jak naplnit vnitřní repeater deklarativně i programově.

Úvod

Šablony mohou kromě statického HTML a syntaxe datových vazeb obsahovat také webové ovládací prvky a uživatelské ovládací prvky. Tyto webové ovládací prvky mohou mít své vlastnosti přiřazené prostřednictvím deklarativní syntaxe datové vazby nebo je možné k němu přistupovat programově 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 použití TemplateFields v GridView ovládacího prvku kurzu jsme viděli, jak přizpůsobit zobrazení GridView s přidáním ovládacího prvku Calendar v TemplateField zobrazit datum náboru zaměstnance; v kurzech Přidání ověřovacích ovládacích prvků do úprav a vkládání rozhraní a Přizpůsobení rozhraní pro úpravu dat jsme viděli, jak upravit a vložit rozhraní 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 mohou také obsahovat další webové ovládací prvky dat. To znamená, že můžeme mít dataList, který obsahuje další dataList (nebo Repeater, GridView nebo DetailsView atd.) v rámci svých šablon. Výzvou s takovým rozhraním je vazba příslušných dat na vnitřní data webového ovládacího prvku. K dispozici je několik různých přístupů, od deklarativních možností pomocí ObjectDataSource až po programové.

V tomto kurzu prozkoumáme, jak používat opakovač vnořený do jiného repeateru. Vnější repeater bude obsahovat položku pro každou kategorii v databázi, která zobrazí název a popis kategorie. Vnitřní repeater každé položky kategorie zobrazí informace o každém produktu, který patří do dané kategorie (viz obrázek 1) v seznamu s odrážkami. Naše příklady ukazují, jak naplnit vnitřní repeater deklarativně i programově.

Každá kategorie, spolu s produkty, jsou uvedeny

Obrázek 1: Každá kategorie spolu se svými produkty je uvedena (kliknutím zobrazíte obrázek v plné velikosti)

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

Při vytváření stránky, která používá webové ovládací prvky vnořených dat, je užitečné navrhnout, vytvořit a otestovat nejkrajnější datový webový ovládací prvek, aniž by se museli starat o vnitřní vnořený ovládací prvek. Proto pojďme začít tím, že si projdeme kroky potřebné k přidání repeateru na stránku, která obsahuje název a popis každé kategorie.

Začněte tím, že NestedControls.aspx otevřete stránku ve DataListRepeaterBasics složce a přidáte na stránku ovládací prvek Repeater a nastavíte jeho ID vlastnost na CategoryList. Z inteligentní značky Repeater zvolte vytvoření nového objektu ObjectDataSource s názvem CategoriesDataSource.

Pojmenujte Nový objektDataSource CategoriesDataSource.

Obrázek 2: Pojmenujte nový objektDataSource CategoriesDataSource (kliknutím zobrazíte obrázek v plné velikosti)

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

Nakonfigurujte ObjectDataSource tak, aby používal třídu CategoriesBLL s metodou GetCategories.

Obrázek 3: Konfigurace objektu ObjectDataSource pro použití CategoriesBLL metody Class s GetCategories (kliknutím zobrazíte obrázek v plné velikosti)

Pokud chcete určit obsah šablony Repeater s, musíme přejít do zobrazení Zdroj a ručně zadat deklarativní syntaxi. Přidejte objekt ItemTemplate , který zobrazuje název kategorie v elementu <h4> a popis kategorie v elementu odstavce (<p>). Kromě toho oddělme každou kategorii vodorovným pravidlem (<hr>). Po provedení těchto změn by stránka měla obsahovat deklarativní syntaxi pro Repeater 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 znázorňuje náš postup při prohlížení prostřednictvím prohlížeče.

Název a popis jednotlivých kategorií jsou uvedené a jsou oddělené vodorovným pravidlem.

Obrázek 4: Název a popis každé kategorie jsou uvedené a jsou oddělené vodorovným pravidlem (kliknutím zobrazíte obrázek v plné velikosti).

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

Po dokončení výpisu kategorií je naším dalším úkolem přidat do CategoryListItemTemplate seznamu opakovač, který zobrazuje informace o 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. Dva z těchto způsobů prozkoumáme za chvíli. Prozatím pojďme vytvořit produkty Repeater v rámci CategoryList repeater s ItemTemplate. Konkrétně nechte produkt Repeater zobrazit každý produkt v seznamu s odrážkami s každou položkou seznamu včetně názvu produktu a ceny.

K vytvoření tohoto repeateru musíme ručně zadat vnitřní syntaxi a šablony repeateru CategoryList do s ItemTemplate. Do rutiny Repeater přidejte ItemTemplatenásledující kódCategoryList:

<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 Category-Specific Products k opakovači ProductsByCategoryList

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

Data pro vytvoření vazby s vnitřním ovládacím prvkem Repeater lze přistupovat buď deklarativně prostřednictvím ObjectDataSource v CategoryList repeateru ItemTemplate, nebo programově z ASP.NET stránky s kódem na pozadí. Podobně lze tato data svázat s vnitřním repeaterem buď deklarativně – prostřednictvím vnitřní vlastnosti Repeater s DataSourceID nebo prostřednictvím deklarativní syntaxe datové vazby nebo programově odkazováním na vnitřní repeater v CategoryList obslužné rutině ItemDataBound události Repeater, programovým nastavením jeho DataSource vlastnosti a voláním jeho DataBind() metody. Pojďme prozkoumat každý z těchto přístupů.

Deklarativní přístup k datům pomocí ovládacího prvku ObjectDataSource a obslužné rutinyItemDataBoundudálosti

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

Repeater bohužel neumožňuje úpravy svých šablon prostřednictvím návrhového zobrazení, takže musíme přidat deklarativní syntaxi pro tento ovládací prvek ObjectDataSource ručně. Následující syntaxe zobrazuje CategoryList hodnoty Repeater ItemTemplate po přidání tohoto nového objektu 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 musíme nastavit ProductsByCategoryList vlastnost Repeater s DataSourceID na ID vlastnost ObjectDataSource (ProductsByCategoryDataSource). Všimněte si také, že náš ObjectDataSource má <asp:Parameter> element, který určuje categoryID hodnotu, která bude předána GetProductsByCategoryID(categoryID) do metody . Ale jak tuto hodnotu určíme? V ideálním případě bychom byli schopni jednoduše nastavit DefaultValue vlastnost elementu <asp:Parameter> pomocí syntaxe datové vazby, například takto:

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

Syntaxe vazby dat je bohužel platná jenom v ovládacích prvcích, které mají DataBinding událost. Třída Parameter takovou událost nemá, a proto je výše uvedená syntaxe neplatná a bude mít za následek chybu za běhu.

Abychom mohli nastavit tuto hodnotu, musíme vytvořit obslužnou rutinu CategoryList události pro událost Repeater s ItemDataBound . Vzpomeňte si, že událost se ItemDataBound aktivuje jednou pro každou položku vázanou na repeater. Proto pokaždé, když se tato událost aktivuje pro vnější Repeater, můžeme přiřadit aktuální CategoryID hodnotu k parametru ProductsByCategoryDataSource ObjectDataSource s CategoryID .

Vytvořte obslužnou rutinu CategoryList události pro událost Repeater s ItemDataBound pomocí následujícího kódu:

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 zajišťujeme, že pracujeme s datovou položkou, a ne s položkou záhlaví, zápatí nebo oddělovače. Dále odkazujeme na skutečnou CategoriesRow instanci, která byla právě vázána k aktuálnímu RepeaterItemobjektu . Nakonec odkazujeme na ObjectDataSource v ItemTemplate a přiřadíme jeho CategoryID hodnotu parametru k aktuální RepeaterItemhodnotě CategoryID .

S touto obslužnou rutinou ProductsByCategoryList události je repeater v každém RepeaterItem vázán na produkty v RepeaterItem kategorii s. Obrázek 5 ukazuje snímek obrazovky s výsledným výstupem.

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

Obrázek 5: Vnější opakovač Seznamy každou kategorii; vnitřní Seznamy produkty pro danou kategorii (kliknutím zobrazíte obrázek v plné velikosti)

Přístup k datům produktů 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ší třídě ASP.NET kódu stránky s (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í v CategoryID. Představte si, že takovou metodu máme v naší třídě kódu stránky ASP.NET a že má název GetProductsInCategory(categoryID). S touto metodou bychom mohli svázat produkty pro aktuální kategorii s vnitřním repeaterem pomocí následující deklarativní syntaxe:

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

Vlastnost Repeater s DataSource používá syntaxi datové vazby k označení, že data pocházejí z GetProductsInCategory(categoryID) metody . Vzhledem k tomu, že Eval("CategoryID") vrátí hodnotu typu Object, přetypujeme objekt na před Integer předáním GetProductsInCategory(categoryID) do metody . Všimněte siCategoryID, že zde prostřednictvím syntaxe vazby dat je ve vnějším repeateru (CategoryList), který je CategoryID vázaný na záznamy v Categories tabulce. Proto víme, že CategoryID to nemůže být hodnota databáze NULL , a proto můžeme metodu Eval slepě přetypovat bez kontroly, jestli se jedná o DBNull.

Při tomto přístupu potřebujeme vytvořit metodu GetProductsInCategory(categoryID) a nechat ji načíst odpovídající sadu produktů vzhledem k dodanému categoryID. Můžeme to udělat jednoduchým vrácením ProductsDataTable hodnoty vrácené metodou ProductsBLL třídy s GetProductsByCategoryID(categoryID) . Pojďme vytvořit metodu GetProductsInCategory(categoryID) ve třídě kódu na pozadí pro naši NestedControls.aspx stránku. Udělejte to pomocí následujícího kódu:

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čená Public nebo Protected; pokud je metoda označená Private, nebude přístupná z deklarativního kódu ASP.NET stránky.

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

Poznámka

Vytvoření metody ve třídě ASP.NET stránky s kódem na pozadí může vypadat jako zaneprázdnění GetProductsInCategory(categoryID) . Koneckonců, tato metoda jednoduše vytvoří instanci ProductsBLL třídy a vrátí výsledky její GetProductsByCategoryID(categoryID) metody. Proč nevolat tuto metodu přímo ze syntaxe vazby dat ve vnitřním 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 instanční metodou), můžete úpravou ProductsBLL zahrnout statickou GetProductsByCategoryID(categoryID) metodu nebo zahrnout statickou Instance() metodu, která vrátí novou instanci ProductsBLL třídy.

I když by takové úpravy eliminovaly potřebu GetProductsInCategory(categoryID) metody ve třídě kódu na pozadí ASP.NET stránky, metoda třídy kódu na pozadí nám poskytuje větší flexibilitu při práci s načtenými daty, jak uvidíme za chvíli.

Načítání všech informací o produktu najednou

Dvě pervious techniky, které jsme prozkoumali, zachytí tyto produkty pro aktuální kategorii voláním ProductsBLL metody třídy s GetProductsByCategoryID(categoryID) (první přístup to udělal prostřednictvím ObjectDataSource, druhý metodou GetProductsInCategory(categoryID) ve třídě kódu na pozadí). Při každém vyvolání této metody volá vrstva obchodní logiky vrstvu přístupu k datům, která dotazuje databázi pomocí příkazu SQL, který vrací řádky z Products tabulky, jejíž CategoryID pole odpovídá zadanému vstupnímu parametru.

Vzhledem k N kategoriím v systému tento přístup vytěsní N + 1 volání do databáze jeden databázový dotaz, aby se získaly všechny kategorie, a pak volání N pro získání produktů specifických pro každou kategorii. Můžeme ale načíst všechna potřebná data v pouhých dvou databázových voláních jedním voláním, abychom získali všechny kategorie a dalším, abychom získali všechny produkty. Jakmile budeme mít všechny produkty, můžeme tyto produkty filtrovat tak, aby pouze produkty, které odpovídají aktuálnímu CategoryID , byly vázány na vnitřní repeater dané kategorie.

Abychom mohli tuto funkci poskytnout, stačí provést pouze mírnou úpravu GetProductsInCategory(categoryID) metody v naší třídě kódu ASP.NET stránky s kódem na pozadí. Místo toho, abychom slepě vraceli výsledky ProductsBLL metody třídy GetProductsByCategoryID(categoryID) , můžeme místo toho nejprve získat přístup ke všem produktům (pokud k nim ještě nedošlo) a pak vrátit jenom filtrované zobrazení produktů na základě předávaného CategoryIDobjektu .

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í, že allProducts objekt byl vytvořen a naplněn, metoda filtruje výsledky DataTable tak, aby byly přístupné pouze ty řádky, které CategoryID odpovídají zadanému CategoryID . Tento přístup snižuje počet přístupů k databázi z N + 1 dolů na dva.

Toto vylepšení nezavádí žádnou změnu vykreslených značek stránky ani nepřináší méně záznamů než jiný přístup. Jednoduše se tím sníží počet volání do databáze.

Poznámka

Můžete intuitivně zdůvodnit, že snížení počtu přístupů k databázi by určitě zvýšilo výkon. To ale nemusí být tento případ. Pokud máte velký počet produktů, jejichž CategoryID je NULLnapříklad , pak volání GetProducts metody vrátí počet produktů, které se nikdy nezobrazí. Kromě toho může být vrácení všech produktů zbytečné, pokud zobrazujete pouze podmnožinu kategorií, což může být případ, pokud jste implementovali stránkování.

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

Souhrn

V tomto kurzu jsme viděli, jak vnořit jeden datový webový ovládací prvek do jiného, konkrétně jsme prozkoumali, jak nechat vnější repeater zobrazit položku pro každou kategorii s vnitřním Repeaterem, který obsahuje produkty pro každou kategorii v seznamu s odrážkami. Hlavní výzvou při vytváření vnořeného uživatelského rozhraní je přístup ke správným datům a jejich vazba na vnitřní ovládací prvek webu. K dispozici jsou různé techniky, z nichž dvě jsme prozkoumali v tomto kurzu. První zkoumaný přístup použil ObjectDataSource v ovládacím prvku ItemTemplate vnější data web, který byl vázán na vnitřní data Web ovládací prvek prostřednictvím jeho DataSourceID vlastnosti. Druhá technika přistupovala k datům prostřednictvím metody ve třídě kódu stránky ASP.NET. Tuto metodu pak lze vázat na vlastnost ovládacího prvku DataSource web vnitřních dat prostřednictvím syntaxe vazby dat.

Zatímco vnořené uživatelské rozhraní zkoumané v tomto kurzu používalo opakovač vnořený v repeateru, tyto techniky lze rozšířit na další datové webové ovládací prvky. Repeater můžete vnořit do objektu GridView nebo GridView v seznamu DataList atd.

Šťastné programování!

O autorovi

Scott Mitchell, autor sedmi knih o ASP/ASP.NET a zakladatel 4GuysFromRolla.com, pracuje s webovými technologiemi Microsoftu od roku 1998. Scott pracuje jako nezávislý konzultant, školitel a spisovatel. Jeho nejnovější kniha je Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Můžete ho najít na mitchell@4GuysFromRolla.comadrese . nebo prostřednictvím jeho blogu, který najdete na http://ScottOnWriting.NETadrese .

Zvláštní poděkování

Tato série kurzů byla zkontrolována mnoha užitečnými recenzenty. Hlavními recenzenty tohoto kurzu byli Zack Jones a Liz Shulok. Chcete si projít moje nadcházející články na WEBU MSDN? Pokud ano, dejte mi čáru na mitchell@4GuysFromRolla.comadresu .