当前位置: 移动技术网 > IT编程>开发语言>.net > 在ASP.NET 2.0中操作数据之三十五:使用Repeater和DataList单页面实现主/从报表

在ASP.NET 2.0中操作数据之三十五:使用Repeater和DataList单页面实现主/从报表

2017年12月12日  | 移动技术网IT编程  | 我要评论

61.153.224.189,竹林仙翁,手机彩铃排行榜

导言

  在前面一章里我们学习了如何用两个页分别显示主/从信息。在“主”页里我们用repeater来显示category。每个category的name都是一个链到“从”页的hyperlink。在从页里用一个两列的datalist显示选中的category下的product。本章我们将还是使用单页,在左边显示category列表,category的名字用linkbutton显示。点击其中一个时页面postback,在右边以两列的datalist显示出相关的product。除了名字外,左边的repeater还会显示与该category相关联的product总数。(见图1)

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909288097.jpg
图 1: category的 name 和 product总数显示在左边

第一步: 在页面左部显示一个repeater

  本章我们将在左边显示category,右表显示它关联的product。web页的内容可以使用标准html元素或者css来定位。到目前为止我们都是使用css来定位。在母板页和站点导航 一章里我们使用绝对定位来创建导航时,为导航列表和内容之间指定了明确的距离。当然css也可以用来对两个元素的位置进行调整。

  打开datalistrepeaterfiltering文件夹下的categoriesandproducts.aspx页,添加一个repeater和datalist.id分别设置为categories和categoryproducts。然后到源视图里将它们分别放到<div>元素里。也就是说在repeater后面加一个闭合的</div>,在datalist前加一个开始的<div>。现在你的代码看起来应该和下面差不多:

<div>
 <asp:repeater id="categories" runat="server">
 </asp:repeater>
</div>
<div>
 <asp:datalist id="categoryproducts" runat="server">
 </asp:datalist>
</div>

我们需要使用float属性来将repeater放到datalist左边,见下面代码:

<div>
 repeater
</div>
<div>
 datalist
</div>

  float:left 将第一个<div>放到第二个的左边。width和padding-right指定了第一个<div>的宽和<div>内容和右边框的距离。更多的floating元素信息请参考floatutorial.我们在styles.css里创建一个新的css类,名为floatleft(而不是直接在<p>的样式里设置):

.floatleft
{
 float: left;
 width: 33%;
 padding-right: 10px;
}

  然后我们用<div class="floatleft">将<div style="float:left">替换掉。
完成以上所讲的内容后,切换到设计视图。你应该看到repeater已经在datalist左边了(由于还没有配置数据源或模板,这两个控件都是灰的)。

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909283612.jpg
图 2: 调整完位置后的页面

第二步: 获取每个category关联的products总数

  完成了样式设置后,我们现在来将category数据绑定到repeater。如图1所示,除了category名字外,我们需要显示和它关联的product总数,为了获取这个信息我们可以:

  在asp.net page的code-behind 里获取这个信息. 根据给定的categoryid我们可以通过productsbll类的getproductsbycategoryid(categoryid)方法来获取关联的product总数。这个方法返回一个productsdatatable对象,它的count属性表示了我们需要知道的信息。我们可以为repeater创建一个itemdatabound event handler,在每个category绑定到repeater时调用这个方法然后将总数输出。

  在dataset里更新categoriesdatatable 添加一个numberofproducts列. 我们可以更新categoriesdatatable的getcategories()方法来包含这个信息或者保留这个方法,再创建一个新的名为getcategoriesandnumberofproducts()方法。

  我们来看看这两种方法。第一种写起来更简单,因为我们不需要更新dal。但是它需要和数据库更多的连接。在itemdatabound event handler里调用getproductsbycategoryid(categoryid)方法又增加了一次数据库连接(这在每个category绑定时会发生一次)。这时一共会有n+1次对数据库的请求(n为repeater里显示的category的总数)。而第二种方法product总数从getcategories()(或getcategoriesandnumberofproducts())方法返回,这样只请求一次数据库就可以了。

在itemdatabound event handler里获取products总数

  在itemdatabound event handler里获取product总数不需要修改dal。只需要直接修改categoriesandproducts.aspx页。通过repeater的智能标签添加一个新的名为categoriesdatasource的objectdatasource。使用categoriesbll类的getcategories()方法配置它。

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909284898.jpg
图 3: 配置 objectdatasource

  repeater里的每个category都是可点的,而且在点了之后,categoryproducts datalist会显示那些相关的product。我们可以将每个category设为hyperlink,链到本页(categoriesandproducts.aspx),通过querystring为categoryid赋值。这种方法的好处是,特定category的product可以通为搜索建立索引和书签。

  我们也可以将每个category设为linkbutton,在本章我们使用这个方法。linkbutton看起来象一个hyperlink,但是点击后会产生一个postback。datalist的objectdatasource会刷新以显示选中category相关联的product。在本章使用hyperlink更合理。然而在别的情况下可以使用linkbutton会好一点。虽然是这样,我们在这里也使用linkbutton。我们将会看到,使用linkbutton会有一些使用hyperlink时碰不到的挑战。因此我们可以学习更好学习它,以便以后使用。

  注意:如果你使用hyperlink或<a>来代替linkbutton来重复练习一次本章的内容,是最好不过了。

