介绍
借助 Asp.Net 提供的数据绑定控件,我们无需太多的代码,甚至不需要代码,只要在 VS2005 中拖拽几下控件,进行一些属性的设置,便可以实现在Asp时代需要做大量工作才能够实现的分页功能。但在实际的应用中,尤其是在Web站点程序中,我们经常需要更加丰富的用户界面,而类似DataList或者 GridView 这样的数据控件往往不能或者很难满足我们的要求。此时,我们常常求助于 Repeater 控件,这样我们依旧会面临分页及其显示的问题。
本文不是讲述如何进行数据库分页,而将注意力集中在如何实现可定制地 获取页码、获取路径、显示分页链接,并且通过构建一个用户控件来实现代码重用上。如果你是一个初学者,你可以借鉴一下我的实现方式;如果你已经是一位高手,不妨提出设计的不足和改进意见。
NOTE:本文是以接口的实现方式作为讲述,这是因为我写这篇文章的时候使用的是接口,但我后来又提供了一种更好的使用继承的方式来实现,我提供了两个版本的代码下载,你可以相互对比着参考。
控件组成
为了能迅速提起大家的兴趣,可以先点击这个链接,看看实际的效果:
http://www.tracefact.net/Demo/Pager/Default.aspx
IUrlManager 接口
想一想如果你在设计一个可重用的分页用户控件,你面临的问题是什么每个人获取页码的方式都不同,例如,你的站点URL可能是类似这样的 Default.aspxpage=1 ,而另外一个站点的URL 是这样的 Default.aspxp=1。更有一些可能根本不使用 QueryString 来获取页码,它们的URL可能是这样的 Default-1.aspx、Default-2.aspx 等等。获取页码的方式不同,根据页码产生链接地址的方法自然也不相同。按照封装变化的思想,我们应该将这变化的部分取出来,建一个 IUrlManager 接口:
public interface IUrlManager
{
int CurrentPageIndex{ get; } // 当前页码
string GetPageUrl(int pageIndex); // 根据 页码 获取页面路径
}
而实际上,当前页码不应该大于总页数,所以获取当前页CurrentPageIndex属性需要能得知 总页数,而总页数通常是由 记录数 和 分页大小计算得出,这个接口实际上应该是这样:
public interface IUrlManager
{
int CurrentPageIndex{ get; } // 当前页码
string GetPageUrl(int pageIndex); // 根据 页码 获取页面路径
int PageCount { get; } // 总页数
int RecordCount { get; } // 记录总数
int PageSize { get; } // 分页大小
}
要求在 IUrlManager里实现总页数PageCount属性可能有点奇怪,但仔细想一想就明白了:
控件本身需要知道总共有多少页,然后才能判断显示多少链接数。我们的用户控件部分仅仅是进行一系列链接的显示,它仅需要知道 当前页码、总页码、以及链接的URL 就足够了 因为 当前页码 应该小于等于 总页码,所以获取当前页码 CurrentPageIndex 也需要知道 总页码。 而为什么要实现 RecordSize 和 PageSize 属性是一个值得思考的地方:
总页码 是根据 记录数 和 分页大小 算得的,所以对于实现 IUrlManager 接口的类,我们总是需要提供 记录数 和 分页大小,何不简单地提供一个属性来对它们进行访问。而其他的地方(比如某个方法)有可能会需要这两个值,此时我们可以直接将 IUrlManager 作为参数传进去。
问题是:如果这个接口仅仅是用于 分页控件,那么实现 RecordSize 和 PageSize 是不必要的,我们也不应该在控件上设置 RecordCount、PageSize。这里的粒度可能大了。
DefaultUrlManager 类
现在 所有获取页码 及 根据页码获取路径 的逻辑都可以放在实现了这个接口的类中。如果你想使用这个控件,你需要提供一个实现了IUrlManger接口的类。为了使控件立即可用,我在这里提供了一个默认实现,我管它叫做 DefaultUrlManger。它通过Request.QueryString获取页码,并默认以"Page"作为参数。
public class DefaultUrlManager : IUrlManager {
private HttpContext context;
private Regex reg;
private string queryParam; // QueryString 参数名称
private int currentPageIndex; // 当前页
private int pageCount; // 总页数
private int recordCount; // 记录总数
private int pageSize; // 分页大小
// 构造函数
public DefaultUrlManager(int recordCount, int pageSize, string queryParam)
{
if (recordCount < 0)
throw new ArgumentOutOfRangeException("recordCount 应该大于等于 0 !");
if (pageSize < = 0)
throw new ArgumentOutOfRangeException("pageSize 应该大于 0 !");
if (string.IsNullOrEmpty(queryParam))
throw new ArgumentNullException("queryParam 不能为空!");
// 设置私有变量
this.recordCount = recordCount;
this.pageSize = pageSize;
this.queryParam = queryParam;
context = HttpContext.Current;
string pattern = @"(< r1>[&]" + queryParam + @"=)[^&]*";
reg = new Regex(pattern, RegexOptions.IgnoreCase);
// 如果记录数为0,至少也显示一页
if (recordCount == 0) {
currentPageIndex = 1;
pageCount = 1;
} else {
// 设置总页数
double recordCount2 = Convert.ToDouble(recordCount);
double pageSize2 = Convert.ToDouble(pageSize);
pageCount = Convert.ToInt32(Math.Ceiling(recordCount2 / pageSize2));
// 设置当前页码
string queryIndex = context.Request.QueryString[queryParam];
if (string.IsNullOrEmpty(queryIndex))
currentPageIndex = 1;
else {
try {
currentPageIndex = Math.Abs(int.Parse(queryIndex));
if (currentPageIndex == 0)
currentPageIndex = 1;
if (currentPageIndex > pageCount)
currentPageIndex = pageCount;
}
catch {
currentPageIndex = 1;
}
}
}
}
// 默认以 "Page" 作为 QueryString 的参数
public DefaultUrlManager(int recordCount, int pageSize) : this(recordCount, pageSize, "Page") { }
// 默认以 10 作为 PageSize
public DefaultUrlManager(int recordCount, string queryParam) : this(recordCount, 10, queryParam) { }
// 默认以 "Page" 作为 QueryString 的参数,以 10 作为分页大小
public DefaultUrlManager(int recordCount) : this(recordCount, 10) { }
public string GetPageUrl(int pageIndex) {
string pageUrl = context.Request.RawUrl;
// 如果找到匹配,也就是URL中含有类似 page=3 或者 &page=4 这样的字符串