共用方式為


巢狀資料 Web 控制項 (C#)

作者:Scott Mitchell

下載 PDF

在本教學課程中,我們將探索如何使用巢狀在另一個 Repeater 內使用 Repeater。 這些範例將說明如何以宣告方式和以程序設計方式填入內部 Repeater。

簡介

除了靜態 HTML 和數據系結語法之外,範本也可以包含 Web 控件和使用者控制件。 這些 Web 控制項可以透過宣告式、資料系結語法指派其屬性,或在適當的伺服器端事件處理程式中以程式設計方式存取。

藉由在範本內嵌控件,即可自定義及改善外觀和用戶體驗。 例如,在 GridView 控件中使用 TemplateFields 教學課程中,我們已瞭解如何在 TemplateField 中新增行事歷控件來自定義 GridView 的顯示,以顯示員工雇用日期;在 [ 將驗證控件新增至編輯和插入介面][自定義數據修改介面 ] 教學課程中,我們已瞭解如何藉由新增驗證控件、TextBox、DropDownLists 和其他 Web 控件來自定義編輯和插入介面。

範本也可以包含其他數據 Web 控制件。 也就是說,我們可以有一個 DataList,其中包含另一個 DataList (或 Repeater 或 GridView 或 DetailsView,依此類) 在其範本內。 這類介面的挑戰是將適當的數據系結至內部數據 Web 控件。 有一些不同的方法可供使用,範圍從使用 ObjectDataSource 的宣告式選項到程序設計方式。

在本教學課程中,我們將探索如何使用巢狀在另一個 Repeater 內使用 Repeater。 外部 Repeater 將會包含資料庫中每個類別的專案,並顯示類別的名稱和描述。 每個類別項目的內部重複程式都會顯示屬於該類別之每個產品的資訊, (請參閱點符清單中的圖 1) 。 我們的範例將說明如何以宣告方式和程序設計方式填入內部 Repeater。

每個類別及其產品都會列出

圖 1:每個類別及其產品都會列出 (按兩下即可檢視大小完整的影像)

步驟 1:建立類別清單

建置使用巢狀數據 Web 控件的頁面時,我發現設計、建立及測試最外層的數據 Web 控件會很有説明,甚至不必擔心內部巢狀控件。 因此,讓我們從逐步解說將重複程式新增至頁面所需的步驟,以列出每個類別的名稱和描述。

首先, NestedControls.aspx 開啟資料夾中的頁面 DataListRepeaterBasics ,並將 Repeater 控制項新增至頁面,並將其 ID 屬性設定為 CategoryList。 從 Repeater 的智慧標記中,選擇建立名為 CategoriesDataSource的新 ObjectDataSource。

將 New ObjectDataSource CategoriesDataSource 命名為

圖 2:將 New ObjectDataSource CategoriesDataSource 命名 (按兩下即可檢視完整大小的影像)

設定 ObjectDataSource,使其從 CategoriesBLL 類別 s GetCategories 方法提取其數據。

將 ObjectDataSource 設定為使用 CategoriesBLL 類別 s GetCategories 方法

圖 3:將 ObjectDataSource 設定為使用 CategoriesBLL 類別 s GetCategories 方法 (按兩下以檢視完整大小的影像)

若要指定 Repeater 的範本內容,我們必須移至 [來源] 檢視,然後手動輸入宣告式語法。 新增 , ItemTemplate 以顯示元素中的 <h4> 類別名稱,以及段落元素中的類別描述 (<p>) 。 此外,讓我們以水平規則分隔每個類別 () <hr> 。 進行這些變更之後,您的頁面應該包含 Repeater 和 ObjectDataSource 的宣告式語法,如下所示:

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

圖 4 顯示透過瀏覽器檢視時的進度。

每個類別的名稱和描述都會列出,並以水平規則分隔

圖 4:列出每個類別的名稱和描述,並以水平規則分隔 (按兩下以檢視大小完整的影像)

步驟 2:新增巢狀產品重複程式

完成類別清單之後,下一個工作就是將 Repeater 新增至 ,CategoryListItemTemplate以顯示屬於適當類別之這些產品的相關信息。 我們有許多方式可以擷取此內部 Repeater 的數據,其中兩種我們很快就會探索。 現在,讓我們在 Repeater s ItemTemplateCategoryList建立產品 Repeater。 具體來說,讓我們讓產品重複程式在每個專案符號清單中顯示每個產品,其中包含產品名稱和價格。

若要建立此重複程式,我們需要手動將內部 Repeater 宣告式語法和範本 CategoryList 輸入 s ItemTemplate。 在 Repeater s ItemTemplateCategoryList新增下列標記:

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

步驟 3:將 Category-Specific 產品系結至 ProductsByCategoryList 重複項

如果您此時透過瀏覽器瀏覽頁面,您的畫面看起來會與圖 4 相同,因為我們尚未將任何數據系結至重複程式。 我們有幾個方法可以抓取適當的產品記錄,並將其系結至 Repeater,有些比其他記錄更有效率。 此處的主要挑戰是針對指定的類別取回適當的產品。

要系結至內部 Repeater 控件的數據可以宣告方式、透過 Repeater s ItemTemplate中的 CategoryList ObjectDataSource 存取,或透過程式設計方式從 ASP.NET 頁的程式代碼後置頁面存取。 同樣地,此數據可以宣告方式系結至內部 Repeater - 透過內部 Repeater s DataSourceID 屬性,或透過宣告式數據系結語法,或透過程式設計方式參考 Repeater 事件處理程式中的 CategoryList 內部 Repeater ItemDataBound 、以程式設計方式設定其 DataSource 屬性,以及呼叫其 DataBind() 方法。 讓我們來探索這其中每一種方法。

使用 ObjectDataSource 控制項和ItemDataBound事件處理程式以宣告方式存取數據

由於我們已在此教學課程系列中廣泛使用 ObjectDataSource,因此存取此範例數據的最自然選擇是遵守 ObjectDataSource。 類別 ProductsBLLGetProductsByCategoryID(categoryID) 方法可傳回屬於指定 categoryID之 之產品的相關信息。 因此,我們可以將 ObjectDataSource 新增至 CategoryList Repeater s ItemTemplate ,並將其設定為從這個類別 s 方法存取其數據。

可惜的是,Repeater 不允許其範本透過設計視圖進行編輯,因此我們需要手動新增此 ObjectDataSource 控件的宣告式語法。 下列語法顯示CategoryList新增這個新的 ObjectDataSource () ProductsByCategoryDataSource 之後的 Repeater:ItemTemplate

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

使用 ObjectDataSource 方法時,我們需要將 ProductsByCategoryList Repeater s DataSourceID 屬性設定為 ID ObjectDataSource 的 (ProductsByCategoryDataSource) 。 此外,請注意,我們的 ObjectDataSource 有一個 <asp:Parameter> 元素,指定 categoryID 將傳遞至 GetProductsByCategoryID(categoryID) 方法的值。 但是,我們要如何指定此值? 在理想情況下,我們只能使用數據系結語法來設定 DefaultValue 元素的 <asp:Parameter> 屬性,如下所示:

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

不幸的是,數據系結語法只有在具有 DataBinding 事件的控件中才有效。 類別 Parameter 缺少這類事件,因此上述語法不合法,因此會導致運行時錯誤。

若要設定此值,我們需要為 CategoryList Repeater s ItemDataBound 事件建立事件處理程式。 回想一下,事件 ItemDataBound 會針對系結至重複程式的每個項目引發一次。 因此,每次針對外部 Repeater 引發此事件時,我們可以將目前 CategoryID 值指派給 ProductsByCategoryDataSource ObjectDataSource s CategoryID 參數。

使用下列程式代碼建立 CategoryList Repeater s 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();
    }
}

