巢狀資料 Web 控制項 (C#)
在本教學課程中,我們將探索如何使用巢狀在另一個 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。
圖 2:將 New ObjectDataSource CategoriesDataSource
命名 (按兩下即可檢視完整大小的影像)
設定 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 新增至 ,CategoryList
ItemTemplate
以顯示屬於適當類別之這些產品的相關信息。 我們有許多方式可以擷取此內部 Repeater 的數據,其中兩種我們很快就會探索。 現在,讓我們在 Repeater s ItemTemplate
內CategoryList
建立產品 Repeater。 具體來說,讓我們讓產品重複程式在每個專案符號清單中顯示每個產品,其中包含產品名稱和價格。
若要建立此重複程式,我們需要手動將內部 Repeater 宣告式語法和範本 CategoryList
輸入 s ItemTemplate
。 在 Repeater s ItemTemplate
中CategoryList
新增下列標記:
<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。 類別 ProductsBLL
有 GetProductsByCategoryID(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)
方法。 請注意,透過資料系結語法存取此處的 CategoryID
是CategoryID
外部 Repeater () CategoryList
,這是系結至資料表中記錄的 Categories
。 因此,我們知道 CategoryID
不能是資料庫 NULL
值,這就是為什麼我們可以不小心轉換 Eval
方法,而不檢查我們是否正在處理 DBNull
。
使用此方法時,我們需要建立 GetProductsInCategory(categoryID)
方法,並讓它擷取提供的適當產品 categoryID
集。 我們只要傳回 類別 s GetProductsByCategoryID(categoryID)
方法所傳回ProductsDataTable
的 ProductsBLL
,即可這樣做。 讓我們在頁面的程式代碼後置類別中建立 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 語句來查詢資料庫,該語句會從Products
CategoryID
數據表中傳回其字段符合所提供輸入參數的數據列。
在系統中指定 N 個類別時,此方法會 nets N + 1 呼叫資料庫一個資料庫查詢,以取得所有類別,然後 呼叫 N 來取得每個類別特有的產品。 不過,我們可以只擷取兩個資料庫中所需的所有數據呼叫一次,以取得所有類別,另一個呼叫即可取得所有產品。 擁有所有產品之後,我們可以篩選這些產品,讓只有符合目前 CategoryID
的產品系結至該類別的內部 Repeater。
為了提供這項功能,我們只需要稍微修改 GetProductsInCategory(categoryID)
ASP.NET 頁程序代碼後置類別中的方法。 如果尚未) 存取所有產品,然後只傳回根據傳入 CategoryID
的產品篩選檢視,我們就可以改為直接ProductsBLL
GetProductsByCategoryID(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 存取資料庫到兩次的次數。
這項增強功能不會對頁面的轉譯標記造成任何變更,也不會讓其他方法傳回較少的記錄。 它只會減少對資料庫的呼叫數目。
注意
其中一個可能直覺的理由是減少數據庫存取數目,可保證能改善效能。 不過,這可能不是這種情況。 例如,如果您有大量的產品 CategoryID
, NULL
例如, 方法的呼叫 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。