您的位置:首页 > 产品设计 > UI/UE

Security Tutorials系列第十二章:Building an Interface to Select One User Account from Many

2008-08-04 08:11 393 查看
本文英文原版及代码下载:http://www.asp.net/learn/security/tutorial-12-cs.aspx

Security Tutorials系列文章第十二章:Building an Interface to Select One User Account from Many



导言:

在前面的文章《Assigning Roles to Users》里,我们创建了一个比较粗糙的界面供管理员选择一个帐户并管理起角色信息。具体来说,该界面用下拉列表将所有的用户帐户列

出来,但系统的帐户不多的时候这是可行的,但当帐户很多的时候就不合适了,我们就应该提供一个分页的界面。

在本文,我们将构建这样的页面,具体来说,我们的页面有一系列的LinkButtons,将帐户名的起始字母作为过滤条件,而将符合条件的用户帐户显示在一个GridView控件里。不过

最开始我们将会在GridView里列出所有的用户帐户,然后在第3步,我们将添加过滤的LinkButtons.在第4步里对结果分页。我们在第2和第4步里构建的界面在后续的文章里会用到

,以实现对某个具体帐户的管理操作。

Step 1: Adding New ASP.NET Pages

在本文以及后续的2篇文章里,我们将考察各种与管理相关的函数和功能。我们需要创建一系列的页面来执行这些要考察的主题。

首先创建一个新的名为Administration的文件夹,再添加2个页面,运用模板页Site.master。如下:
.ManageUsers.aspx
.UserInformation.aspx

同时在根目录下添加2个页面:
.ChangePassword.aspx
.RecoverPassword.aspx.

此时,这4个页面因包含2个Content控件,对应的是模板页的ContentPlaceHolders控件:MainContent 和 LoginContent.

<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="LoginContent" Runat="Server">
</asp:Content>

我们想在这些页面你显示模板页的id为LoginContent的ContentPlaceHolder的默认标记,因此移除Content2 Content的声明代码,这样页面的标记里就只剩下一个Content控件了。

Administration文件夹里的ASP.NET页面是只允许管理员访问的,我们在文章《Creating and Managing Roles》里向系统添加了Administrators角色。要限制对这2个新页面的访问

的话,在Administration文件夹里添加一个Web.config配置文件,配置其<authorization>文件,只允许Administrators角色的用户访问,如下:

<?xml version="1.0"?>
<configuration>
<system.web>
<authorization>
<allow roles="Administrators" />
<deny users="*"/>
</authorization>
</system.web>
</configuration>

此时你的解决方案管理器看起来和下面的截屏差不多:





图1





最好更新站点地图Web.sitemap,添加如下的内容:
<siteMapNode title="User Administration" url="~/Administration/ManageUsers.aspx" />

更新完成后在浏览器里查看效果,如图2



图2



Step 2: Listing All User Accounts in a GridView



我们最开始将所有的用户帐户列出来,再添加过滤和分页的界面和功能。

打开Administration文件夹里的ManageUsers.aspx页面,添加一个GridView控件,设其id为UserAccounts,稍后我们将用Membership类的GetAllUsers方法返回的结果来对控件进行

绑定。就像在前面的文章里提到过的那样,GetAllUsers方法返回的是一个MembershipUserCollection对象,它有一个MembershipUser对象集合,每一个MembershipUser都有诸如

UserName, Email, IsApproved等的属性。

为了显示期望的用户帐户,我们将GridView的AutoGenerateColumns属性设置False,再添加UserName, Email,以及Comment属性对应的BoundFields;与IsApproved, IsLockedOut,

和 IsOnline属性对应的CheckBoxFields.当然你可以在控件的声明代码里写也可以通过Fields对话框来设置,如图3所示:



图3





完成后,确保声明代码和差不多:

<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

Step 3: Filtering the Results by the First Letter of the Username

目前,我们的UserAccounts GridView显示的是所有的用户帐户,对有几百几千帐户的站点来说不太适宜。为此,我在页面添加用于过滤的LinkButtons,具体来说,在页面添加27