此事件處理程式會從確保我們處理數據項,而不是頁首、頁尾或分隔符項目開始。 接下來,我們會參考剛系結至目前 RepeaterItem的實際CategoriesRow實例。 最後,我們會參考 中的 ItemTemplate ObjectDataSource,並將其 CategoryID 參數值指派給 CategoryID 目前 RepeaterItem的 。

使用此事件處理程式時, ProductsByCategoryList 每個 RepeaterItem 中的 Repeater 都會系結至 類別中的 RepeaterItem 這些產品。 圖 5 顯示結果輸出的螢幕快照。

外部重複程式 清單 每個類別;內部一個 清單 該類別的產品

圖 5:外部重複程式 清單 每個類別;內部一個 清單 該類別的產品 (按兩下即可檢視大小完整的影像)

以程式設計方式依類別數據存取產品

除了使用 ObjectDataSource 來擷取目前類別的產品,我們可以在 ASP.NET 頁的程式代碼後置類別中建立方法, App_Code (或在資料夾或個別的類別庫專案中,) 傳回傳入 CategoryID時傳回適當產品集的產品。 假設我們在 ASP.NET 頁程式代碼後置類別中具有這類方法,且其名稱為 GetProductsInCategory(categoryID)。 有了這個方法,我們可以使用下列宣告式語法,將目前類別的產品系結至內部 Repeater:

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

