Web 应用程序中一种常见的情况是显示列表,如搜索结果的列表或目录中产品的列表。除非该列表很短,否则通常分页显示列表,同时为用户提供一种在这些页之间定位的方式。本演练阐释一种使用 DataGrid Web 服务器控件创建分页输出的方法。
创建分页输出有多种方法,包括:
- 为每页创建并填充数据集。该策略很简单,允许您利用 DataGrid 控件的内置分页功能。但是,因为每次用户移动到另一页时它都从数据库获取整个表,所以不是很有效。有关详细信息,请参见指定 DataGrid Web 服务器控件中的分页行为。
- 创建数据集并将其缓存。您可以创建一个数据集,然后将其存储起来(例如,存储在会话状态中)。这节省了每次从数据库获取表的系统开销,但仍包括将整个表存储在服务器内存中的系统开销。对于较小的表,这可能是一种有效的工作方式。
- 从数据库按需要获取一整页数据。该方法在两方面很有效:一方面,它只传输每次所需的最小数量的数据;另一方面,它不需要服务器内存来存储数据集。但是,它的创建也最为复杂。
本演练将阐释最后一种方法。完成时,将得到看起来类似下图的页:
若要完成本演练,您需要:
- 访问带有 Northwind SQL Server 示例数据库的服务器。SQL Server 可以和 Web 服务器位于同一计算机上,或位于另一服务器上。无论在哪一种情况下,都必须配置访问权限。有关详细信息,请参见从 Web 应用程序访问 SQL Server。
- 充足的权限,以便在 Web 服务器所在的计算机上创建 ASP.NET Web 应用程序项目。有关详细信息,请参见 Visual Studio 中设计时的 Web 应用程序安全性。
演练被分成若干较小的部分:
- 创建 Web 应用程序项目和 Web 窗体页。
- 添加从数据库获取数据所需的数据组件。
- 将 DataGrid 控件添加到窗体上。
- 添加代码当用户从一页移动到另一页时获取正确的数据。
因为本演练着重介绍 Web 窗体页的一些高级概念,所以假设您总体上比较熟悉以下概念:
- 数据命令。有关详细信息,请参见 Visual Studio 中的 DataCommand 对象介绍。
- 设置命令参数。有关详细信息,请参见设置和获取数据命令参数。
- 将数据阅读器用作只读、只进游标。有关详细信息,请参见执行返回结果集的数据命令。
- 在往返过程间将页信息保存在视图状态中。有关详细信息,请参见使用视图状态保存 Web 窗体页值。
创建项目和窗体
第一步是创建 Web 应用程序和 Web 窗体页。
创建项目和窗体
- 在“文件”菜单上指向“新建”,然后单击“项目”。
- 在“新建项目”对话框中,请执行以下操作:
- 在“项目类型”窗格中选择“Visual Basic 项目”或“Visual C# 项目”。
- 在“模板”窗格中选择“ASP.NET Web 应用程序”。
- 在“位置”框中,为您的应用程序输入完整的 URL(包含 http://、服务器名称和项目名称)。Web 服务器上必须安装 IIS 5 版(或更高版本)和 .NET Framework。如果计算机上已安装 IIS,可以为服务器指定 http://localhost。(如果正常使用代理服务器访问 Internet,为了使用本地主机,可能需要配置 Internet Explorer 以绕过代理服务器。)
当单击“确定”时,将在您指定的 Web 服务器的根处创建新的 Web 窗体项目。此外,名为 WebForm1.aspx 的新 Web 窗体页将显示在“设计”视图中 Web 窗体设计器上。
提示 如果在创建 Web 应用程序项目时遇到问题,请参见“Web 访问失败”对话框。
添加数据组件
在本演练中,您将需要执行两个稍微不同的 SQL 语句来获取一页数据。两个语句都基于起始 ID 每次获取 10 行;它们仅在 WHERE 子句部分有所不同。
- 获取下一页数据的语句从当前页上最后一行之后(即 ID 大于最后一行)的那一行开始从表中获取 10 行。
- 获取上一页的语句从上一页上第一个记录开始(即 ID 等于第一个记录)获取 10 个记录。
注意 作为使用这两个语句的替换方法,您可以创建单个语句并动态更改 WHERE 子句。在本演练中,您将使用两个单独的语句以减少所需编写的代码量。
在本演练中,您将使用 SQL Server Northwind 数据库中的 Customers 表。只要符合以下条件,您就可以将本演练中阐释的技术用于任何表:
- 您访问的表具有一个唯一的 ID。
- 该表是按 ID 排序的。
- 您使用的 SQL 语言变体支持 Select 语句中的 TOP 关键字。TOP 关键字允许您指定要返回的行数。当然,在本例中您将使用 TOP 关键字指定每页的行数。
创建和配置连接
首先,添加一个连接,该连接指向要从中读取数据的数据库。
创建数据连接
- 从工具箱的“数据”选项卡中,将 SqlConnection 对象拖到页上。
注意 因为正在使用 SQL Server,所以您可以使用 SqlConnection 对象,它已经进行了优化,可用于 SQL Server 7.0 或更高版本。如果您正在使用其他数据库,则应使用 OleDbConnection 对象,它提供对任何与 OLE DB 兼容的数据源的 ADO.NET 访问。
- 选择连接并在“属性”窗口中单击“ConnectionString”属性框中的按钮。
列表显示任何现有的连接和“<新建连接...>”选项。
- 除非已经有了与 SQL Server Northwind 数据库的连接,否则选择“<新建连接...>”。
显示出“数据链接属性”对话框。
- 创建指向 SQL Server 和 Northwind 数据库的连接,然后单击“确定”。
注意 您需要对所使用的 SQL Server 具有适当的读/写权限。如果 SQL Server 与 IIS 位于同一台计算机上,建议您在创建此连接时指定 Windows 集成安全性。否则,可以指定用户名和密码并将该信息与此连接保存在一起,但这样做会危及安全性。有关更多信息,请参见从 Web 应用程序访问 SQL Server。
指定的连接信息被另存为连接的连接字符串。
现在需要添加命令。您总共需要两个命令,每个 SQL 语句一个。
创建获取多页数据的数据命令
- 从工具箱的“数据”选项卡中,将两个 SqlCommand 对象拖到页上。
- 将其中一个命名为 cmdNext,将另一个命名为 cmdPrevious。
- 选择 cmdNext 命令,然后在“属性”窗口中单击“Connection”属性中的按钮,选择以前建立的连接。(您需要打开“现有”节点。)
- 在 CommandText 属性中,单击省略号按钮以打开“查询生成器”对话框。
- 使用“查询生成器”对话框构造以下 SQL 语句:
6. SELECT TOP 10 CustomerID, CompanyName, City
7. FROM Customers
8. WHERE (CustomerID > @customerid)
ORDER BY CustomerID
- 完成后关闭“查询生成器”对话框。
此 SQL 语句被填充到 CommandText 属性中。
- 为 cmdPrevious 命令对象重复第三步到第五步,创建该查询:
11. SELECT TOP 10 CustomerID, CompanyName, City
12. FROM Customers
13. WHERE (CustomerID >= @customerid)
ORDER BY CustomerID
您已经将数据组件添加到了页。
添加 DataGrid 控件
若要阐释分页,您需要一个 DataGrid 控件。为了进行定位,您将创建“上一页”和“下一页”按钮。
添加并配置 DataGrid 控件
- 从工具箱的“Web 窗体”选项卡中,将 DataGrid 控件拖到页上。
- 在“属性”窗口的底部选择“自动套用格式”链接,并为网格选择预定义格式。
- 在“属性”窗口中,单击“属性生成器”链接。
出现“DataGrid1 属性”对话框。
- 单击“分页”选项卡并进行以下设置:
属性 |
设置 |
允许分页 |
选中 |
允许自定义分页 |
选中 |
页大小 |
10 |
显示导航按钮 |
不选中 |
- 单击“确定”关闭对话框。
注意 如果以前使用过数据绑定 DataGrid 控件,您可能希望设置控件的 DataSource 属性。然而,在本例中将以代码设置数据源。同样,将不设置任何列。
添加定位按钮
- 从工具箱的“Web 窗体”选项卡中,将两个 LinkButton 控件拖到页上。
- 将其中一个命名为 btnPrevious,将另一个命名为 btnNext。
- 适当地设置按钮的文本。例如,“下一页”按钮的文本可能是 Next >。
添加获取并显示数据页的代码
现在您可以添加执行分页的代码。基本逻辑是当用户进行定位时,您将(使用您创建的数据命令对象)执行 SQL 语句并返回带有 10 行的数据阅读器(SqlDataReader 对象)。将 DataGrid 控件绑定到数据阅读器,行自动显示在网格中。
下一页和上一页
页中唯一复杂的部分是实现定位,问题是这样的:如何确定从数据库获取哪 10 条记录?
下一页的逻辑是从紧接在网格中最后一行之后的一行开始获取 10 行。因此,将获取网格中最后一行的 ID 并将其用作先前创建的 cmdNext 命令的参数。
移动到上一页的逻辑更加复杂。您要获取从当前网格中的页前一页的第一个 ID 开始(包括第一个 ID)的 10 行。为此,您需要得到上一页的 ID,如果用户继续反向翻页,则还需要之前的页的 ID。
一种解决方案是创建结构,当显示每一页时,该结构都存储该页第一个记录的 ID。通过这种方式,当您返回到该页时,便可以获取其起始 ID 并使用它作为填充该页的查询的参数。
因为这是 Web 窗体页,所以不能将页信息存储在变量中;您必须将它存储在某个位置,在往返过程间它都被保留在该位置。在本演练中,您将信息存储在视图状态中,这将它放入页本身(在隐藏字段中)。您将需要存储两种类型的项:
- 当前页码。将使用页码作为索引值,它还将告诉您用户何时向后定位到第一页。
- 对于每页,网格中第一行的 ID。您将在视图状态中为每页创建一个新项。该项的名称是页码,它的值是 ID。
例如,如果位于网格的第三页,则向视图状态添加四项,如下所示:
CurrentPage=3
0=ALFKI
1=BSBEV
2=FAMIA
下面一节显示实现分页逻辑所需的代码。
在网格中显示数据
不管用户如何进行定位,填充网格都是相同的。因此,您可以创建执行 cmdNext 或 cmdPrevious 命令(这取决于哪一个命令传递到方法)的一般方法。
在网格中显示数据
- 创建名为 CurrentPage 的私有变量。稍后您将用其他方法设置该变量的值。
- 创建一个接受数据命令 (SqlCommand) 作为参数的方法(可能名为 FillGrid)。
- 实例化数据阅读器。
- 打开连接和数据命令的 ExecuteReader 方法以执行其 SQL 语句并获取行。
- 将 DataGrid 控件的 DataSource 属性设置为数据阅读器,然后绑定网格。
- 关闭数据阅读器和连接。
- 将两个值保存在视图状态中:
- CurrentPage 变量的值。
- 网格中第一行 ID 列的值。将该值存储在视图状态项中,其名称是页码。逻辑如下所示:
· ' Visual Basic
· ViewState(CurrentPage.ToString()) = DataGrid1.Items(0).Cells(0).Text
·
· // C#
ViewState[CurrentPage.ToString()] = DataGrid1.Items[0].Cells[0].Text;
- 最后,确定您是否位于表的结尾。为了简单起见,在本演练中,您将只测试查询返回的行数是否少于页大小 (10)。如果您已到达表的结尾,则禁用“下一页”按钮,这样用户就不会尝试继续。
注意 有关如何确定是否位于表尾的另一个建议,请参见本演练稍后部分的“后续步骤”。
下面的示例显示如何创建 CurrentPage 变量:
' Visual Basic
Public Class WebForm1
Inherits System.Web.UI.Page
Private CurrentPage As Integer
' more declarations here
//C#
public class WebForm1 : System.Web.UI.Page
{
private int CurrentPage;
// more declarations here
下面的示例显示完整的一般方法的代码是什么样的:
' Visual Basic
Private Sub FillGrid(ByVal currentSqlCommand as SqlClient.SqlCommand)
Dim dr As SqlClient.SqlDataReader
SqlConnection1.Open()
dr = currentSqlCommand.ExecuteReader()
DataGrid1.DataSource = dr
DataGrid1.DataBind()
dr.Close()
SqlConnection1.Close()
ViewState("CurrentPage") = CurrentPage
ViewState(CurrentPage.ToString()) = DataGrid1.Items(0).Cells(0).Text
' Determine how many rows were filled into the grid. If it is less
' than the number of rows per page, there are no more rows in the
' table, and the Next button should be disabled.
If DataGrid1.Items.Count < DataGrid1.PageSize Then
btnNext.Enabled = False
End If
End Sub
//C#
private void FillGrid(System.Data.SqlClient.SqlCommand currentSqlCommand)
{
System.Data.SqlClient.SqlDataReader dr;
sqlConnection1.Open();
dr = currentSqlCommand.ExecuteReader();
DataGrid1.DataSource = dr;
DataGrid1.DataBind();
dr.Close();
sqlConnection1.Close();
ViewState["CurrentPage"] = CurrentPage;
ViewState[CurrentPage.ToString()] = DataGrid1.Items[0].Cells[0].Text;
// Determine how many rows were filled into the grid. If it is less
// than the number of rows per page, there are no more rows in the
// table, and the Next button should be disabled.
if (DataGrid1.Items.Count < DataGrid1.PageSize)
{
btnNext.Enabled = false;
}
}
初始化页
页第一次运行时,您要显示第一页数据。既已具有了填充网格的一般方法,所有需要做的就是设置参数及调用方法。您要执行 cmdNext 命令。将其 @customerid 参数设置为空字符串,意味着查询将取回大于 "" 的前 10 条记录,即表中的前 10 条记录。还要将页码 (CurrentPage) 设置为零以指示您位于第一页。
代码如下所示:
' Visual Basic
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'Put user code to initialize the page here
If Not Page.IsPostBack Then
cmdNext.Parameters("@customerid").Value = ""
CurrentPage = 0
FillGrid(cmdNext)
End If
End Sub
//C#
private void Page_Load(object sender, System.EventArgs e)
{
// Put user code to initialize the page here
if (!Page.IsPostBack)
{
cmdNext.Parameters["@customerid"].Value = "";
CurrentPage = 0;
FillGrid(cmdNext);
}
}
定位到下一页
当用户定位到下一页时,您要再次使用一般方法执行 cmdNext 命令。这一次,命令的参数是网格中最后一行 ID 列的值。
您还需要将页码加 1,以便一般 FillGrid 方法可以使用该数字为该 ID 创建另一个视图状态项。若要将页码加 1,您需要从视图状态中获取旧页码。当进行该操作时,需要将其强制转换为一个整数,因为视图状态中的所有项都存储为对象。
“下一页”按钮的代码如下所示:
' Visual Basic
Private Sub btnNext_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnNext.Click
' Get the page number of the page most recently displayed
CurrentPage = CType(ViewState("CurrentPage"), Integer)
CurrentPage += 1
'Gets the id on current page
Dim lastid As String = DataGrid1.Items(9).Cells(0).Text
cmdNext.Parameters("@customerid").Value = lastid
FillGrid(cmdNext)
End Sub
//C#
private void btnNext_Click(object sender, System.EventArgs e)
{
// Get the page number of the page most recently displayed
CurrentPage = (int)(ViewState["CurrentPage"]);
CurrentPage++;
// Gets the id on current page
string lastid = DataGrid1.Items[9].Cells[0].Text;
cmdNext.Parameters["@customerid"].Value = lastid;
FillGrid(cmdNext);
}
定位到上一页
最后,您需要添加向后移动的代码。这里将使用您存储在视图状态中的信息。首先,获取当前页码后减 1。然后使用该页码作为名称查找视图状态,获取页上第一行的 ID。使用该 ID 作为 cmdPrevious 命令(将该命令传递给一般 FillGrid 方法)的参数。
为了防止离开表的开头,您应该检查页计数不在零以下。另外,您应该启用“下一页”按钮,以防它在上一次定位中被禁用。
' Visual Basic
Private Sub btnPrevious_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnPrevious.Click
btnNext.Enabled = True
CurrentPage = CType(ViewState("CurrentPage"), Integer)
CurrentPage -= 1
If CurrentPage >= 0 Then
Dim firstid As String
firstid = CType(ViewState(CurrentPage.ToString()), String)
cmdPrevious.Parameters("@customerid").Value = firstid
FillGrid(cmdPrevious)
End If
End Sub
//C#
private void btnPrevious_Click(object sender, System.EventArgs e)
{
btnNext.Enabled = true;
CurrentPage = (int)(ViewState["CurrentPage"]);
CurrentPage--;
if (CurrentPage >= 0)
{
string firstid;
firstid = (string)(ViewState[CurrentPage.ToString()]);
cmdPrevious.Parameters["@customerid"].Value = firstid;
FillGrid(cmdPrevious);
}
}
测试
添加了代码后,测试 Web 窗体页中的数据访问。
测试 Web 窗体页
- 保存页。
- 在解决方案资源管理器中,右击该页并选择“在浏览器中查看”。
确认网格中是否显示了一个包含 10 个客户的列表。
- 多次单击“下一页”和“上一页”按钮以确保可以正确定位。要进行的特殊测试包括:
- 在第一页上,单击“上一页”,不应该发生任何变化。
- 单击“下一页”直到到达表的结尾并确保这时“下一页”按钮是禁用的。
后续步骤
本演练阐释实现分页的一种方法。此处阐释的情况虽然简单,但是很完整。在实际的应用程序中,许多其他的因素可能会发挥作用,分页可能变得非常复杂。要考虑的一些问题包括:
- 确定何时到达文件结尾,本演练使用的算法很简单,但不是十分可靠。(它将返回行的数目与页中行的数目进行比较)。在实际的应用程序中,可能要添加更加复杂的算法。其中一种可能是执行下面的操作:
- 将另一个命令对象添加到包括该 SQL 语句的页:
Select Count(CustomerID) From Customers
- 第一次运行页时,使用命令的 ExecuteScalar 方法执行该命令以获取表中行的总数。然后计算该表所需的总的页计数并将其分别存储在全局变量和视图状态中。在后面的发送中,从视图状态中取回该值。
- 在 FillGrid 方法中,检查当前页是否已到达总的页计数。如果到达,则禁用“下一页”按钮。
- 该演练假设数据不是易失的。如果其他用户向该表中添加了行,页边界将更改,那么存储给定页的第一个 ID 将不是一个可行的解决方案。
- 您可以创建存储过程来执行此处通过 SQL 语句完成的操作。
- 安全性。有关详细信息,请参见 Web 应用程序安全威胁概述。