最近将公司开发框架由ASP.NET2.0升级到4.0时,原来在2.0(.Net Framework 3.5)下工作非常正常的 GridView 控件的自定义分页功能却出现非常诡异的问题:第一次点击“下一页”按钮控件的数据绑定正常,第二次点击则无法绑定任何数据,第三次点击又绑定正常,再次点击绑定又出现异常,上述两种情况交替出现,直至达到最大页码数。如下图所示:
图一:数据绑定正常
图二:数据绑定不正常
由于该功能在之前的ASP.NET2.0中工作非常正常,而升级到4.0中却出现如此诡异的问题,我们的第一反映是在4.0中微软对GridView进行了改动,但所有资料均只显示微软仅对该控件中的客户端ID的产生模式进行了调整(新增了ClientIDRowSuffix及ClientIDRowSuffixDataKey属性),这在解决问题时给了我们比较大的误导。从对问题所表现出来的现象来看,是由于未能引发GridView的PageIndexChanging及PageIndexChanged事件(自定义分页功能在PageIndexChanging及PageIndexChanged事件中处理),由于对分页后的数据的绑定操作是在PageIndexChanged事件中定义,因而未能重新绑定数据也未能引发GridView的RowDataBound事件,最终导致如图二中所显示的问题。
为了更好的理解问题的产生过程,我们简单梳理一下分页功能的实现过程:
1.点击分页按钮,引发页回发事件处理函数RaisePostBackEvent;
2.在页的RaisePostBackEvent函数中找到引发回发事件的控件(分页控件:LinkButton)并调用控件的回发事件处理函数;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
Control control = null;
if (flag) {
control = this.FindControl(str);
}
if ((control != null) && (control.PostBackEventHandler != null)) {
string eventArgument = postData["__EVENTARGUMENT"];
this.RaisePostBackEvent(control.PostBackEventHandler, eventArgument);
}
3.在分页控件(LinkButton)中的RaisePostBackEvent函数中引发按钮的Command事件并根据控件层次结构向上传递引发GridView控件的RowCommand事件并捕捉到分页按钮控件的Page命令从而引发GridView控件的PageIndexChanging及PageIndexChanged事件(这里的机制比较复杂,我们在这就简略点描述);
4.在GridView的PageIndexChanging及PageIndexChanged事件中处理自定义分页逻辑。
在这个问题中,我们已经明确了解到GridView的PageIndexChanging及PageIndexChanged事件是没有引发的,那RowCommand事件有没有被引发呢?我们也写了相关的测试代码,最后证明在出现问题时GridView控件的RowCommand事件及分页控件的加发事件都是没有被没有引发的,测试到此,结合我们上面实现分页功能的简单描述,我们猜测到问题可能出现在第2步,即倒数第二行代码“this.RaisePostBackEvent(control.PostBackEventHandler, eventArgument);”没有被调用,也即 control 可能为 null 值。
通过配置对.Net Framework源代码的调试,最后证明了我们的猜想,但关键是为什么 control 会为 null 值呢?这个问题比较容易理解,从调试中拿到的控件 ID 结合最后显示出的界面我们可以得出,所有数据行并未被渲染,因而这些行控件全部为 null 值,行中包含的控件自然也都为 null 值。所以现在的问题集中在为什么在 PostBack 后不会重新渲染所有行呢?继续调试 .Net Framework 的源代码,最后发现微软对 GridView 的内部实现方式进行了修改,问题出现在 CreateChildControls 函数中,该函数中有一段代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 pagedDataSource.DataSource = dataSource;
2 if (pagedDataSource.IsPagingEnabled && dataBinding) {
3 int pageCount = pagedDataSource.PageCount;
4 if (pagedDataSource.CurrentPageIndex >= pageCount) {
5 int num6 = pageCount - 1;
6 pagedDataSource.CurrentPageIndex = this._pageIndex = num6;
7 }
8 }
9
请注意 if 语句的判断条件,当仅分页且需要绑定数据时才设置分页数据源控件的当前页码索引,而在 PostBack 时,dataBinding 参数一直为 false,这就造成了在 PostBack 后得不到正确的页码索引而造成无法得到正确的数据,对比ASP.NET 2.0中该部分的实现代码,我们发现在 ASP.NET2.0中该部分的代码的 if 语句中是不存在 && dataBinding 的,因而无论 PostBack 与否,我们均能获取到正确的页面索引。目前问题已经完全明朗,解决方法也就非常简单了:仅需要将原来 override PageIndex 的方式修改为 new PageIndex 的方式即可,其基类中的 PageIndex 完全由 .Net Framework 自己维护,永远保持为 0,而我们的派生类中的 PageIndex 由我们自己维护,即可得到正确的结果。
通过本实例我们可以看到,本次微软对.Net Framework的改动还是非常大的,内部作了非常多的细节调整,在后续的升级过程中还有可能碰到其它类似的问题,这就要求我们在后续的工作碰到问题时需要非常认真仔细的排查各类情况。