Repeater s DataSource 屬性會使用數據系結語法來指出其數據來自 GetProductsInCategory(categoryID) 方法。 由於 Eval("CategoryID") 會傳回 類型的 Object值,因此我們會先將 物件轉換成 , Integer 然後再將它傳遞至 GetProductsInCategory(categoryID) 方法。 請注意,透過資料系結語法存取此處的 CategoryIDCategoryID外部 Repeater () CategoryList ,這是系結至資料表中記錄的 Categories 。 因此,我們知道 CategoryID 不能是資料庫 NULL 值,這就是為什麼我們可以不小心轉換 Eval 方法,而不檢查我們是否正在處理 DBNull

使用此方法時,我們需要建立 GetProductsInCategory(categoryID) 方法,並讓它擷取提供的適當產品 categoryID集。 我們只要傳回 類別 s GetProductsByCategoryID(categoryID) 方法所傳回ProductsDataTableProductsBLL ,即可這樣做。 讓我們在頁面的程式代碼後置類別中建立 GetProductsInCategory(categoryID)NestedControls.aspx 方法。 請使用下列程式代碼來執行此動作:

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

這個方法只會建立 方法的 ProductsBLL 實例,並傳回 方法的結果 GetProductsByCategoryID(categoryID) 。 請注意,方法必須標示 Public 為 或 Protected;如果方法標示 Private為 ,則無法從 ASP.NET 頁面的宣告式標記存取此方法。

進行這些變更以使用此新技術之後,請花點時間透過瀏覽器檢視頁面。 使用 ObjectDataSource 和 ItemDataBound 事件處理程式方法時,輸出應該與輸出相同 (回到圖 5,以查看螢幕快照) 。

注意

在 ASP.NET 頁程序代碼後置類別中建立 GetProductsInCategory(categoryID) 方法似乎很忙碌。 之後,這個方法只會建立 類別的 ProductsBLL 實例,並傳回其 GetProductsByCategoryID(categoryID) 方法的結果。 為什麼不只直接從內部 Repeater 中的數據系結語法呼叫這個方法,例如: DataSource='<%# ProductsBLL.GetProductsByCategoryID((int)(Eval("CategoryID"))) %>'? 雖然此語法不適用於類別的目前實作 ProductsBLL (,因為GetProductsByCategoryID(categoryID)方法是實例方法) ,但您可以修改ProductsBLL以包含靜態方法,或讓 類別包含靜態GetProductsByCategoryID(categoryID)Instance()方法,以傳回 類別的新實例ProductsBLL

雖然這類修改會消除 ASP.NET 頁程式代碼後置類別中方法的需求 GetProductsInCategory(categoryID) ,但程式代碼後置類別方法可讓我們更有彈性地處理擷取的數據,如我們稍後所見。

一次擷取所有產品資訊