个LinkButtons,第一个显示“All”,剩下的26一个对应一个英文字母,如果点击“All”则显示所有的用户帐户,如果点击某个具体的字母,则将以该字母开头的用户帐户显示出

来。

要添加27个LinkButton的话,我们可以一个一个的添加,不过另一种更高效的办法是添加一个Repeater控件,在其ItemTemplate里呈现一个LinkButton,再将过滤条件一个string

数组的形式绑定到Repeater.

首先在UserAccounts GridView控件上方添加一个Repeater,设其id为FilteringUI,配置Repeater的模板,这样其ItemTemplate里将呈现以LinkButton,其Text 和 CommandName属

性绑定到当前的数组元素,就像我们在文章《Assigning Roles to Users》你探讨的那样,我们可以用Container.DataItem绑定语法来实现,用Repeater的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>

为了将过滤条件绑定到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();
}

该方法在string数组filterOptions里指定了过滤的元素,对数组里的每一个元素,Repeater都将呈现为一个LinkButton,其Text 和 CommandName属性被赋值为数组元素的value.

图5显示的是在浏览器里登陆时的界面:







图5

注意:
Usernames是以任意字符开头的,包括数字和标点。为了显示这些帐户,管理员可以点击“All”来查看,或者,你可以再添加一个LinkButton来返回以数子开头的帐户,我将

这作为练习留给读者。

点击任意一个过滤条件按钮都将引发Repeater的ItemCommand事件,但此时没有变化,因为我们还没有写代码过滤结果。Membership类里有一个FindUsersByName method方法,根据

指定条件返回合乎要求的帐户,我们可以利用该方法来达到过滤的目的。

在ManageUser.aspx的后台代码里添加一个名为UsernameToMatch的属性,该属性在页面回传的时候保存username过滤条件字符串:

private string UsernameToMatch
{
get
{
object o = ViewState["UsernameToMatch"];
if (o == null)
return string.Empty;
else
return (string)o;
}
set
{
ViewState["UsernameToMatch"] = value;
}
}

当读取该属性的值时,它检查看在ViewState collection里是否存在一个值,如果不存在则返回默认值—空字符串;该UsernameToMatch属性示范了一个常用模式,也就是将值存储

在一个view state,在页面回传时,可以保存对属性的任何改动。对该模式的更多详情,请参与文章《Understanding ASP.NET View State》.

接下来,更新BindUserAccounts方法,不调用Membership.GetAllUsers而是调用Membership.FindUsersByName方法,将传入的UsernameToMatch属性值与SQL通配符%结合起来,如下



private void BindUserAccounts()
{
UserAccounts.DataSource = Membership.FindUsersByName(this.UsernameToMatch + "%");
UserAccounts.DataBind();
}

要展示所有以“A”开头的帐户,只需要将UsernameToMatch属性设为“A”,再调用BindUserAccounts方法,这将最终调用Membership.FindUsersByName("A%")方法,返回所有以“

A”开头的用户帐户。同意要点击所有的用户帐户,只需要将空字符串赋值给UsernameToMatch属性,那么BindUserAccounts方法将调用Membership.FindUsersByName("%"),从而返

回所有的用户帐户。

为Repeater的ItemCommand事件创建处理器。但任何时候任何一个过滤的LinkButtons被点击时都会触发该事件,它是通过RepeaterCommandEventArgs object,以被点击的

LinkButton的CommandName值来进行传递的.我们需要为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();
}

完成后来进行测试,初次登陆页面时将显示所有的用户,但点击“A”时,页面回传只显示那些以字母“A”开头的帐户.





图6



Step 4: Updating the GridView to Use Paging

如图5和6里的GridView所示,显示的索所有的合乎要求的记录,如果记录很多的时候那么查看帐户就很麻烦了(比如初次登陆时,或点击“All”的时候),为了更好的进行管理,我们来让GridView每次显示10条记录。

GridView控件提供了2种类型的分页:

.默认分页——贯彻容易但效率低下,GridView从数据源获取所有的记录,但只显示页面大小设定的那么多条记录.