下面的标记语言是repeater和objectdatasource的,注意repeater的template将每个item表示为linkbutton。

<asp:repeater id="categories" runat="server" datasourceid="categoriesdatasource">
 <headertemplate>
  <ul>
 </headertemplate>
 <itemtemplate>
  <li><asp:linkbutton runat="server" id="viewcategory" /></li>
 </itemtemplate>
 <footertemplate>
  </ul>
 </footertemplate>
</asp:repeater>
<asp:objectdatasource id="categoriesdatasource" runat="server"
 oldvaluesparameterformatstring="original_{0}"
 selectmethod="getcategories" typename="categoriesbll">
</asp:objectdatasource>
           

  注意:在本章repeater的view state必须开启(repeater的声明语法里的enableviewstate="false")。在第三步我们将为itemcommand事件创建一个event handler,在它里面我们要更新datalist的objectdatasource的seleceparameters集合。如果view state 被禁用的话repeater的itemcommand不会被激发。想了解具体的原因和更多的信息请参考 a stumper of an asp.net question 和its solution 。

  id为viewcategory的linkbutton还没有设置text属性。如果我们只需要显示category名字,我们可以通过绑定语法象下面这样来直接设置:

<asp:linkbutton runat="server" id="viewcategory"
 text='<%# eval("categoryname") %>' />

然而在这里我们需要显示的是category的name和proudct的总数。见下面的代码:

protected void categories_itemdatabound(object sender, repeateritemeventargs e)
{
 // make sure we're working with a data item...
 if (e.item.itemtype == listitemtype.item ||
  e.item.itemtype == listitemtype.alternatingitem)
 {
  // reference the categoriesrow instance bound to this repeateritem
  northwind.categoriesrow category =
   (northwind.categoriesrow) ((system.data.datarowview) e.item.dataitem).row;
  // determine how many products are in this category
  northwindtableadapters.productstableadapter productsapi =
   new northwindtableadapters.productstableadapter();
  int productcount =
   productsapi.getproductsbycategoryid(category.categoryid).count;
  // reference the viewcategory linkbutton and set its text property
  linkbutton viewcategory = (linkbutton)e.item.findcontrol("viewcategory");
  viewcategory.text =
   string.format("{0} ({1:n0})", category.categoryname, productcount);
 }
}
         

  我们首先要确保我们处理的是data item(itemtype为item或alternatingitem)然后引用刚刚绑定到当前repeateritem的categoriesrow。然后调用getcategoriesbyproductid(categoryid)方法,通过count属性获取返回的记录条数。最后将itemtemplate里的viewcategory linkbutton的text属性设为"categoryname(numberofproductsincategory)"。

  注意:我们也可以在asp.net页的code-behind里写一个格式化功能,接收categoryname和categoryid的值,返回categoryname和product总数的连接字符串。然后将结果直接赋给linkbutton的text属性,而不需要处理itemdatabound事件。更多的格式化功能信息参考在gridview控件中使用templatefield 和格式化datalist和repeater的数据。添加完event handler后,在浏览器里看看页面。见图4。

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909289412.jpg
图 4: 显示每个 category的 name 和 products总数

  更新categoriesdatatable和categoriestableadpter来包含每个category的product总数除了在每个category绑定到repeater时获取product总数外,我们还可以修改dal里categoriesdatatable和categoriestableadapter来包含这个信息.我们在categoriesdatatable里加一列.打开app_code/dal/northwind.xsd,右键点datatable,选择add/column.见图5.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909287469.jpg
图 5: 为categoriesadatasource增加一个新列

  这样会添加一个名为column1的列,你可以很方便的修改它的名字.将它重命名为numberofproducts.然后我们需要配置这列的属性.点这个列,来到属性窗口.将datatype从system.string修改为system.int32.将readonly属性设为true.见图6.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909297969.jpg
图 6: 设置新列的属性

  现在categoriesdatatable里已经包含了numberofproducts列,但它的值还没有设置.我们可以修改getcategories()方法,当每次获取category信息的时候返回它的信息.在这里由于只是本章用到了这个数据,我们来创建一个新的名为getcategoriesandnumberofproducts().右键点categoriestableadapter,选择new query.会出现tableadapter query配置向导.选择sql statement.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909293485.jpg