我們檢查的兩種繁瑣技術,方法是呼叫 ProductsBLL 類別 s GetProductsByCategoryID(categoryID) 方法, (第一種方法透過 ObjectDataSource 來擷取目前類別的產品,第二種方法是透過 GetProductsInCategory(categoryID) 程式代碼後置類別中的 方法) 。 每次叫用這個方法時,商業規則層會向下呼叫數據存取層,該層會使用 SQL 語句來查詢資料庫,該語句會從ProductsCategoryID數據表中傳回其字段符合所提供輸入參數的數據列。

在系統中指定 N 個類別時,此方法會 nets N + 1 呼叫資料庫一個資料庫查詢,以取得所有類別,然後 呼叫 N 來取得每個類別特有的產品。 不過,我們可以只擷取兩個資料庫中所需的所有數據呼叫一次,以取得所有類別,另一個呼叫即可取得所有產品。 擁有所有產品之後,我們可以篩選這些產品,讓只有符合目前 CategoryID 的產品系結至該類別的內部 Repeater。

為了提供這項功能,我們只需要稍微修改 GetProductsInCategory(categoryID) ASP.NET 頁程序代碼後置類別中的方法。 如果尚未) 存取所有產品,然後只傳回根據傳入 CategoryID的產品篩選檢視,我們就可以改為直接ProductsBLLGetProductsByCategoryID(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;
}

請注意新增頁面層級變數 allProducts 這會保留所有產品的相關信息,並在第一次叫用方法時 GetProductsInCategory(categoryID) 填入。 確保allProducts物件已建立並填入之後,方法會篩選 DataTable 的結果,讓只有符合指定CategoryID的數據CategoryID列可供存取。 此方法可減少從 N + 1 存取資料庫到兩次的次數。

這項增強功能不會對頁面的轉譯標記造成任何變更,也不會讓其他方法傳回較少的記錄。 它只會減少對資料庫的呼叫數目。

注意

其中一個可能直覺的理由是減少數據庫存取數目,可保證能改善效能。 不過,這可能不是這種情況。 例如,如果您有大量的產品 CategoryIDNULL例如, 方法的呼叫 GetProducts 會傳回一些永遠不會顯示的產品。 此外,如果您只顯示類別的子集,傳回所有產品可能會浪費,如果您已實作分頁,這可能是這種情況。

一如往常,當分析兩種技術的效能時,唯一的確定性量值是執行針對應用程式常見案例量身打造的受控制測試。

摘要

在本教學課程中,我們已瞭解如何在另一個數據 Web 控件內巢狀化,特別是檢查如何讓外部 Repeater 顯示每個類別的專案,並列出點符清單中每個類別的產品的內部 Repeater。 建置巢狀使用者介面的主要挑戰在於存取和系結正確的數據至內部數據 Web 控件。 有各種可用的技術,我們在本教學課程中會檢查其中兩種技術。 第一個檢查方法使用外部數據 Web 控制項 ItemTemplate 中的 ObjectDataSource,該控件是透過其 DataSourceID 屬性系結至內部資料 Web 控制件。 第二種技術會透過 ASP.NET 頁面程序代碼後置類別中的 方法來存取數據。 這個方法接著可以透過數據系結語法系結至內部數據 Web 控件的 DataSource 屬性。

雖然本教學課程中所檢查的巢狀使用者介面使用 Repeater 巢狀於 Repeater 內,但這些技術可以延伸至其他數據 Web 控件。 您可以在 GridView 內巢狀化重複項,或 DataList 內的 GridView 等。

快樂的程序設計!

關於作者

Scott Mitchell 是 1998 年以來,1998 年與 Microsoft Web 技術合作的 篇 ASP/ASP.NET 書籍和 4GuysFromRolla.com 作者。 Scott 是獨立的顧問、訓練者和作者。 他的最新書籍是 Sams 在 24 小時內自行 ASP.NET 2.0。 您可以透過mitchell@4GuysFromRolla.com部落格連到,也可以透過其部落格來存取,網址為 http://ScottOnWriting.NET

特別感謝

本教學課程系列是由許多實用的檢閱者所檢閱。 本教學課程的首席檢閱者是 Zack Jones 和 Liz Shulok。 想要檢閱即將推出的 MSDN 文章嗎? 如果是,請將一行放在 mitchell@4GuysFromRolla.com。