.自定义分页——需要多做些工作但比默认分页高效,因为数据源只返回我们需要显示出来的的记录。

由于我们假定有很多的帐户,因此构架界面的时候我们使用自定义分页。
注意:
关于默认分页和自定义分页差异的更多探讨,以及自定义分页要面临的问题请参阅《Efficiently Paging Through Large Amounts of Data》,而对默认分页和自定义分页的效率差异的分析,请参阅《Custom Paging in ASP.NET with SQL Server 2005》



为完成自定义分页,我们首先需要机制来返回指定的显示在GridView里的记录,好消息是Membership类的FindUsersByName方法有一个重载,允许我们指定页面索引和页面大小,仅仅返回我们期望的记录。该重载的签名如下:
FindUsersByName(usernameToMatch, pageIndex, pageSize, totalRecords).

其中,pageIndex指定了页面索引,pageSize指定了每页显示的记录条数,而totalRecords是一个输出参数,返回用户帐户总数。

注意:
FindUsersByName返回的数据是根据username来排序的,不能自己指定排序标准。

我们可以配置GridView来使用自定义分页,但要绑定到一个ObjectDataSourc控件才行。对ObjectDataSource控件而言,要贯彻自定义分页的话,它要用到2个方法,一个接受2个参数,起始记录的索引,和每页最多显示的记录数,返回的是我们期望的那些记录;第2个方法返回的是总的记录条数。而我们的FindUsersByName重载方法接受的是页面索引参数和页面大小参数,用一个输出参数返回总记录数。因此有些不合时宜。

一种解决办法是创建一个代理类来实现ObjectDataSource控件所需的接口,再在内部调用FindUsersByName方法;另一种办法是创建我们自己的分页界面,而不用GridView内置的分页接口。

Creating a First, Previous, Next, Last Paging Interface

我们来创建一个有First, Previous, Next,和Last的LinkButtons的界面.但点击First LinkButton时候,将转到第一页,点Previous会转到上一页,点Next 和 Last将分别转到下一页和最后一页。在UserAccounts GridView下面添加4个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事件创建事件处理器。
下图显示的是在Visual Web Developer的Design模式里看到的界面












图7



Keeping Track of the Current Page Index

但我们第一次登陆ManageUsers.aspx页面,或点击某个过滤条件按钮时我们都希望GridView显示第一页的数据。但点击任何的导航按钮时,我们需要更新页面索引。为了维护页面索引和每页要显示的记录数,我们在页面的后台代码里添加如下的2个属性:

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属性将值存储在view state里。而只读的PageSize属性返回的是硬编码的值10,我希望有兴趣的读者更新该属性,就像PageIndex属性一样,但用户登陆该页面时可以指定每页显示多少条记录。

Retrieving Just the Current Page’s Records, Updating the Page Index, and Enabling and Disabling the Paging Interface LinkButtons

做了上述修改后我们要对BindUserAccounts方法进行改动,以调用相应的FindUsersByName重载。此外,我们还要启用或禁用页面的某些导航按钮,比如在访问第一页时要禁用First 和Previous按钮,而访问最末页时要禁用Next 和 Last按钮。

更新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),当然前缀是out关键字。

但返回指定页面的记录后,这4个按钮要么可用要么禁用,这取决于是否访问的第一或最末页。

最后要为在4个按钮的Click事件写事件处理器,这些事件处理器要对PageIndex属性进行更新,再通过BindUserAccounts方法绑定到GridView控件。First, Previous,和Next按钮的事件处理器都很简单,而Last按钮的就稍微复杂些,因为我们要判断总记录数进而判断最后一页的页面索引。

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条记录。点Next 或 Last按钮,将会产生页面回传,将PageIndex属性改为1,将第二页的数据绑定到控件.







图8









图9

结语:

管理员经常要从一系列帐户里选择一个帐户,在前面的文章里我们看到了使用一个下拉列表来展示用户帐户,但该方法不太好,在本文我们看到了另一个跟灵活的界面,将结果显示在一个分页的GridView控件里。有了该界面,管理员就可以快速而有效的从数千个帐户里选出一个帐户了。



祝编程愉快!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