Asp.net如何使用DataList和Repeater过滤数据(3) - 实现主/从报表

本章我们将还是使用单页,在左边显示category列表,category的名字用LinkButton显示。点击其中一个时页面postback,在 右边以两列的DataList显示出相关的product。除了名字外,左边的Repeater还会显示与该category相关联的product总 数。(见图1)


图 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>。现在你的代码看起 来应该和下面差不多:[code]<div>
            <asp:Repeater ID="Categories" runat="server">
            </asp:Repeater>
            </div>
            <div>
            <asp:DataList ID="CategoryProducts" runat="server">
            </asp:DataList>
            </div>[/code]我们需要使用float属性来将Repeater放到DataList左边,见下面代码: [code]<div style="float: left; width: 33%; padding-right: 10px;">
            Repeater
            </div>
            <div>
            DataList
            </div>[/code]float:left 将第一个<div>放到第二个的左边。width和padding-right指定了第一个<div>的宽 和<div>内容和右边框的距离。更多的floating元素信息请参考Floatutorial.
我们在Styles.css里创建一个新的CSS类,名为Floatleft(而不是直接在<p>的样式里设置):[code].FloatLeft
            {
            float: left;
            width: 33%;
            padding-right: 10px;
            }[/code]然后我们用<div class="FloatLeft">将<div style="float:left">替换掉。
完成以上所讲的内容后,切换到设计视图。你应该看到Repeater已经在DataList左边了(由于还没有配置数据源或模板,这两个控件都是灰的)。


图 2: 调整完位置后的页面
第 二步: 获取每个Category关联的Products总数 With the Repeater and DataList’s surrounding markup complete, we’reready to bind the category data to the Repeater control. However, asthe bulleted list of categories in Figure 1 shows, in addition to eachcategory’s name we also need to display the number of productsassociated with the category. To access this information we can either:
完成了样式设置后,我们现在来将category数据绑定到Repeater。如图1所示,除了category名字外,我们需要显示和它关联的product总数,为了获取这个信息我们可以:

  • 在ASP.NET page的code-behind 里获取这个信息.根 据给定的categoryID我们可以通过ProductsBLL类的GetProductsByCategoryID(categoryID)方法来获 取关联的product总数。这个方法返回一个ProductsDataTable对象,它的Count属性表示了我们需要知道的信息。我们可以为 Repeater创建一个ItemDataBound eventhandler,在每个category绑定到Repeater时调用这个方法然后将总数输出。
  • 在DataSet里更新CategoriesDataTable 添加一个NumberOfProducts列. 我们可以更新CategoriesDataTable的GetCategories()方法来包含这个信息或者保留这个方法,再创建一个新的名为GetCategoriesAndNumberOfProducts()方法。

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

在ItemDataBound Event Handler里获取Products总数在ItemDataBound eventhandler里获取product总数不需要修改DAL。只需要直接修改CategoriesAndProducts.aspx页。通过 Repeater的智能标签添加一个新的名为CategoriesDataSource的ObjectDataSource。使用 CategoriesBLL类的GetCategories()方法配置它。


图 3: 配置 ObjectDataSource

Repeater 里的每个Category都是可点的,而且在点了之后,CategoryProductsDataList会显示那些相关的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。[code]<asp:Repeater ID="Categories" runat="server" DataSourceID="CategoriesDataSource">
            <HeaderTemplate>
            <ul>
            </HeaderTemplate>
            <ItemTemplate>
            <li><asp:LinkButton runat="server" ID="ViewCategory"></asp:LinkButton></li>
            </ItemTemplate>
            <FooterTemplate>
            </ul>
            </FooterTemplate>
            </asp:Repeater>
            <asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
            OldValuesParameterFormatString="original_{0}"
            SelectMethod="GetCategories" TypeName="CategoriesBLL">
            </asp:ObjectDataSource>[/code]注意:在本章Repeater的viewstate必须开启 (Repeater的声明语法里的EnableViewState="False")。在第三步我们将为ItemCommand事件创建一个 eventhandler,在它里面我们要更新DataList的ObjectDataSource的SeleceParameters集合。如果 viewstate 被禁用的话Repeater的ItemCommand不会被激发。想了解具体的原因和更多的信息请参考 A Stumper of an ASP.NET Questionits solution
ID 为ViewCategory的LinkButton还没有设置Text属性。如果我们只需要显示category名字,我们可以通过绑定语法象下面这样来 直接设置:[code]<asp:LinkButton runat="server" ID="ViewCategory" Text='<%# Eval("CategoryName") %>' />[/code]然而在这里我们需要显示的是category的name和proudct的总数。见下面的代码:[code]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);
            }
            }[/code]我们首先要确保我们处理的是dataitem(ItemType为Item或AlternatingItem)然后引用刚刚绑定到当前 RepeaterItem的CategoriesRow。然后调用GetCategoriesByProductID(categoryID)方法,通过 Count属性获取返回的记录条数。最后将ItemTemplate里的ViewCategoryLinkButton的Text属性设 为"CategoryName(NumberOfProductsInCategory)"。

注意:我们也可以在ASP.NET页的code-behind里写一个格式化功能,接收CategoryName和CategoryID的值,返回CategoryName和product总数的连接字符串
添加完event handler后,在浏览器里看看页面。见图4。