图 7: 选择sql statement

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909297999.jpg
图 8: sql statement 返回行数

下一步需要我们写sql语句.下面的语句返回每个category的categoryid,categoryname,description和相关product的总数:

select categoryid, categoryname, description,
  (select count(*) from products p where p.categoryid = c.categoryid)
   as numberofproducts
from categories c
     

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909299284.jpg
图 9: 使用的sql语句

  注意计算product总数的子查询的别名为numberofproducts.它和categoriesdatatable的numberofproducts列关联.最后一步是写方法的名字.分别为fill a datatable和return a datatable命名为fillwithnumberofproducts和getcategoriesandnumberofproducts.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909299098.jpg
图 10: 为新的tableadapter的方法命名

  现在dal已经修改完了.由于我们所有展现层,bll,dal是逐层调用,所以我们需要在categoriesbll类的添加相应的getcategoriesandnumberofproducts方法.

[system.componentmodel.dataobjectmethodattribute
 (system.componentmodel.dataobjectmethodtype.select, false)]
public northwind.categoriesdatatable getcategoriesandnumberofproducts()
{
 return adapter.getcategoriesandnumberofproducts();
}
       

  完成dal和bll后,我们来将数据绑定到categories repeater.如果在"在itemdatabound event handler里获取products总数"那部分里你已经为repeater创建了objectdatasource,删掉它,然后去掉repeater的datasourceid属性,同样去掉itemdatabound事件.repeater现在回到了初始状态,添加一个名为categoriesdatasource的objectdatasource.使用categoriesbll类的getcategoriesandnumberofproducts()方法来配置它.见图11.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909292856.jpg
图 11: 配置objectdatasource

  然后修改itemtemplate,使用数据绑定语法来将categoryname和numberofproducts字段绑定到linkbutton的text属性.完整的标记语言如下:

<asp:repeater id="categories" runat="server" datasourceid="categoriesdatasource">
 <headertemplate>
  <ul>
 </headertemplate>
 <itemtemplate>
  <li><asp:linkbutton runat="server" id="viewcategory"
    text='<%# string.format("{0} ({1:n0})", _
     eval("categoryname"), eval("numberofproducts")) %>' />
  </li>
 </itemtemplate>
 <footertemplate>
  </ul>
 </footertemplate>
</asp:repeater>
<asp:objectdatasource id="categoriesdatasource" runat="server"
 oldvaluesparameterformatstring="original_{0}"
 selectmethod="getcategoriesandnumberofproducts" typename="categoriesbll">
</asp:objectdatasource>
 

使用这种方法的页面看起来和前面一种方法一样(见图4).

第三步: 显示选中的category关联的products

  现在category和product总数的部分已经完成.repeater将每个category显示为linkbutton,当点击时产生postback,这时我们需要将那些关联的product在categoryproducts datalist里显示出来.

  现在我们面临的一个挑战是如何将特定category下的product在datalist里显示出拉一.在使用gridview 和detailview实现的主/从报表一章里我们学习了创建一个girdview,当选择它的一行时将"从"信息在本页的detailsview里显示出来.gridview的objectdatasource用productsbll的getproducts()返回product信息.而detailsview的objectdatasource用getproductsbyproductid(productid)返回选中的product信息.productid参数通过girdview的selectedvalue属性来提供.不幸的是,repeater没有selectedvalue属性.

  注意:这是我们在repeater里使用linkbutton的其中一个挑战.如果我们使用hperlink,可以通过querystring来传递categoryid.在我们解决这个问题前,首先将objectdatasource绑定到datalist,然后指定itemtemplate.从datalist的智能标签添加一个名为categoryproductsdatasource的objectdatasource,并使用productsbll类的getproductsbycategoryid(cateogryid)配置它.由于此datalist只提供只读功能,因此在insert,update,delete标签里选择none.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909295898.jpg
图 12: 配置 objectdatasource

  由于getproductsbycategoryid(categoryid)方法需要一个输入参数,向导会要求我们指定参数源.我们使用gridview或datalist列出categories时,可以将参数源设为control,controlid设为数据控件的id.然而由于repeater没有selectedvalue属性,所以不能用作参数源.你可以查看controlid下拉列表,它里面只包含一个控件id—categoryproducts(datalist).

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909308656.jpg
图 13: 配置参数

  配置完数据源后,visual studio为datalist自动产生itemtemplate.用我们前面使用的template替换默认的itemtemplate.将datalist的repeatcolumns属性设为2.完成这些后,你的代码应该和下面的差不多:

