作者 :斯科特·米切尔
在本教程中,我们将探讨如何使用嵌套在另一个 Repeater 内的 Repeater。 这些示例将演示如何以声明方式和编程方式填充内部 Repeater。
介绍
除了静态 HTML 和数据绑定语法外,模板还可以包括 Web 控件和用户控件。 这些 Web 控件可以通过声明性、数据绑定语法分配其属性,也可以在相应的服务器端事件处理程序中以编程方式访问。
通过在模板中嵌入控件,可以自定义和改进外观和用户体验。 例如,在 GridView 控件教程的 Using TemplateFields 中,我们了解了如何在 TemplateField 中添加日历控件以显示员工雇佣日期来自定义 GridView 的显示;在“向编辑和插入接口添加验证控件”和“自定义数据修改接口”教程中,我们了解了如何通过添加验证控件、TextBoxes、DropDownLists 和其他 Web 控件来自定义编辑和插入接口。
模板还可以包含其他数据 Web 控件。 也就是说,我们可以拥有一个包含另一个 DataList(或者 Repeater、GridView、DetailsView 等)的 DataList,并将其包含在模板中。 此类接口的挑战是将适当的数据绑定到内部数据 Web 控件。 有几种不同的方法可用,从使用 ObjectDataSource 的声明性选项到编程方法不等。
本教程中,我们将探讨如何使用嵌套在另一个 Repeater 内的 Repeater。 外部 Repeater 将包含数据库中每个类别的项,其中显示了类别的名称和说明。 每个类别项的内部 Repeater 将以项目符号列表的形式显示属于该类别的每个产品的信息(见图 1)。 我们的示例将演示如何以声明方式和编程方式填充内部 Repeater。
图 1:列出了每个类别及其产品(单击以查看全尺寸图像)
步骤 1:创建类别列表
生成使用嵌套数据 Web 控件的页面时,我发现先设计、创建和测试最外部的数据 Web 控件会很有帮助,甚至不必担心内部嵌套控件。 因此,让我们首先逐步讲解将 Repeater 添加到列出每个类别名称和说明的页面所需的步骤。
首先打开 NestedControls.aspx
文件夹中的页面,并向页面 DataListRepeaterBasics
添加一个 Repeater 控件,并将其 ID
属性设置为 CategoryList
。 从 Repeater 的智能标记中,选择创建名为 CategoriesDataSource
的新 ObjectDataSource。
图 2:将 New ObjectDataSource CategoriesDataSource
命名(单击可查看全尺寸图像)
配置 ObjectDataSource,以便从 CategoriesBLL
类方法 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
s ItemTemplate
中,以显示属于相应类别的产品信息。 可通过多种方式检索此内部 Repeater 的数据,其中两种将很快进行探索。 现在,让我们在 Repeater s ItemTemplate
中创建CategoryList
产品 Repeater。 具体而言,让产品Repeater在项目符号列表中显示每个产品,并在每个列表项中包含产品名称和价格。
若要创建此 Repeater,我们需要将内部 Repeater 的声明性语法和模板手动输入到 CategoryList
的 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 的 ItemTemplate
中声明式的 CategoryList
ObjectDataSource,或通过 ASP.NET 页面的后台代码以编程方式访问。 同样,此数据可以通过声明方式绑定到内部 Repeater:要么通过内部 Repeater 的 DataSourceID
属性和声明性数据绑定语法,要么通过编程方式在 Repeater 的 CategoryList
事件处理程序中引用内部 Repeater,然后以编程方式设置其 DataSource
属性,并调用其 DataBind()
方法。 让我们探讨其中每个方法。
使用 ObjectDataSource 控件和ItemDataBound
事件处理程序以声明方式访问数据
鉴于我们在本教程系列中广泛使用了 ObjectDataSource,因此对该示例数据的最自然访问方法是继续使用 ObjectDataSource。 该 ProductsBLL
类具有一个 GetProductsByCategoryID(categoryID)
方法,该方法返回有关属于指定 categoryID
产品的相关信息。 因此,我们可以将 ObjectDataSource 添加到 CategoryList
Repeater 中,并将其配置为通过此类的方法访问其数据。
遗憾的是,Repeater 不允许通过设计视图编辑其模板,因此我们需要手动添加此 ObjectDataSource 控件的声明性语法。 以下语法展示了在添加这个新的 ObjectDataSource 之后的 CategoryList
Repeater ItemTemplate
(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>
使用 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 事件 ItemDataBound
创建事件处理程序。 回想一下,对于绑定到 Repeater 的每个项,该 ItemDataBound
事件都会触发一次。 因此,每次针对外部 Repeater 触发此事件时,我们可以将当前 CategoryID
值 ProductsByCategoryDataSource
分配给 ObjectDataSource s CategoryID
参数。
使用以下代码为 CategoryList
Repeater s 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
参数值赋给当前 RepeaterItem
的 CategoryID
。
使用此事件处理程序,ProductsByCategoryList
中每个 RepeaterItem
的 Repeater 将绑定到 RepeaterItem
类别中的这些产品。 图 5 显示了生成的输出的屏幕截图。
图 5:外部重复程序列出每个类别;内部一个列出该类别的产品 (单击以查看全尺寸图像)
以编程方式按类别数据访问产品
我们可以在 ASP.NET 页的代码隐藏类(或在 App_Code
文件夹或单独的类库项目中)中创建方法,以便在传入 CategoryID
时返回相应的产品集,而不是使用 ObjectDataSource 检索当前类别的产品。 假设我们在 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
值,这就是为什么我们可以直接强制转换 Eval
方法,而不检查是否正在处理 DBNull
。
使用此方法时,我们需要创建 GetProductsInCategory(categoryID)
该方法,并让该方法检索给定的 categoryID
相应产品集。 我们只需通过返回ProductsBLL
类的GetProductsByCategoryID(categoryID)
方法返回的ProductsDataTable
即可实现这一点。 让我们在页面的代码隐藏类中创建 GetProductsInCategory(categoryID)
方法用于 NestedControls.aspx
。 请使用以下代码执行操作:
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)
方法,但代码隐藏类的方法提供了更多灵活性来处理检索到的数据,后面我们将看到。
一次性检索所有产品信息
我们通过调用 ProductsBLL
类的 GetProductsByCategoryID(categoryID)
方法来获取当前类别的产品(第一种方法是通过 ObjectDataSource 实现的,而第二种方法则是通过代码隐藏类中的 GetProductsInCategory(categoryID)
方法实现的)。 每次调用此方法时,业务逻辑层都会调用数据访问层,该层使用 SQL 语句查询数据库,该语句返回表中 Products
的行,该表的 CategoryID
字段与提供的输入参数匹配。
给定系统中的 N 个类别,此方法对数据库的调用总数为 N + 1,其中一个查询用于获取所有类别,然后进行 N 次调用以获取各类别特定的产品。 但是,我们可以检索两个数据库调用中的所有所需数据,一个调用以获取所有类别,另一个用于获取所有产品。 一旦我们拥有了所有产品,我们可以对其进行筛选,只将与当前 CategoryID
匹配的产品绑定到该类别中内部的 Repeater。
为了提供此功能,我们只需对 ASP.NET 页代码后置类中的方法GetProductsInCategory(categoryID)
进行轻微修改。 我们不必盲目返回ProductsBLL
类的GetProductsByCategoryID(categoryID)
方法的结果,而是首先访问所有产品(如果尚未访问它们),然后仅返回基于传入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 访问的次数减少到 2 次。
此增强功能不会对页面的呈现标记引入任何更改,也不会返回比其他方法更少的记录。 它只会减少对数据库的调用数。
注释
一个可能直观地认为,减少数据库访问的数量可以保证提高性能。 但是,情况可能并非如此。 例如,如果你有大量产品,其CategoryID
为NULL
,那么调用GetProducts
方法将返回一些从未展示的产品。 此外,如果只显示类别的子集,则返回所有产品可能会浪费,如果已实现分页,则可能是这种情况。
与往常一样,在分析两种技术的性能时,唯一的肯定措施是运行针对应用程序常见情况定制的受控测试。
概要
在本教程中,我们学习了如何将一个数据 Web 控件嵌套在另一个数据 Web 控件中,特别探讨了如何让外部 Repeater 显示每个类别的项目,内部 Repeater 用列表形式列出该类别的产品。 构建嵌套用户界面的主要挑战在于访问正确的数据并将其绑定到内部数据 Web 控件。 提供了多种可用的技术,其中两种技术在本教程中进行了介绍。 测试的第一种方法是在外部数据 Web 控件中使用了一个 ObjectDataSource,该控件通过其 DataSourceID
属性绑定到内部数据 Web 控件。 第二种方法通过 ASP.NET 页代码隐藏类中的方法访问数据。 然后,此方法可以通过数据绑定语法绑定到内部数据 Web 控件的属性 DataSource
。
虽然本教程中检查的嵌套用户界面使用了嵌套在 Repeater 中的 Repeater,但这些技术还可以应用于其他数据 Web 控件。 您可以在 GridView 中嵌套 Repeater,或在 DataList 中嵌套 GridView,等等。
快乐编程!
关于作者
斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 山姆氏24小时自学 ASP.NET 2.0。 可以通过 mitchell@4GuysFromRolla.com 联系他。
特别感谢
本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是 Zack Jones 和 Liz Shulok。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请给我发信息。mitchell@4GuysFromRolla.com