ASP.NET中使用ListView多层绑定的分页问题始末

  前段时间着手做一个网站,在使用ListView和DataPager时遇到了一个新问题。先描述一下页面的要求吧:有两级类别,一个大类,下面有子类,子类下才对应了产品,然后在一个页面中把大类(指定了哪些)、子类、产品按结构显示出来,其中产品要有分页。好了,目前来说,整个页面交互性只在于产品的分页上,大类、子类在服务器端只是显示的作用,故我理所当然地想到了使用ListView来层层绑定,并在产品层添加DataPager控件来分页。

  后台的数据DTO也已经定义好,分别有大类、小类、产品:

 1     // 产品大类DTO
 2     public class ProductTypeDTO
 3     {
 4         public int ID { get; set; }
 5         // ...
 6         public IEnumerable<CategoryDTO> Categorys { get; set; }
 7     }
 8 
 9     // 产品小类DTO
10     public class CategoryDTO
11     {
12         // ...
13         public IEnumerable<ProductDTO> Products { get; set; }
14     }
15 
16     // 产品DTO
17     public class ProductDTO
18     {
19         public int ID { get; set; }
20         // ...
21     }

  以惯性思维,先把大类数据源传给第一层ListView,再在一层内写小类ListView,最后再在小类ListView中写产品与DataPager。目前假设已经处理完成的数据源为IList<ProductTypeDTO> ProductTypeList,它就是我们要显示的东西了。

  最初设计的前台代码为:

 1              <!-- Begin Types -->
 2              <asp:ListView ID="LV_Products" runat="server">
 3                  <ItemTemplate>
 4                      // 这里是大类需要解析的HTML控件           
 5                      <!-- Begin Categorys-->
 6                      <asp:ListView DataSource='<%# DataBinder.Eval(Container.DataItem,"Categorys") %>' runat="server">
 7                          <ItemTemplate>
 8                                      // 这里是小类需要解析的HTML控件                                              
 9                                      <!-- Begin Products -->
10                                      <asp:ListView ID="LV_Item" DataSource='<%# DataBinder.Eval(Container.DataItem,"Products") %>' runat="server">
11                                          <ItemTemplate>
12                                              // 这是具体产品需要解析的HTML控件                                            
13                                          </ItemTemplate>
14                                      </asp:ListView>
15                                      <!-- End Products-->
16                                      <div class="cleardiv"></div>
17                                      <!-- Begin DataPager-->
18                                      <div class="product_dp">
19                                          <asp:DataPager ID="DP_Item" PageSize="10" PagedControlID="LV_Item" runat="server">
20                                              <Fields>
21                                                  // 这是分页按钮
22                                              </Fields>
23                                          </asp:DataPager>
24                                      </div>
25                                      <!-- End DataPager-->                                
26                              <div class="cleardiv"></div>
27                              <br />
28                          </ItemTemplate>
29                      </asp:ListView>
30                      <!-- End Categorys-->
31                      <div class="cleardiv"></div>
32                      <br />
33                  </ItemTemplate>
34              </asp:ListView>
35              <!-- End Types -->

  以上省略了与问题无关的一些绑定项代码,根据以往的经验,要想不出现点击两次才翻页的情况,可以把数据绑定语句:

  LV_Products.DataSource = ProductTypeList;
  LV_Products.DataBind();

  写在Page_PreRender中,在IIS中调试一看,唔,数据正常显示,分页已经分好,但是我一点其它页的,完全没作用,页面闪了一下,页面的元素还是第一页那些。经过排查,我觉得是数据绑定的问题(在PreRender中进行绑定,会导致每次都进行了一次数据更新,而我的LV_Item是动态生成的,DataPager也是动态的,这会导致生成一个全新的页面,而不会根据以前的PageIndex生成翻页),于是把后台绑定的代码改成写在!IsPost中,看了下,能分页、能显示,点击分页,好像是发生了跳转,可惜那些数据不能正常地绑定进来,而且需要点击2次才能正常跳转。

  思来想去,还是对ASP.NET的页面加载不了解,于是仔细看了它的初始化流程,觉得与Page_PreRender相似的,我使用ListView.PreRender事件应该也可以达到相同效果,那么需要取消LV_Item的绑定,而在后台手动来绑定其数据源(LV_Products的绑定放在!IsPostBack内)。当页面第一次加载时就绑定所有LV_Item的数据,当分页回传时,只重新绑定对应的LV_Item的数据源。LV_Item的前台代码改为:

<asp:ListView ID="LV_Item" OnPagePropertiesChanged="LV_PageChanged" runat="server">
    // ...
</asp:ListView>

  看到了没,就是通过ListView的分页改变事件来触发数据源绑定的,绑定需要发生在它的PreRender事件中:

protected void LV_PageChanged(object sender, EventArgs e)
{
    (sender as ListView).PreRender += LV_OnPreRender;
}

  这下明白了吧,第一次加载页面,所有的ListView都会自动触发OnPagePropertiesChanged事件,于是所有的ListView都会加载PreRender事件处理函数进行数据绑定,而当点击分页回传里,只有一个ListView会触发OnPagePropertiesChanged事件,也就只有一个ListView会加载PreRender事件处理函数,所以绑定源的重新绑定将只发生一个LV_Item身上。

  好了,LV_OnPreRender作为LV_Item的重新绑定函数,需要有具体的大类、小类的信息才能把产品列表绑定给ListView,目前我只有一个ProductTypeList把大类、小类、产品的关系给封装好了,而LV_OnPreRender、LV_PageChanged都不能得到目前这个ListView是哪个大类下的哪个小类的列表信息,怎么办?

  只好另辟蹊径了,来看个LV_Item生成的客户端代码:

  发现了什么,这个是有命名规则的!CPH_main是我用的母版名,LV_Products是我的大类ListView,然后ctl100,它是我的小类ListView(没给它命名,自动生成的),然后后面的0,就是第0个大类!再又接了个ctl100是我用的ajax UpdataPanel(本文中没有,这是实际项目中用的),后面的0、1、2、3...就是实际的小类了!唔,经过测试,这是完全没有问题的,只要你没有改ClientIDMode。在后台代码中,我们可以通过ListView.UniqueID来获取与这类似的值,只是有一点区别。于是我们最后一个要点LV_OnPreRender的函数为:

 1     private static Regex _productRegex = new Regex(@"(?<=ctrl)\d+", RegexOptions.Singleline);
 2     // 绑定数据源
 3     protected void LV_OnPreRender(object sender, EventArgs e)
 4     {        
 5         var lv = sender as ListView;
 6         // 确认数据源
 7         /* 根据具体情况来,我的UnitqueID是这样的:
 8          * 全局标识 ctl00$CPH_main$LV_Products$ctrl0$ctl00$ctrl0$LV_Item
 9          *                                     大类        小类                       
10         */
11         var matche = _productRegex.Match(lv.UniqueID, 25);
12         // 重新绑定数据
13         try
14         {
15             int categoryID = Int32.Parse(matche.NextMatch().Value);
16             int typeID = Int32.Parse(matche.Value);
17             lv.DataSource = BaseConfig.ProductTypeList[typeID].Categorys.ElementAt(categoryID).Products;
18             lv.DataBind();
19         }
20         catch
21         {
22             // 错误处理
23         }
24     }

  至此,分页完成,效率不错,在IIS上测试起来非常快,百来条一下的事。想看最终效果图的,可以点我打开图片链接。

  转载请注明原址:http://www.cnblogs.com/lekko/archive/2013/04/27/3046481.html

原文地址:https://www.cnblogs.com/lekko/p/3046481.html