<asp:datalist id="categoryproducts" runat="server" datakeyfield="productid"
 datasourceid="categoryproductsdatasource" repeatcolumns="2"
 enableviewstate="false">
 <itemtemplate>
  <h5><%# eval("productname") %></h5>
  <p>
   supplied by <%# eval("suppliername") %><br />
   <%# eval("unitprice", "{0:c}") %>
  </p>
 </itemtemplate>
</asp:datalist>
<asp:objectdatasource id="categoryproductsdatasource"
 oldvaluesparameterformatstring="original_{0}" runat="server"
 selectmethod="getproductsbycategoryid" typename="productsbll">
 <selectparameters>
  <asp:parameter name="categoryid" type="int32" />
 </selectparameters>
</asp:objectdatasource> 
     

  目前为止categoryproductsdatasource objectdatasource的categoryid参数还没有设置.所以浏览页面时没有任何的product显示出来.我们现在需要将它设置为repeater中的被点击的category的categoryid.这里有两个问题,第一是我们如何判断什么时候repeater的itemtemplate被点了.二是哪个被点了.

  和button,imagebutton一样,linkbutton有一个click event和一个command event.click事件仅仅用来说明linkbutton被点击了.有时候我们需要传递更多的信息到event handler里.这样的话,就需要使用linkbutton的commandname 和commandargument .当linkbutton被点时,command事件激发,event handler会接受commandname和commandargument的值.

  当repeater里的template里激发了一个command事件时,rpeater的itemcommand事件被激发.并将被点击的linkbutton(或者button和imagebutton)的commandname和commandargument的值传进来.因此,判断category linkbutton什么时候被点击了,我们需要:

  设置rpeater里的itemtemplate的linkbutton的commandname属性(我使用的"listproducts").设置了值后linkbutton被点后command事件会激发.

  设置linkbutton的commandargument属性为当前item的categoryid.
为repeater的itemcommand事件创建一个event handler.在它里面将传入的commandargument值赋给categoryproductsdatasource objectdatasource的categoryid参数.

下面是完成了1,2步后的标记.注意categoryid是如何通过绑定语法来赋给commandargument的.

<itemtemplate>
 <li>
  <asp:linkbutton commandname="listproducts" runat="server"
   commandargument='<%# eval("categoryid") %>' id="viewcategory"
   text='<%# string.format("{0} ({1:n0})", _
    eval("categoryname"), eval("numberofproducts")) %>'>
  </asp:linkbutton>
 </li>
</itemtemplate>

  由于任何一个button,linkbutton或imagebutton的command事件都会激发itemcommand事件,所以无论在任何时候创建itemcommand event handler首先都要小心谨慎的检查commandname的值.而由于我们现在只有一个linkbutton,以后我们可能会向repeater添加新的button控件,当点被点击时,激发同样的itemcommand event handler.因此最好确保检查了commandname,然后根据它的值来进行逻辑处理.

  在确保了传入的commandname的值等于"listproducts"后,event handler将categoryproductsdatasource objectdatasource的categoryid的参数设为传入的commandargument.对objectdatasource的selectparameters的修改自动引起datalist重新绑定到数据源,显示新的选中的category关联的product.

protected void categories_itemcommand(object source, repeatercommandeventargs e)
{
 // if it's the "listproducts" command that has been issued...
 if (string.compare(e.commandname, "listproducts", true) == 0)
 {
  // set the categoryproductsdatasource objectdatasource's categoryid parameter
  // to the categoryid of the category that was just clicked (e.commandargument)...
  categoryproductsdatasource.selectparameters["categoryid"].defaultvalue =
   e.commandargument.tostring();
 }
}

  做完这些后,本章就结束了!现在在浏览器里看看你的页面.图14是第一次浏览时的样子.因为还没有category被选中,所以没有product显示出来.点击一个category,比如produce,和它关联的product以两列的方式显示出来.见图15.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909289412.jpg
图 14:第一次浏览页面时没有product显示

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909288097.jpg
图 15: 点击produce category 后,相关的 products 在右边显示出来

总结

  我们在本章和前面一章里学习了主/从表可以分别显示在两个页或者一起显示在一个页.如果显示在一个页上,我们需要考虑如何来控制它们的外观.在使用gridview 和detailview实现的主/从报表一章我们将从记录显示在主记录之上,而在本章我们使用css将主记录显示在从记录的左边.我们还探讨了如何获取每个category关联的product数量,以及在点击repeater里的linkbutton(或buttonimagebutton)时服务器端的处理逻辑.

  到这里为止使用datalist和repeater来显示主/从表已经完成了.后面我们将演示如何在datalist里添加编辑和删除的功能.

  祝编程愉快!

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网