生成用于从多个用户帐户中选择一个帐户的界面 (C#)
在本教程中,我们将生成一个用户界面,其中包含分页的可筛选网格。 具体而言,我们的用户界面将包含一系列 LinkButton,用于根据用户名的起始字母筛选结果,以及一个 GridView 控件来显示匹配的用户。 首先,我们将在 GridView 中列出所有用户帐户。 然后,在步骤 3 中,我们将添加筛选器 LinkButtons。 步骤 4 查看对筛选结果进行分页。 步骤 2 到步骤 4 中构造的接口将在后续教程中用于为特定用户帐户执行管理任务。
简介
在 “向用户分配角色” 教程中,我们创建了一个基本的界面,供管理员选择用户和管理其角色。 具体而言,界面为管理员提供了所有用户的下拉列表。 当有十几个左右的用户帐户,但对于拥有数百或数千个帐户的网站来说,这种界面很合适。 分页的可筛选网格更适合具有大量用户群的网站。
在本教程中,我们将生成这样的用户界面。 具体而言,我们的用户界面将包含一系列 LinkButton,用于根据用户名的起始字母筛选结果,以及一个 GridView 控件来显示匹配的用户。 首先,我们将在 GridView 中列出所有用户帐户。 然后,在步骤 3 中,我们将添加筛选器 LinkButtons。 步骤 4 查看对筛选结果进行分页。 步骤 2 到步骤 4 中构造的接口将在后续教程中用于为特定用户帐户执行管理任务。
现在就开始吧!
步骤 1:添加新 ASP.NET 页
在本教程和接下来的两个教程中,我们将研究各种与管理相关的功能和功能。 我们需要一系列 ASP.NET 页面来实现这些教程中介绍的主题。 让我们创建这些页面并更新站点地图。
首先,在名为 Administration
的项目中创建一个新文件夹。 接下来,将两个新的 ASP.NET 页添加到 文件夹中,将每个页面与 Site.master
母版页链接。 为页面命名:
ManageUsers.aspx
UserInformation.aspx
此外,将两个页面添加到网站的根目录: ChangePassword.aspx
和 RecoverPassword.aspx
。
此时,这四个页面应具有两个 Content 控件,一个用于母版页的 ContentPlaceHolders: MainContent
和 LoginContent
。
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="LoginContent" Runat="Server">
</asp:Content>
我们希望显示这些页面的 ContentPlaceHolder 的母版页的默认标记 LoginContent
。 因此,请删除 Content 控件的 Content2
声明性标记。 执行此操作后,页面的标记应仅包含一个 Content 控件。
文件夹中 ASP.NET 页 Administration
仅供管理用户使用。 我们在创建和管理角色教程中向系统添加了管理员角色;将对这两个页面的访问权限限制为此角色。 为此,请将文件 Web.config
添加到文件夹, Administration
并配置其 <authorization>
元素以允许管理员角色的用户并拒绝所有其他用户。
<?xml version="1.0"?>
<configuration>
<system.web>
<authorization>
<allow roles="Administrators" />
<deny users="*"/>
</authorization>
</system.web>
</configuration>
此时,项目的解决方案资源管理器应类似于图 1 所示的屏幕截图。
图 1:已将四个新页面和一个 Web.config
文件添加到网站 (单击以查看全尺寸图像)
最后, () Web.sitemap
更新站点地图,以包含页面条目 ManageUsers.aspx
。 在为角色教程添加的 后面 <siteMapNode>
添加以下 XML。
<siteMapNode title="User Administration" url="~/Administration/ManageUsers.aspx"/>
更新站点地图后,通过浏览器访问站点。 如图 2 所示,左侧的导航现在包含管理教程的项。
图 2:站点地图包含标题为“用户管理”的节点 (单击以查看全尺寸图像)
步骤 2:列出 GridView 中的所有用户帐户
本教程的最终目标是创建一个分页的、可筛选的网格,管理员可以通过该网格选择要管理的用户帐户。 首先, 我们列出 GridView 中的所有用户。 完成此操作后,我们将添加筛选和分页接口和功能。
ManageUsers.aspx
打开 文件夹中的页面Administration
并添加 GridView,并将其ID
设置为 UserAccounts
。 稍后,我们将编写代码,使用 Membership
类 GetAllUsers
的 方法将用户帐户集绑定到 GridView。 如前面的教程中所述,GetAllUsers 方法返回对象 MembershipUserCollection
,该对象是对象的集合 MembershipUser
。 MembershipUser
集合中的每个都包含 、UserName
Email
、 IsApproved
等属性。
若要在 GridView 中显示所需的用户帐户信息,请将 GridView 的 AutoGenerateColumns
属性设置为 False,并为 、 Email
和 Comment
属性添加 BoundFieldsUserName
,并为 、 IsLockedOut
和 IsOnline
属性添加 CheckBoxFieldsIsApproved
。 可以通过控件的声明性标记或通过“字段”对话框应用此配置。 图 3 显示了在取消选中“自动生成字段”复选框并添加并配置“BoundFields”和“CheckBoxFields”后,“字段”对话框的屏幕截图。
图 3:将三个 BoundFields 和三个 CheckBoxFields 添加到 GridView (单击以查看全尺寸图像)
配置 GridView 后,请确保其声明性标记如下所示:
<asp:GridView ID="UserAccounts" runat="server" AutoGenerateColumns="False">
<Columns>
<asp:BoundField DataField="UserName" HeaderText="UserName"/>
<asp:BoundField DataField="Email" HeaderText="Email" />
<asp:CheckBoxField DataField="IsApproved" HeaderText="Approved?"/>
<asp:CheckBoxField DataField="IsLockedOut" HeaderText="Locked Out?" />
<asp:CheckBoxField DataField="IsOnline" HeaderText="Online?"/>
<asp:BoundField DataField="Comment" HeaderText="Comment"/>
</Columns>
</asp:GridView>
接下来,我们需要编写将用户帐户绑定到 GridView 的代码。 创建一个名为 BindUserAccounts
的方法来执行此任务,然后在第一页访问时从 Page_Load
事件处理程序调用它。
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
BindUserAccounts();
}
private void BindUserAccounts()
{
UserAccounts.DataSource = Membership.GetAllUsers();
UserAccounts.DataBind();
}
花点时间通过浏览器测试页面。 如图 4 所示, UserAccounts
GridView 列出了系统中所有用户的用户名、电子邮件地址和其他相关帐户信息。
图 4:用户帐户在 GridView 中列出 (单击以查看全尺寸图像)
步骤 3:按用户名的第一个字母筛选结果
目前 GridView UserAccounts
显示 所有 用户帐户。 对于具有数百或数千个用户帐户的网站,用户必须能够快速分析显示的帐户。 这可以通过向页面添加筛选 LinkButton 来实现。 让我们向页面添加 27 个 LinkButton:一个标题为 All,并为字母表的每个字母添加一个 LinkButton。 如果访问者单击“所有链接”按钮,GridView 将显示所有用户。 如果他们单击特定字母,则只会显示用户名以所选字母开头的用户。
我们的第一个任务是添加 27 个 LinkButton 控件。 一种选择是以声明方式创建 27 个 LinkButton,一次创建一个。 一种更灵活的方法是将 Repeater 控件与 一起使用 ItemTemplate
,该控件呈现 LinkButton,然后将筛选选项作为 string
数组绑定到 Repeater。
首先,将 Repeater 控件添加到 GridView 上方的页面 UserAccounts
。 将 Repeater 的 ID
属性设置为 FilteringUI
。 配置 Repeater 的模板,使其 ItemTemplate
呈现一个 LinkButton,其 Text
和 CommandName
属性绑定到当前数组元素。 正如我们在向用户分配角色教程中看到的那样,这可以使用数据绑定语法来完成Container.DataItem
。 使用中继器在 SeparatorTemplate
各链接之间显示一条垂直线。
<asp:Repeater ID="FilteringUI" runat="server">
<ItemTemplate>
<asp:LinkButton runat="server" ID="lnkFilter"
Text='<%# Container.DataItem %>'
CommandName='<%# Container.DataItem %>'></asp:LinkButton>
</ItemTemplate>
<SeparatorTemplate>|</SeparatorTemplate>
</asp:Repeater>
若要使用所需的筛选选项填充此中继器,请创建名为 BindFilteringUI
的方法。 请确保在第一页加载时从 Page_Load
事件处理程序调用此方法。
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
BindUserAccounts();
BindFilteringUI();
}
}
private void BindFilteringUI()
{
string[] filterOptions = { "All", "A", "B", "C","D", "E", "F", "G", "H", "I","J", "K", "L", "M", "N", "O","P", "Q", "R", "S", "T", "U","V", "W", "X", "Y", "Z" };
FilteringUI.DataSource = filterOptions;
FilteringUI.DataBind();
}
此方法将筛选选项指定为数组 filterOptions
中的string
元素。 对于数组中的每个元素,Repeater 将呈现一个 LinkButton,其 Text
和 CommandName
属性分配给数组元素的值。
图 5 显示了 ManageUsers.aspx
通过浏览器查看时的页面。
图 5:Repeater Lists 27 筛选 LinkButtons (Click to view full size image)
注意
用户名可以以任何字符开头,包括数字和标点符号。 若要查看这些帐户,管理员必须使用“所有 LinkButton”选项。 或者,可以添加 LinkButton 以返回以数字开头的所有用户帐户。 我把这个留作读者的练习。
单击任何筛选 LinkButton 会导致回发并引发 Repeater 的事件 ItemCommand
,但网格中没有变化,因为我们尚未编写任何代码来筛选结果。 类 Membership
包含一个 FindUsersByName
方法 ,该方法返回用户名与指定搜索模式匹配的用户帐户。 我们可以使用此方法仅检索其用户名以单击的筛选 LinkButton 的 指定的字母开头 CommandName
的用户帐户。
首先更新 ManageUser.aspx
页面的代码隐藏类,使其包含名为 的属性 UsernameToMatch
。 此属性跨回发保留用户名筛选器字符串:
private string UsernameToMatch
{
get
{
object o = ViewState["UsernameToMatch"];
if (o == null)
return string.Empty;
else
return (string)o;
}
set
{
ViewState["UsernameToMatch"] = value;
}
}
属性 UsernameToMatch
使用键 UsernameToMatch 将其赋值存储到 ViewState
集合中。 读取此属性的值时,它会检查集合中 ViewState
是否存在值;否则,它将返回默认值(空字符串)。 属性 UsernameToMatch
呈现一种常见模式,即保留值以查看状态,以便跨回发保留对属性的任何更改。 有关此模式的详细信息,请阅读 了解 ASP.NET 视图状态。
接下来,更新 BindUserAccounts
方法,使其不调用 Membership.GetAllUsers
,而是调用 Membership.FindUsersByName
,传入随 SQL 通配符 %追加的属性的值 UsernameToMatch
。
private void BindUserAccounts()
{
UserAccounts.DataSource = Membership.FindUsersByName(this.UsernameToMatch + "%");
UserAccounts.DataBind();
}
若要仅显示用户名以字母 A 开头的用户,请将 UsernameToMatch
属性设置为 A,然后调用 BindUserAccounts
。 这将导致对 Membership.FindUsersByName("A%")
的调用,这将返回用户名以 A 开头的所有用户。同样,若要返回 所有用户 ,请向 UsernameToMatch
属性分配一个空字符串,以便 BindUserAccounts
方法调用 Membership.FindUsersByName("%")
,从而返回所有用户帐户。
为中继器 ItemCommand
的事件创建事件处理程序。 每当单击其中一个筛选器 LinkButton 时,将引发此事件;它通过 RepeaterCommandEventArgs
对象传递单击的 CommandName
LinkButton 的值。 我们需要将适当的值分配给 属性, UsernameToMatch
然后调用 BindUserAccounts
方法。 CommandName
如果 为 All,请向 UsernameToMatch
分配一个空字符串,以便显示所有用户帐户。 否则,请将 CommandName
值分配给 UsernameToMatch
。
protected void FilteringUI_ItemCommand(object source, RepeaterCommandEventArgs e)
{
if (e.CommandName == "All")
this.UsernameToMatch = string.Empty;
else
this.UsernameToMatch e.CommandName;
BindUserAccounts();
}
完成此代码后,测试筛选功能。 首次访问页面时,将显示所有用户帐户, (参考图 5) 。 单击 A LinkButton 会导致回发并筛选结果,仅显示以 A 开头的那些用户帐户。
图 6:使用筛选链接按钮显示其用户名以特定字母开头的用户 (单击以查看全尺寸图像)
步骤 4:更新 GridView 以使用分页
图 5 和图 6 中显示的 GridView 列出了从 FindUsersByName
方法返回的所有记录。 如果有数百或数千个用户帐户,这可能会导致在查看所有帐户时信息过载, (就像单击“所有链接按钮”或最初访问页面) 时一样。 为了帮助在更易于管理的区块中显示用户帐户,让我们将 GridView 配置为一次显示 10 个用户帐户。
GridView 控件提供两种类型的分页:
- 默认分页 - 易于实现,但效率低下。 简而言之,使用默认分页,GridView 需要来自其数据源 的所有 记录。 然后,它仅显示相应的记录页。
- 自定义分页 - 需要更多工作才能实现,但比默认分页更高效,因为使用自定义分页,数据源仅返回要显示的精确记录集。
在对数千条记录进行分页时,默认分页和自定义分页之间的性能差异可能很大。 由于我们正在构建此接口,假设可能有数百个或数千个用户帐户,因此让我们使用自定义分页。
注意
有关默认分页和自定义分页之间的差异以及实现自定义分页所涉及的挑战的更深入的讨论,请参阅 通过大量数据高效分页。
若要实现自定义分页,我们首先需要某种机制来检索 GridView 显示的记录的精确子集。 好消息是 Membership
类的 FindUsersByName
方法具有一个重载,它允许我们指定页面索引和页面大小,并且仅返回属于该记录范围的用户帐户。
具体而言,此重载具有以下签名: FindUsersByName(usernameToMatch, pageIndex, pageSize, totalRecords)
。
pageIndex 参数指定要返回的用户帐户页;pageSize 指示每页要显示的记录数。 totalRecords 参数是返回out
用户存储中用户帐户总数的参数。
注意
返回 FindUsersByName
的数据按用户名排序;无法自定义排序条件。
GridView 可以配置为利用自定义分页,但仅当绑定到 ObjectDataSource 控件时。 要使 ObjectDataSource 控件实现自定义分页,它需要两种方法:一种方法传递起始行索引和要显示的最大记录数,并返回属于该范围的记录的精确子集:和一个返回正在分页的记录总数的方法。 重 FindUsersByName
载接受页索引和页大小,并通过 out
参数返回记录总数。 因此,此处存在接口不匹配。
一种选择是创建一个代理类,该类公开 ObjectDataSource 所需的接口,然后在内部调用 FindUsersByName
方法。 另一个选项是创建我们自己的分页接口,而不是 GridView 的内置分页接口。
创建第一个、上一个、下一个、最后一个分页接口
让我们使用 First、Previous、Next 和 Last LinkButton 生成分页接口。 单击“第一个 LinkButton”会将用户带到数据的第一页,而“上一页”将返回上一页。 同样,“下一页”和“最后一页”将分别将用户移动到下一页和最后一页。 在 GridView 下添加四个 UserAccounts
LinkButton 控件。
<p>
<asp:LinkButton ID="lnkFirst" runat="server"> First</asp:LinkButton> |
<asp:LinkButton ID="lnkPrev" runat="server"> Prev</asp:LinkButton>|
<asp:LinkButton ID="lnkNext" runat="server">Next </asp:LinkButton>|
<asp:LinkButton ID="lnkLast" runat="server">Last </asp:LinkButton>
</p>
接下来,为每个 LinkButton Click
事件创建事件处理程序。
图 7 显示了通过 Visual Web 开发人员设计视图查看时的四个 LinkButton。
图 7:添加第一个、上一个、下一个和最后一个链接按钮在 GridView (单击以查看全尺寸图像)
跟踪当前页索引
当用户首次访问 ManageUsers.aspx
页面或单击其中一个筛选按钮时,我们希望在 GridView 中显示数据的第一页。 但是,当用户单击其中一个导航 LinkButton 时,我们需要更新页面索引。 若要维护页面索引和每页显示的记录数,请将以下两个属性添加到页面的代码隐藏类:
private int PageIndex
{
get
{
object o = ViewState["PageIndex"];
if (o == null)
return 0;
else
return (int)o;
}
set
{
ViewState["PageIndex"] = value;
}
}
private int PageSize
{
get
{
return 10;
}
}
与 属性一 UsernameToMatch
样,属性 PageIndex
会保留其值以查看状态。 只读 PageSize
属性返回硬编码值 10。 我邀请感兴趣的读者更新此属性,以使用与 PageIndex
相同的模式,然后扩充 ManageUsers.aspx
页面,以便访问页面的人员可以指定每页显示多少个用户帐户。
仅检索当前页的记录、更新页面索引以及启用和禁用分页接口 LinkButton
有了分页接口并 PageIndex
添加了 和 PageSize
属性,我们就可以更新 BindUserAccounts
方法,使其使用适当的 FindUsersByName
重载。 此外,我们需要根据显示的页面启用或禁用分页接口。 查看数据的第一页时,应禁用“第一个”和“上一个”链接;查看最后一页时,应禁用“下一页”和“最后一页”。
使用以下代码更新 BindUserAccounts
方法:
private void BindUserAccounts()
{
int totalRecords;
UserAccounts.DataSource = Membership.FindUsersByName(this.UsernameToMatch + "%",this.PageIndex, this.PageSize, out totalRecords);
UserAccounts.DataBind();
// Enable/disable the paging interface
bool visitingFirstPage = (this.PageIndex == 0);
lnkFirst.Enabled = !visitingFirstPage;
lnkPrev.Enabled = !visitingFirstPage;
int lastPageIndex = (totalRecords - 1) / this.PageSize;
bool visitingLastPage = (this.PageIndex >= lastPageIndex);
lnkNext.Enabled = !visitingLastPage;
lnkLast.Enabled = !visitingLastPage;
}
请注意,正在分页的记录总数由 方法的最后一 FindUsersByName
个参数确定。 这是一个out
参数,因此我们需要首先声明一个变量来保留此值 (totalRecords
) ,然后使用关键字 (keyword) 作为out
前缀。
返回用户帐户的指定页面后,将启用或禁用四个 LinkButton,具体取决于是查看数据的第一页还是最后一页。
最后一步是为四个 LinkButton 的 Click
事件处理程序编写代码。 这些事件处理程序需要更新 属性, PageIndex
然后通过调用 BindUserAccounts
将数据重新绑定到 GridView。 First、Previous 和 Next 事件处理程序非常简单。 Click
但是,Last LinkButton 的事件处理程序稍微复杂一些,因为我们需要确定要显示的记录数才能确定最后一个页面索引。
protected void lnkFirst_Click(object sender, EventArgs e)
{
this.PageIndex = 0;
BindUserAccounts();
}
protected void lnkPrev_Click(object sender, EventArgs e)
{
this.PageIndex -= 1;
BindUserAccounts();
}
protected void lnkNext_Click(object sender, EventArgs e)
{
this.PageIndex += 1;
BindUserAccounts();
}
protected void lnkLast_Click(object sender, EventArgs e)
{
// Determine the total number of records
int totalRecords;
Membership.FindUsersByName(this.UsernameToMatch + "%", this.PageIndex,this.PageSize, out totalRecords);
// Navigate to the last page index
this.PageIndex = (totalRecords - 1) / this.PageSize;
BindUserAccounts();
}
图 8 和图 9 显示了自定义分页接口的操作。 图 8 显示了 ManageUsers.aspx
查看所有用户帐户的第一页数据时的页面。 请注意,仅显示 13 个帐户中的 10 个。 单击“下一页”或“最后一个”链接会导致回发,将 更新 PageIndex
为 1,并将第二页用户帐户绑定到网格 (请参阅图 9) 。
图 8:显示前 10 个用户帐户 (单击以查看全尺寸图像)
图 9:单击“下一个链接”显示用户帐户的第二页 (单击以查看全尺寸图像)
总结
管理员通常需要从帐户列表中选择用户。 在前面的教程中,我们介绍了如何使用填充有用户的下拉列表,但此方法无法很好地缩放。 在本教程中,我们探索了一个更好的替代方法:一个可筛选的接口,其结果显示在分页的 GridView 中。 使用此用户界面,管理员可以在数千个用户帐户中快速高效地找到并选择一个用户帐户。
编程快乐!
深入阅读
有关本教程中讨论的主题的详细信息,请参阅以下资源:
关于作者
Scott Mitchell 是多本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自 1998 年以来一直从事 Microsoft Web 技术工作。 Scott 担任独立顾问、培训师和作家。 他的最新一本书是 山姆斯在 24 小时内 ASP.NET 2.0。 可以在 上联系 mitchell@4guysfromrolla.com 斯科特,也可以通过他的博客在 联系 http://ScottOnWriting.NET。
特别感谢
本教程系列由许多有用的审阅者审阅。 本教程的首席审阅者是 Alicja Maziarz。 有兴趣查看我即将发布的 MSDN 文章? 如果是这样,请在 mitchell@4GuysFromRolla.com