图 4: 显示每个 Category的 Name 和 Products总数

更新CategoriesDataTable和CategoriesTableAdpter来包含每个Category的Product总数

除 了在每个category绑定到Repeater时获取product总数外,我们还可以修改DAL里CategoriesDataTable和 CategoriesTableAdapter来包含这个信息.我们在CategoriesDataTable里加一列.打开App_Code\DAL \Northwind.xsd,右键点DataTable,选择Add/Column.见图5.


图 5: 为CategoriesaDataSource增加一个新列

这 样会添加一个名为Column1的列,你可以很方便的修改它的名字.将它重命名为NumberOfProducts.然后我们需要配置这列的属性.点这个 列,来到属性窗口.将DataType从System.String修改为System.Int32.将ReadOnly属性设为True.见图6.



图 6: 设置新列的属性
现 在CategoriesDataTable里已经包含了NumberOfProducts列,但它的值还没有设置.我们可以修改 GetCategories()方法,当每次获取category信息的时候返回它的信息.在这里由于只是本章用到了这个数据,我们来创建一个新的名为 GetCategoriesAndNumberOfProducts().
右键点CategoriesTableAdapter,选择New Query.会出现TableAdapter Query配置向导.选择SQL statement.


图 7: 选择SQL Statement

图 8: SQL Statement 返回行数

下 一步需要我们写sql语句.下面的语句返回每个category的CategoryID,CategoryName,Description和相关 product的总数:[code]SELECT CategoryID, CategoryName, Description,
            (SELECT COUNT(*) FROM Products p WHERE p.CategoryID = c.CategoryID) as NumberOfProducts
            FROM Categories c[/code]
图 9: 使用的sql语句

注意计算product总数的子查询的别名为NumberOfProducts.它和CategoriesDataTable的NumberOfProducts列关联.

最后一步是写方法的名字.分别为Fill a DataTable和Return a DataTable命名为FillWithNumberOfProducts和GetCategoriesAndNumberOfProducts.


图 10: 为新的TableAdapter的方法命名
现 在DAL已经修改完了.由于我们所有展现层,BLL,DAL是逐层调用,所以我们需要在CategoriesBLL类的添加相应的 GetCategoriesAndNumberOfProducts方法.[code] [System.ComponentModel.DataObjectMethodAttribute
            (System.ComponentModel.DataObjectMethodType.Select, false)]
            public Northwind.CategoriesDataTable GetCategoriesAndNumberOfProducts()
            {
            return Adapter.GetCategoriesAndNumberOfProducts();
            }[/code]完成DAL和BLL后,我们来将数据绑定到Categories Repeater.如果在"在ItemDataBound EventHandler里获取Products总数"那部分里你已经为Repeater创建了ObjectDataSource,删掉它,然后去掉 Repeater的DataSourceID属性,同样去掉ItemDataBound事件.Repeater现在回到了初始状态,添加一个名为 CategoriesDataSource的ObjectDataSource.使用CategoriesBLL类的 GetCategoriesAndNumberOfProducts()方法来配置它.见图11.


图 11: 配置ObjectDataSource

然 后修改ItemTemplate,使用数据绑定语法来将CategoryName和NumberOfProducts字段绑定到LinkButton的 Text属性.完整的标记语言如下:[code]<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>[/code]使用这种方法的页面看起来和前面一种方法一样(见图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.


图 12: 配置 ObjectDataSource

由 于GetProductsByCategoryID(categoryID)方法需要一个输入参数,向导会要求我们指定参数源.我们使用GridView 或DataList列出categories时,可以将参数源设为Control,ControlID设为数据控件的ID.然而由于Repeater没有 SelectedValue属性,所以不能用作参数源.你可以查看ControlID下拉列表,它里面只包含一个控件ID— CategoryProducts(DataList).


图 13: 配置参数

配 置完数据源后,VisualStudio为DataList自动产生ItemTemplate.用我们前面使用的template替换默认的 ItemTemplate.将DataList的RepeatColumns属性设为2.完成这些后,你的代码应该和下面的差不多: [code]<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>[/code]目前为止CategoryProductsDataSource
ObjectDataSource的categoryID参数还没有设置.所以浏览页面时没有任何的product显示出来.我们现在需要将它设置为
Repeater中的被点击的category的CategoryID.这里有两个问题,第一是我们如何判断什么时候Repeater的
ItemTemplate被点了.二是哪个被点了.[code]<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>[/code]由于任何一个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的

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

SelectParameters的修改自动引起DataList重新绑定到数据源,显示新的选中的category关联的product.


和Button,ImageButton一样,LinkButton有一个Click event和一个Command event.Click事件仅仅用来说明LinkButton被点击了.有时候我们需要传递更多的信息到event handler里.这样的话,就需要使用LinkButton的CommandNameCommandArgument .当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的.[code]<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>[/code]由于任何一个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的[code]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();
            }[/code]做完这些后,本章就结束了!现在在浏览器里看看你的页面.图14是第一次浏览时的样子.因为还没有category被选中,所以没有 product显示出来.点击一个category,比如Produce,和它关联的product以两列的方式显示出来.见图15.


图 14:第一次浏览页面时没有Product显示

图 15: 点击Produce Category 后,相关的 Products 在右边显示出来

共有0个回答