共用方式為


巢狀資料 Web 控制項 (VB)

作者 :Scott Mitchell

下載 PDF

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

簡介

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

藉由在範本中內嵌控件,即可自定義和改善外觀和用戶體驗。 例如,在 GridView 控件教學課程中使用 TemplateFields 中,我們已瞭解如何在 TemplateField 中新增行事歷控件來自定義 GridView 顯示,以顯示員工的雇用日期;在將驗證控件新增至編輯和插入介面和自定義數據修改介面教學課程中,我們已瞭解如何藉由新增驗證控件、TextBoxes、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:新增巢狀產品重複程式

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

若要建立這個 Repeater,我們需要手動將內部 Repeater 的宣告式語法和範本輸入 CategoryList s ItemTemplate。 在 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>

步驟 3:將 Category-Specific 產品系結至 ProductsByCategoryList Repeater

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

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

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

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

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

<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 方法時,我們需要將 Repeater 的 DataSourceID 屬性設定ProductsByCategoryListID 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 的每個項目引發一次事件。 因此,每次針對外部 Repeater 引發此事件時,我們都可以將目前的 CategoryID 值指派給 ProductsByCategoryDataSource ObjectDataSource s CategoryID 參數。

使用下列程式代碼建立 CategoryList Repeater 事件的 ItemDataBound 事件處理程式:

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

此事件處理程式一開始會確保我們會處理數據項,而不是頁首、頁尾或分隔符專案。 接下來,我們會參考剛系結至目前 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(CType(Eval("CategoryID"), Integer)) %>'>
  ...
</asp:Repeater>

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

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

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

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

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

注意

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

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

一次擷取所有產品資訊

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

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

為了提供這項功能,我們只需要稍微修改 GetProductsInCategory(categoryID) ASP.NET 頁面程序代碼後置類別中的方法。 如果尚未存取產品) ,則我們可以改為以盲目方式傳回類別 s GetProductsByCategoryID(categoryID) 方法的結果ProductsBLL,而是先存取所有產品 (,然後只傳回根據傳入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

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

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

注意

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

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

摘要

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

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

快樂的程序設計!

關於作者

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

特別感謝

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