在ASP.NET 2.0中操作数据之三十二:数据控件的嵌套

导言

  除了静态HTML和数据绑定语法,template也可以包含Web控件和用户控件.这些控件的属性可以通过声明语法,数据绑定语法或在服务器端通过事件处理编程来设置.

  通过将控件嵌入到template里,可以自定义界面,提升用户体验.例如,在在GridView控件中使用TemplateField 里,我们学习了如何通过在GridView的TemplateField里加一个Calendar控件来表示员工的雇佣日期.在给编辑和新增界面增加验证控件 和定制数据修改界面 里,我们学习了如何通过添加验证控件, TextBox,DropDownList和其它Web控件来自定义编辑,插入界面.

  Template也可以包含其它数据控件.即,我们可以让DataList在Template里包含其它DataList(或者Repeater,GridView,DetailsView等).这个工作的挑战在于将数据绑定到里面的数据控件上.有几种不同的方法可以实现,包括从使用ObjectDataSource的声明语言到直接编程.

  在本章里我们将探索如何使用嵌套的Repeater.外层的Repeater将每个category显示为一个item,包含category的name和description.每个category的item里的Repeater显示此category下的每个product(见图1).我们将分别学习如何通过声明和编程的方法创建内层的Repeater.


图1: Category和属于它的Product一起被列出

第一步: 创建Category列表

  当创建一个使用嵌套数据控件的页时,我发现开始从最外层的控件的设计,创建和测试开始非常的有帮助,这个时候不用管内层嵌套的控件.因此,我们首先实现往页面里添加一个Repeater来列出category的name和description.

  打开DataListRepeaterBasics文件夹里的NestedControls.aspx页.添加一个Repeater控件,将ID设为CategoryList..通过它的智能标签,选择创建一个新的名为CategoriesDataSource的ObjectDataSource.


图 2: 创建一个名为CategoriesDataSource的ObjectDataSource

用CategoriesBLL类的GetCategories方法配置O


图3: 用CategoriesBLL类的GetCategories方法配置ObjectDataSource

  我们需要切换到源视图来手动输入声明代码指定Repeater的template内容.增加一个带<h4>的name和<p>的description的ItemTemplate.用<hr>将category分开.在作完这些后,你的页面代码里的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:列出每个Category的 Name 和Description , 用水平线隔开

第二步: 增加嵌套的Repeater显示Product

  下一步我们的任务是在CategoryList的ItemTemplate里添加一个Repeater用来显示属于各个category下的product.有很多方法可以存取内层的Repeater数据,我们将探讨两种现在我们在CategoryList Repeater的ItemTemplate里创建product Repeater.每个product里将包含name和price我们将下面的标记加到CategoryList的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>

第三步: 将各Category下的Product绑定到 ProductsByCategoryList Repeater

  如果现在你浏览这个页,你会看到象图4一样的页面,因为我们还没有在Repeater里绑定任何数据.有几种方法可以将合适的product记录绑定到Repeater里,其中一些会比较有效.现在主要的任务是为指定category取到合适的product.可以通过在ItemTemplate里语法声明ObjectDataSource或者直接在后台代码编程来将数据绑定到内层的Repeater.

  通过ObjectDataSource和ItemDataBound来获取数据

  这里我们还是用ObjectDataSource来实现.ProductsBLL类的GetProductsByCategoryID(Category)
方法可以返回特定CategoryID的products信息.因此,我们将在CategoryList Repeater的ItemTemplate里新建一个ObjectDataSource,并用这个方法配置它.不幸的,Repeater不允许通过设计视图来修改template,因此我们需要手动添加将声明语法.见下面的代码:

<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的DataSourceID为ObjectDataSource(ProductsByCategoryDataSource).注意ObjectDataSource有一个<asp:Parameter>来指定传给GetProductsByCategoryID(categoryID)的categoryID.但是我们怎么来指定这个值呢?我们可以设置DefaultValue属性为<asp:Parameter>,见下面的代码:

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

  不幸的,数据绑定语法只能用在有DataBinding事件的控件里.Parameter类没有这样的事件,因此这样使用会出错.我们需要为CategoryList Repeater的ItemDataBound创建一个事件处理来设置这个值.每个item绑定到Repeater时激发ItemDataBound事件.因此每次外层的Repeater激发这个时间时,我们可以将当前的CaegoryID的值传给ProductsByCategoryDataSource ObjectDataSource的CategoryID参数.下面的代码是为CategoryList Repeater的ItemDataBound创建一个event handler:

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

  这个event handler首先保证我们操作的是data item而不是header,footer或separator item.然后,引用刚刚绑定到当前RepeaterItem的CategoriesRow实例.最后,引用在ItemTemplate里的ObjectDataSource并将当前RepeaterItem的CategoryID传给CategoryID参数.

  在这个event handler里,每个RepeaterItem里的ProductsByCategoryList Repeater都绑定到RepeaterItem的category里的product.见图5.


图 5: 外层的Repeater 列出每个Category; 内层的Repeater 列出属于Category的Products

  直接编程来获取Category 下的Products

  除了使用ObjectDataSource来获取当前category下的proudct外,我们还可以在ASP.NET页的code-behind里(或App_Code文件夹里或一个单独的类项目里)来创建一个根据传入的CategoryID返回合适的product集的方法.假设在ASP.NET页的code-behind里有一个名为GetProductsInCategory(categoryID)方法.我们可以使用这个方法来将当前category下的product绑定到内层的Repeater.见下面的代码:

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

  Repeater的DataSource属性通过绑定语法来指定它的数据是通过GetProductsInCategory(categoryID)得到.由于Eval("CategryID")返回的是Object类型,我们在它传入GetProductsInCategory(categoryID)前将它转化成Integer.注意这里的CategoryID是通过外层Repeater(CategoryList)的CategoryID(已经绑定到Categories table)获取的.因此它不可能是一个NULL值.所以我们在绑定前没有检查.

  我们现在需要创建GetProductsInCategory(categoryID)方法.在这里简单使用ProductsBLL类的GetProductsByCategoryID(categoryID)方法返回的ProductsDataTable就可以了.我们在NestedControls.aspx页的code-behind里创建GetProductsInCategory(categoryID).见下面的代码:

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 event handler方法差不多(图5).

  注意:在ASP.NET页的code-behind里创建GetProductsInCategory(categoryID)方法好象只是一个形式,毕竟这个方法只是调用BLL里的方法.为什么不直接在内层Repeater里的绑定语法里直接调用这个方法.比如:
DataSource='<%#ProductsBLL.GetProductsByCategoryID(CType(Eval("CategoryID"),Integer))%>')
虽然这个声明是不起作用的(因为GetProductsByCategoryID(categoryID)方法是一个实例方法),你可以修改ProductsBLL来包含一个这样的静态方法.这样的修改可以满足ASP.NET页的GetProductsInCategory(categoryID)方法的需要,但是写在code-behind里可以更灵活的获取数据,我们在后面会看到这点.

获取所有的Product 信息

  前面两个方法我们通过调用ProductsBLL类的GetProductsByCategoryID(categoryID)方法来获取当前category的product(第一种通过ObjectDataSource,第二种通过GetProductsInCategory(categoryID)).每次方法被调用时,BLL调用DAL,DAL通过SQL查询数据库,返回特定的记录.

  如果有N个category,这个方法会访问数据库N+1次— 一次返回所有的category,N次返回特定category下的product.然而我们可以通过访问数据库两次来获取所有需要的数据— 一次返回所有的category,一次返回所有的product.一旦我们得到所有的product,我们可以根据CategoryID来过滤,然后再绑定.

  我们只需要稍微修改ASP.NET页的code-behind里的GetProductsInCategory(categoryID)方法来实现这个功能.我们首先来返回所有的product,然后根据传入的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)时返回所有product信息.确定allProducts对象被创建后,在根据CategoryID来对DataTable过滤.这个方法将访问数据库的次数从N+1减少到2次.
这个改进没有修改页面的声明语言.仅仅只是减少了数据库的访问次数.

  注意:可能想当然的觉得减少了数据库访问次数会提高性能.但是这个不一定.如果你有大量的categoryID为NULL的product,这样使用GetProducts方法返回的product有一部分不会被显示.而且如果你只需要显示一部分category的proudct(分页时就是这样),而返回所有的product,这样对资源也是一种浪费.通常对两种技术进行性能分析,唯一正确的方法是设置程序常见的场景来进行压力测试.

总结

  本章我们学习了如何嵌套Web控件.通过如何在外层Repeater显示各个category,内层Repeater显示每个category下的product来作为例子.主要的任务在于获取正确的数据并绑定到内层的Web控件上.有很多方法可以使用,我们这里讨论了两种.第一种是使用在外层控件的ItemTemplate里ObjectDataSource来绑定到内层控件.第二种是使用ASP.NET页的code-behind里的方法.它通过内层控件的DataSource属性来绑定.本章使用的控件是Repeater,也可以将Repeater嵌套在GridView里,或GridView嵌套在DataList里等.

  祝编程快乐!

作者简介

  Scott Mitchell,著有六本ASP/ASP.NET方面的书,是4GuysFromRolla.com的创始人,自1998年以来一直应用 微软Web技术。Scott是个独立的技术咨询顾问,培训师,作家,最近完成了将由Sams出版社出版的新作,24小时内精通ASP.NET 2.0。他的联系电邮为mitchell@4guysfromrolla.com,也可以通过他的博客http://ScottOnWriting.NET与他联系。

(0)

相关推荐

  • ASP.NET 跨页面传值方法

    1. 使用QueryString变量 QueryString是一种非常简单的传值方式,他可以将传送的值显示在浏览器的地址栏中.如果是传递一个或多个安全性要求不高或是结构简单的数值时,可以使用这个方法.但是对于传递数组或对象的话,就不能用这个方法了.下面是一个例子: a.aspx的C#代码 复制代码 代码如下: private void Button1_Click(object sender, System.EventArgs e) { string s_url; s_url = "b.aspx?

  • 在ASP.NET 2.0中操作数据之三十四:基于DataList和Repeater跨页面的主/从报表

    导言 在前面一章里我们学习了如何在一个页里显示主/从信息.另外一种经常使用的模式就是将主从信息用两个页分别显示.在前面的跨页面的主/从报表 我们通过GridView显示所有的supplier来使用这个模式.GridView里包含一个HyperLinkField,链接到另外一个页,并将SupplierID通过querystring传过去.第二个页使用GridView列出了选中的supplier提供的product. 这样的两页主/从表也可以用DataList和Repeater来实现.唯一的区别是D

  • 在ASP.NET 2.0中操作数据之二十七:创建自定义排序用户界面

    简介 显示大量已经按类别(不是很多)排序的数据但没有类别分界线,用户很难找到所需要的类别.例如,数据库中只有9个类别(8个不同的类别和1个null),共81种产品.现在用一个GridView列出所有产品,假设有用户对类别Seafood的产品感兴趣,她一定会按类别排序,把Seafood产品排列在一起.排序后,用户便寻找Seafood产品开始和结束的地方.虽然是按英文字母排列类别不难找到Seafood,但仍要花些时间在GridView寻找.为了进一步的区分类别,许多网站使用类别分界线这种排序用户界面

  • 在ASP.NET 2.0中操作数据之三十一:使用DataList来一行显示多条记录

    导言 在前两章的做的DataList的例子里我们都是使用单列的HTML<table>来显示数据.而自定义使DataList将数据显示在多列多行的table里也非常容易.而且还可以以单行多列来显示数据. 我们可以通过RepeatColumns和RepeatDirection属性来自定义DataList.这两个属性决定了数据显示时候的列数和方向(水平或垂直).图1是以一个3列的table来显示product信息的DataList例子. 图 1: DataList 一行显示三条product信息 通

  • 在ASP.NET 2.0中操作数据之三十三:基于DataList和Repeater使用DropDownList过滤的主/从报表

    导言 在前面的使用DropDownList过滤的主/从报表一章里我们使用GridView创建的主/从表,显示一些"主"记录.用户可以根据主记录来查看"从"(详细)的内容.主/从表在呈现一对多关系和含多列的表的信息时是一个好的选择.在前面我们已经学过如何使用GridView和DetailsView来实现.本章和后面两章我们将重新复习一下这些概念,但是主要学习使用DataList和Repeater来实现.本章我们将学习使用DropDownList包含主记录,而在Data

  • ASP.NET 2.0中的数据操作之九:跨页面的主/从报表

    导言 在前面的两篇教程中,我们看到了如何在单一页面中显示主/从报表, 它使用DropDownList显示主记录,使用GridView或DetailsView显示详细信息. 另外一种常见的主/从报表模式是在一个页面中显示主记录而在另一个页面中显示详细信息.互联网上的论坛,如www.asp.net ,就是该模式在实际应用中非常典型例子. Asp.Net论坛由多个子论坛组成: Getting Started, Web Forms, Data Presentation Controls 等等. 每个子论

  • 在ASP.NET 2.0中操作数据之二十八:GridView里的Button

    导言 一般控件(比如GridView)显示数据的时候对数据只能读取,而需要处理数据的功能是非常常见的.典型的情况是为每行数据添加一个Button, LinkButton, 或ImageButton . 当点击这些button时,数据会PostBack,执行一些服务器端的代码. 一条条的编辑或删除数据是最常见的情况.实际上,编辑和删除是如此常见,从概述插入.更新和删除数据 开始, 我们可以看到GridView, DetailsView, 和 FormView可以零代码的完成这些功能. 除了编辑和删

  • 在ASP.NET 2.0中操作数据之三十:格式化DataList和Repeater的数据

    导言 在前面的教程里我们学习了DataList提供了一些风格样式的属性.而且我们还学习了如何定义HeadStyle, ItemStyle, AlternatingItemStyle, 和SelectedItemStyle等属性的默认CSS.除了这四个属性外,DataList还提供了其它属性,比如Font, ForeColor, BackColor, 和BorderWidth.而Repeater没有提供任何这样的属性.如果你需要用Reperter来实现这些效果,你就需要在templates里直接写

  • 在ASP.NET 2.0中操作数据之二十六:排序自定义分页数据

    导言 和默认翻页方式相比,自定义分页能提高几个数量级的效率.当我们的需要对大量数据分页的时候就需要考虑自定义分页,然而实现自定义分页相比默认分页需要做更多工作.对于排序自定义分页数据也是这样,在本教程中我们就会扩展前面的例子来实现自定义分页数据的排序. 注意:既然本教程是基于前一个的,因此我们需要把前面教程示例页面EfficientPaging.aspx的<asp:Content>元素中的代码复制到本教程SortParameter.aspx示例页面中.关于如何进行这样的复制操作请参看为删除数据

  • 在ASP.NET 2.0中操作数据之二十九:用DataList和Repeater来显示数据

    导言 在之前的28篇教程的例子里,如果我们需要显示某个数据源的多条记录,我们使用GridView .GridView 的一行表示数据源的一条记录,列表示一个字段.虽然GridView 用来显示数据,分页,排序,编辑,删除非常的方便,但是有点臃肿.而且GridView 结构的标记是固定的-它包含一个带有<tr>和<td>的HTML <table>标记. 为了在显示多条记录时,有更好的自定义功能,ASP.NET 2.0提供了DataList 和Repeater (ASP.N

随机推荐