为用户指定角色

本文档是 Visual C# 教程 (转至 Visual Basic 教程 ) 。

在本教程中,我们要创建两个 ASP.NET 网页,用来管理哪些用户属于哪些角色。第一个网页所具备的功能是,可以查看某特定角色中都有哪些用户,或某特定用户属于哪些角色;它还具备为某一用户指定或免除某一角色的功能。在第二个网页上,我们放置一个CreateUserWizard 控件,它添加一个步骤,指定新创建的用户属于哪些角色。在管理员能够创建新用户帐户的场合,这是很有用的。

<<前一篇教程下一篇教程>>

简介

上一篇教程 中我们探讨了Roles framework 和SqlRoleProvider ,学习了怎样用Roles 类去创建、检索和删除角色。除了创建和删除角色之外,我们还必须能为一个用户指定或免除角色。遗憾的是,ASP.NET 并不提供任何Web 控件来管理哪些用户属于哪些角色。所以,我们必须创建自己的ASP.NET 网页来管理这些关系。好在向角色中添加和移除用户实现起来相当容易。Roles 类包含很多方法,向一个或多个角色添加一个或多个用户。

在本教程中,我们要创建两个 ASP.NET 网页,用来管理哪些用户属于哪些角色。第一个网页所具备的功能是,可以查看哪些用户属于某一特定角色,或某特定用户属于哪些角色;它还具备为某一用户指定或免除某一角色的功能。在第二个网页上,我们放置一个CreateUserWizard 控件,它添加一个步骤,指定新创建的用户属于哪些角色。在管理员能够创建新用户帐户的场合,这是很有用的。

让我们开始吧 !

列出哪些用户属于哪些角色

本教程的第一项工作,是创建一个向用户指定角色的网页。在我们考虑向用户指定角色之前,我们首先需要知道哪些用户属于哪些角色。有两种方式显示出这一信息:“按角色”或是“按用户”。我们可以让访问者选择一个角色,然后列出所有属于该角色的用户(“按角色”显示);也可以让访问者选择一个用户,然后显示指定给该用户的角色(“按用户”显示)。

当访问者想知道某一特定角色中都有哪些用户时,“按角色”显示正好适用;而当访问者想了解某一特定用户的角色时,则“按用户”显示可以满足需求。我们的网页上要包含“按角色”和“按用户”两种界面。

我们先建立“按用户”界面。该界面中将包括一个下拉框和一组复选框。下拉框中列出的是系统中的用户,而复选框则列出各个角色。从下拉框列表中选择一个用户后,将勾选该用户所属的角色复选框。然后,网页访问者可以通过在复选框勾选或去掉勾选,来为选定的用户指定或免除相应的角色。

注意 : 对于一个拥有大量用户帐户的网站来说,用一个下拉框来列出用户帐户不是一个很理想的方法。下拉框比较适宜让用户从一个相对较短的列表中选出一个选项。当列表中的选项数目增大时,它很快变得笨拙了。如果您所建立的网站可能拥有数目众多的用户帐户,您就可能需要考虑使用另外一种形式的用户界面了,比如一个可分页的GridView 或一个可过滤的界面,给出提示让访问者选择一个字母,然后仅列出以该字母打头的用户名。

步骤1 :建立“按用户”用户界面

打开 UsersAndRoles.aspx 网页。在这页的顶部,加入一个Label Web 控件,命名为 ActionStatus ,并清除其 Text 属性。我们要用该 Label 来反馈所实施的操作,显示诸如“已将用户Tito 添加到Administrators 角色”或“已将用户 Jisun 从 Supervisors 角色中移除”等信息。为了让这些信息更醒目,将该Label 控件的 CssClass 属性设为 “Important” 。

<p align="center"> 
     <asp:Label ID="ActionStatus" runat="server" CssClass="Important"></asp:Label> 
</p>

然后,添加下列 CSS 类定义到Styles.css 样式表中:

.Important 
{ 
     font-size: large; 
     color: Red; 
}

该 CSS 定义让浏览器在该 Label 控件中显示大号、红色字体。图 1 是在 Visual Studio Designer 中所看到的效果。

图 1 :设置 Label 的 CssClass 属性显示大号、红色字体(单击此处查看实际大小的图像

下一步,添加一个 DropDownList 到页面中,将其 ID 属性设为UserList ,并将 AutoPostBack 属性设为 True 。我们将用该DropDownList 列出系统中的所有用户。将将该 DropDownList 绑定到 MembershipUser 对象的一个集合。因为我们要用该 DropDownList 显示 MembershipUser 对象的UserName 属性(并用它作为列表项的值),所以将该DropDownList 的 DataTextField 属性和 DataValueField 属性都设为 “UserName” 。

在 DropDownList 的下面加上一个Repeater ,命名为 UsersRoleList 。在该Repeater 上,将用一组复选框列出系统中的所有角色。用下列声明标记定义该Repeater 的 ItemTemplate :

<asp:Repeater ID="UsersRoleList" runat="server"> 
     <ItemTemplate> 
          <asp:CheckBox runat="server" ID="RoleCheckBox" AutoPostBack="true" 
               Text='<%# Container.DataItem %>' /> 
          <br /> 
     </ItemTemplate> 
</asp:Repeater>

该 ItemTemplate 标记中包括了一个 CheckBox Web 控件,命名为 RoleCheckBox 。将该CheckBox 的 AutoPostBack 属性设为 True ,并将其 Text 属性绑定到 Container.DataItem 。其中数据绑定语句非常简单,仅为 Container.DataItem ,这是因为 Roles framework 用一个字符串数组返回角色名称列表,而我们正是将该字符串数组绑定到Repeater 上。至于为什么用这样的句法显示绑定到一个数据Web 控件的数组内容,对此的详细说明超出了本教程所要讨论的范畴。有关这方面的更多信息,请参阅Binding a Scalar Array to a Data Web Control

至此,“按用户”界面的声明标记应该如下所示:

<h3>Manage Roles By User</h3> 
<p> 
     <b>Select a User:</b> 
     <asp:DropDownList ID="UserList" runat="server" AutoPostBack="True" 
          DataTextField="UserName" DataValueField="UserName"> 
     </asp:DropDownList> 
</p> 
<p> 
     <asp:Repeater ID="UsersRoleList" runat="server"> 
          <ItemTemplate> 
               <asp:CheckBox runat="server" ID="RoleCheckBox" AutoPostBack="true" 
                    Text='<%# Container.DataItem %>' /> 
               <br /> 
          </ItemTemplate> 
     </asp:Repeater> 
</p>

现在,我们可以编写代码,将用户帐户集合绑定到DropDownList ,将角色集合绑定到 Repeater 。在网页的代码隐藏类(code-behind class )中,添加一个名为BindUsersToUserList 的方法和另一个名为 BindRolesList 的方法,使用以下代码:

private void BindUsersToUserList() 
{ 
     // Get all of the user accounts 
     MembershipUserCollection users = Membership.GetAllUsers(); 
     UserList.DataSource = users; 
     UserList.DataBind(); 
}
 
private void BindRolesToList() 
{ 
     // Get all of the roles 
     string[] roles = Roles.GetAllRoles(); 
     UsersRoleList.DataSource = roles; 
     UsersRoleList.DataBind(); 
}

BindUsersToUserList 方法通过Membership.GetAllUsers 方法 检索出系统中的所有用户。它返回一个MembershipUserCollection对象 ,是 MembershipUser 实例 的一个集合。然后将该集合绑定到 UserList DropDownList 。形成该集合的 MembershipUser 实例包含多个属性,如UserName 、Email 、CreationDate 和 IsOnline 。为了让我们的DropDownList 中显示 UserName 属性的值,要确保UserList DropDownList 的 DataTextField 和 DataValueField 属性都设置为 “UserName” 。

注意 :Membership.GetAllUsers 方法有两个重载:一个不接收输入参数返回所有用户,另一个接收页面索引和页面大小的整数值,仅返回一个特定的用户子集。当需要用分页的用户界面显示大量用户帐户时,用第二个重载可以更高效地翻页查看用户,因为返回的只是适合显示的用户帐户的子集,而不是全体用户。

BindRolesToList 方法首先调用 Roles 类的 GetAllRoles 方法 ,返回一个字符串数组,包含系统中的角色。然后将该字符串数组绑定到我们的Repeater 上。

最后一步,当第一次打开该网页时,我们需要调用这两个方法。 将以下代码添加到 Page_Load 事件处理程序中 :

protected void Page_Load(object sender, EventArgs e) 
{ 
     if (!Page.IsPostBack) 
     { 
          // Bind the users and roles 
          BindUsersToUserList(); 
          BindRolesToList(); 
     } 
}

放置好这段代码以后,我们打开浏览器来访问一下该页面,屏幕上的显示应该如图2 所示。所有的用户帐户都列在下拉框里,它的下面,对应每个角色有一个复选框。因为我们设置了DropDownList 和CheckBoxes 的 AutoPostBack 属性为True ,所以当改变所选择的用户,或当勾选一个角色或去掉勾选时,都应该引发一个回传。然而,并没有什么动作发生,这是因为我们还没有写代码来发出执行动作的指令。我们将在下面两节中完成这些任务。

图 2 :显示用户和角色的页面(单击此处查看实际大小的图像

勾选已选定用户所属的角色

当第一次调出页面时,或每当访问者从下拉框中选择一个新的用户时,我们都需要更新UsersRoleList 复选框,只勾选所选定用户所属的角色复选框。为做到这一点,使用以下代码创建一个名为CheckRolesForSelectedUser 的方法:

private void CheckRolesForSelectedUser() 
{ 
     // Determine what roles the selected user belongs to 
     string selectedUserName = UserList.SelectedValue; 
     string[] selectedUsersRoles = Roles.GetRolesForUser(selectedUserName); 
     // Loop through the Repeater's Items and check or uncheck the checkbox as needed 
     foreach (RepeaterItem ri in UsersRoleList.Items) 
     { 
          // Programmatically reference the CheckBox 
          CheckBox RoleCheckBox = ri.FindControl("RoleCheckBox") as CheckBox; 
          // See if RoleCheckBox.Text is in selectedUsersRoles 
          if (selectedUsersRoles.Contains<string>(RoleCheckBox.Text)) 
               RoleCheckBox.Checked = true; 
          else 
               RoleCheckBox.Checked = false; 
     } 
}

以上代码首先确定被选定的用户是谁。然后用Roles 类的 GetRolesForUser(userName)方法 返回选定用户的角色集合的一个字符串数组。然后,Repeater 的项便一一列举出来,而每一项的RoleCheckBox CheckBox 都通过程序决定是否勾选。只有当selectedUsersRoles 字符串数组中包含有相应的角色时,才勾选该角色的CheckBox 。

注意 : 如果使用的是 ASP.NET 2.0 版,则不编译 selectedUserRoles.Contains<string>(...) 语句。Contains<string> 方法是LINQ library 的一部分,是 ASP.NET 3.5 版的新功能。如果您仍是使用 ASP.NET 2.0 版,则要用 Array.IndexOf<string> 方法 替代。

在两种情况下需要调用 CheckRolesForSelectedUser 方法:当页面第一次调出时,或是每当UserList DropDownList 中的选定项改变时。所以,要在 Page_Load 事件处理程序中调用该方法(在调用 BindUsersToUserList 和BindRolesToList 之后)。此外,还要为 DropDownList 的SelectedIndexChanged 事件创建一个事件处理程序,从那里也调用该方法。

protected void Page_Load(object sender, EventArgs e) 
{ 
     if (!Page.IsPostBack) 
     { 
          // Bind the users and roles 
          BindUsersToUserList(); 
          BindRolesToList(); 
          // Check the selected user's roles 
          CheckRolesForSelectedUser(); 
     } 
} 
... 
protected void UserList_SelectedIndexChanged(object sender, EventArgs e) 
{ 
     CheckRolesForSelectedUser(); 
}

放置好这些代码之后,应该就可以在浏览器上检测网页了。但是,因为UsersAndRoles.aspx 页目前还没有向用户指定角色的功能,所以用户都还不具备角色。我们马上就将建立向用户指定角色的界面,所以您现在有两个选择:或是相信我,认为这些代码可以运行,以后再测试它;或是通过向aspnet_UsersInRoles 表中插入记录,手工地向用户指定角色,以便现在就检测该功能。

为用户指定和免除角色

当访问者在 UsersRoleList Repeater 上勾选一个CheckBox 或去掉勾选后,我们需要在相应的角色中添加或移除所选定的用户。由于CheckBox 的 AutoPostBack 属性目前设置为True ,所以每当 Repeater 上的一个 CheckBox 勾选或去掉勾选时,都将引发一个回传。简言之,我们需要为CheckBox 的 CheckChanged 事件创建一个事件处理程序。因为CheckBox 是在一个 Repeater 控件上,我们需要手工地加入事件处理程序的基础管道(plumbing )。首先,将事件处理程序作为一个受保护的方法加入到一个代码隐藏类中,如下所示:

protected void RoleCheckBox_CheckChanged(object sender, EventArgs e) 
{ 
}

一会儿我们还要回来写该事件处理程序的代码。但首先让我们先将该事件处理程序的基础管道建好。在我们的Repeater 的 ItemTemplate 的 CheckBox 里添加 OnCheckedChanged="RoleCheckBox_CheckChanged" 。这条语句将 RoleCheckBox_CheckChanged 事件处理程序与RoleCheckBox 的 CheckedChanged 事件相关联。

<asp:CheckBox runat="server" ID="RoleCheckBox" 
     AutoPostBack="true" 
     Text='<%# Container.DataItem %>' 
     OnCheckedChanged="RoleCheckBox_CheckChanged" />

我们最后一项任务是完成 RoleCheckBox_CheckChanged 事件处理程序。我们首先需要定位引起事件的CheckBox 控件,因为该 CheckBox 实例通过其 Text 和 Checked 属性告诉我们,哪个角色被勾选或去掉了勾选。使用该信息以及选定用户的UserName ,我们通过Roles 类的 AddUserToRoleRemoveUserFromRole 方法 从角色中添加或移除用户。

protected void RoleCheckBox_CheckChanged(object sender, EventArgs e) 
{ 
     // Reference the CheckBox that raised this event 
     CheckBox RoleCheckBox = sender as CheckBox; 
     // Get the currently selected user and role 
     string selectedUserName = UserList.SelectedValue; 
     string roleName = RoleCheckBox.Text; 
     // Determine if we need to add or remove the user from this role 
     if (RoleCheckBox.Checked) 
     { 
          // Add the user to the role 
          Roles.AddUserToRole(selectedUserName, roleName); 
          // Display a status message 
          ActionStatus.Text = string.Format("User {0} was added to role {1}.", selectedUserName, roleName); 
     } 
     else 
     { 
          // Remove the user from the role 
          Roles.RemoveUserFromRole(selectedUserName, roleName); 
          // Display a status message 
          ActionStatus.Text = string.Format("User {0} was removed from role {1}.", selectedUserName, roleName); 
     } 
}

以上代码首先用程序通过 sender 输入参数定位引发事件的CheckBox 。如果勾选了该 CheckBox ,则将选定用户加入到那个角色中,否则便将他从该角色中移除。无论哪种情况,ActionStatus Label 上都显示出刚刚实施的操作的信息。

让我们用点时间在浏览器上测试该页面。 选定用户Tito ,并将 Tito 加入到 Administrators 和Supervisors 两种角色中。

图 3 :将 Tito 添加到 Administrators 和 Supervisors 角色中(单击此处查看实际大小的图像

然后,在下拉框中选定用户 Bruce 。这引发一个回传,Repeater 上的复选框通过 CheckRolesForSelectedUser 获得更新。因为Bruce 不属于任何一个角色,所以两个复选框均未选中。下一步,为Bruce 指定 Supervisors 角色。

图 4 :将 Bruce 添加到 Supervisors 角色中(单击此处查看实际大小的图像

为进一步检测 CheckRolesForSelectedUser 方法的功能,再选择 Tito 和 Bruce 之外的其他用户。注意观察复选框是如何自动转为未选中状态的,表示这些用户不属于任何角色。重新再选Tito 。选中Administrators 和 Supervisors 两个复选框。

步骤2 :建立“ 按角色” 用户界面

目前,我们已经完成了“按用户”界面,可以开始着手建立“按角色”界面了。“按角色”界面提示用户从一个下拉框中选择一个角色,然后在一个GridView 中显示属于该角色的用户集合。

添加另一个 DropDownList 控件到 UsersAndRoles.aspx 页面上。将它放到 Repeater 控件的下面,命名为RoleList ,并设其 AutoPostBack 属性为 True 。在其下方,添加一个GridView 并命名为 RolesUserList 。该GridView 将列出属于选定角色的用户。设置该GridView 的 AutoGenerateColumns 属性为 False ,添加一个TemplateField 到该表格的 Columns 集合,并设其 HeaderText 属性为“Users” 。定义 TemplateField 的 ItemTemplate ,让它显示 Label 控件UserNameLabel 的 Text 属性中数据绑定表达式 Container.DataItem 的值。

在添加和设置好该 GridView 之后,我们所建立的“按角色”界面的声明标记应该如下所示:

<h3>Manage Users By Role</h3> 
<p> 
     <b>Select a Role:</b> 
     <asp:DropDownList ID="RoleList" runat="server" AutoPostBack="true"></asp:DropDownList> 
</p> 
<p>      <asp:GridView ID="RolesUserList" runat="server" AutoGenerateColumns="false" 
          EmptyDataText="No users belong to this role."> 
          <Columns> 
               <asp:TemplateField HeaderText="Users"> 
                    <ItemTemplate> 
                         <asp:Label runat="server" id="UserNameLabel" 
                              Text='<%# Container.DataItem %>'></asp:Label> 
                    </ItemTemplate> 
               </asp:TemplateField> 
          </Columns> 
     </asp:GridView> 
</p>

我们还需要将系统中的角色列到 RolesList DropDownList 里。为此,修改BindRolesToList 方法,让其绑定由 Roles.GetAllRoles 方法返回到RolesList DropDownList (以及 UsersRoleList Repeater )的字符串数组。

private void BindRolesToList() 
{ 
     // Get all of the roles 
     string[] roles = Roles.GetAllRoles(); 
     UsersRoleList.DataSource = roles; 
     UsersRoleList.DataBind(); 
     RoleList.DataSource = roles; 
     RoleList.DataBind(); 
}

BindRolesToList 方法中所加上的最后两行,绑定角色集合到RoleList DropDownList 控件。图 5 是在浏览器上看到的结果:在下拉框中列出了系统中的角色。

图 5 :在RoleList DropDownList 中显示角色(单击此处查看实际大小的图像

列出属于选定角色的用户

当页面第一次调出时,或每当用户在 RoleList DropDownList 中选定一个新的角色时,我们都需要在GridView 中列出属于该角色的用户。用以下代码创建一个名为DisplayUsersBelongingToRole 的方法:

private void DisplayUsersBelongingToRole() 
{ 
     // Get the selected role 
     string selectedRoleName = RoleList.SelectedValue; 
     // Get the list of usernames that belong to the role 
     string[] usersBelongingToRole = Roles.GetUsersInRole(selectedRoleName); 
     // Bind the list of users to the GridView 
     RolesUserList.DataSource = usersBelongingToRole; 
     RolesUserList.DataBind(); 
}

该方法首先从 RoleList DropDownList 中获得所选定的角色。然后使用 Roles.GetUsersInRole(roleName)方法 检索属于该角色的用户的 UserNames  字符串数组。然后绑定该数组到 RolesUserList GridView 。

在两种情况下需要调用该方法:当网页第一次调出时,以及在RoleList DropDownList 所选定的角色改变时。所以,要更新Page_Load 事件处理程序,在调用 CheckRolesForSelectedUser 之后调用该方法。然后,为RoleList 的 SelectedIndexChanged 事件创建一个事件处理程序,并从中也调用该方法。

protected void Page_Load(object sender, EventArgs e) 
{ 
     if (!Page.IsPostBack) 
     { 
          // Bind the users and roles 
          BindUsersToUserList(); 
          BindRolesToList(); 
          // Check the selected user's roles 
          CheckRolesForSelectedUser(); 
          // Display those users belonging to the currently selected role 
          DisplayUsersBelongingToRole(); 
     } 
} 
... 
protected void RoleList_SelectedIndexChanged(object sender, EventArgs e) 
{ 
     DisplayUsersBelongingToRole(); 
}

写好这些代码之后,RolesUserList GridView 里面就应该列出那些属于选定角色的用户。如图6 所示,Supervisors 角色包含两个成员:Bruce 和 Tito 。

图 6 :GridView 中列出属于选定角色的用户(单击此外查看实际大小的图像

从选定角色中移除用户

我们在 RolesUserList GridView 中增加一个包含 Remove 按钮的列。单击某一用户旁的Remove 按钮,将将该用户从角色中移除。

先添加一个 Delete 按钮列到GridView 中。让该列成为最左侧的一列,并改变其DeleteText 属性,从 “Delete” (默认值)改为 “Remove” 。

图 7 :添加 Remove 按钮到 GridView 中(单击此处查看实际大小的图像

当单击 Remove 按钮后,将引发一个回传,触发GridView 的 RowDeleting 事件。我们需要为该事件创建一个事件处理程序,编写代码从选定角色中移除该用户。创建该事件处理程序并加入以下代码:

protected void RolesUserList_RowDeleting(object sender, GridViewDeleteEventArgs e) 
{ 
     // Get the selected role 
     string selectedRoleName = RoleList.SelectedValue; 
     // Reference the UserNameLabel 
     Label UserNameLabel = RolesUserList.Rows[e.RowIndex].FindControl("UserNameLabel") as Label; 
     // Remove the user from the role 
     Roles.RemoveUserFromRole(UserNameLabel.Text, selectedRoleName); 
     // Refresh the GridView 
     DisplayUsersBelongingToRole(); 
     // Display a status message 
     ActionStatus.Text = string.Format("User {0} was removed from role {1}.", UserNameLabel.Text, selectedRoleName); 
}

代码首先确定选定的角色名称。然后通过程序定位单击Remove 的那一行的UserNameLabel 控件,以确定待移除用户的 UserName 。然后,通过调用 Roles.RemoveUserFromRole 方法将该用户从该角色中移除。随后 RolesUserList GridView 将刷新,并在 ActionStatus Label 控件上显示相关信息。

注意 : 单击 Remove 按钮后,在将用户从角色中移除之前并没有要求任何用户确认。我建议您最好还是加上一定级别的用户确认。最简易的一个确认操作的方法是通过一个客户端对话框。有关这项技术的更多信息,参阅《删除时添加客户端确认 》。

图 8 显示了将用户 Tito 从 Supervisors 角色成员组中移除后的页面。

图 8 :唉,Tito 不再是Supervisor 了(单击此处查看实际大小的图像

向选定角色中添加新用户

除了从选定角色中移除用户之外,访问本页的用户还应该能够向选定角色中添加用户。向选定角色中添加用户的界面是否适宜,取决于用户帐户的数量。如果您的站点只拥有几十个或更少的用户帐户,在这里用一个DropDownList 就可以了。然而,如果有几千个用户帐户的话,就可能需要在界面上允许访问者翻页查看帐户,搜索某一特定帐户,或用某些其它方式过滤用户帐户。

在我们该页面上,我们用一个非常简单的界面,不考虑系统的用户帐户有多少。在这里我们用一个TextBox ,让访问者在其中键入他想要添加到选定角色中的用户名。如果没有该用户名,或该用户已经是该角色组的成员,我们则用一个ActionStatus Label 显示相关信息。反之,如果确实有该用户,并且他也不是该角色的成员,我们就将他添加到该角色中,并刷新表格。

在 GridView 的下面加入一个TextBox 和一个 Button 。设置 TextBox 的 ID 为 UserNameToAddToRole ,分别设置 Button 的 ID 和 Text 属性为AddUserToRoleButton 和 “Add User to Role” 。

<p> 
     <b>UserName:</b> 
     <asp:TextBox ID="UserNameToAddToRole" runat="server"></asp:TextBox> 
     <br /> 
     <asp:Button ID="AddUserToRoleButton" runat="server" Text="Add User to Role" /> 
</p>

然后,为 AddUserToRoleButton 创建一个 Click 事件处理程序,并加入下列代码:

protected void AddUserToRoleButton_Click(object sender, EventArgs e) 
{ 
     // Get the selected role and username 
     string selectedRoleName = RoleList.SelectedValue; 
     string userNameToAddToRole = UserNameToAddToRole.Text; 
     // Make sure that a value was entered 
     if (userNameToAddToRole.Trim().Length == 0) 
     { 
          ActionStatus.Text = "You must enter a username in the textbox."; 
          return; 
     } 
     // Make sure that the user exists in the system 
     MembershipUser userInfo = Membership.GetUser(userNameToAddToRole); 
     if (userInfo == null) 
     { 
          ActionStatus.Text = string.Format("The user {0} does not exist in the system.", userNameToAddToRole); 
          return; 
     } 
     // Make sure that the user doesn't already belong to this role 
     if (Roles.IsUserInRole(userNameToAddToRole, selectedRoleName)) 
     { 
          ActionStatus.Text = string.Format("User {0} already is a member of role {1}.", userNameToAddToRole, selectedRoleName); 
          return; 
     } 
     // If we reach here, we need to add the user to the role 
     Roles.AddUserToRole(userNameToAddToRole, selectedRoleName); 
     // Clear out the TextBox 
     UserNameToAddToRole.Text = string.Empty; 
     // Refresh the GridView 
     DisplayUsersBelongingToRole(); 
     // Display a status message 
     ActionStatus.Text = string.Format("User {0} was added to role {1}.", userNameToAddToRole, selectedRoleName); 
}

该 Click 事件处理程序中的代码主要是执行各种验证。它要确保访问者在UserNameToAddToRole TextBox 中输入的用户名在系统中确实存在,并且它目前不属于选定的角色。如果任一验证没有通过,则将相应信息显示在ActionStatus 上,并退出该事件处理程序。如果这些验证都通过了,则用Roles.AddUserToRole 方法将用户添加到角色中。其后,TextBox 的 Text 属性被清空,GridView 刷新,并在 ActionStatus Label 上显示已将指定用户成功地添加到选定角色中的相关信息。

注意 为确保指定的用户目前不属于选定角色,我们使用Roles.IsUserInRole(userName, roleName) 方法 ,它返回一个布尔值,表明userName 是否是roleName 的一个成员。在下一篇教程 中讨论基于角色的授权时,我们仍会使用该方法。

打开浏览器访问该页面,并从 RoleList DropDownList 中选择 Supervisors 角色。试着输入一个不正确的用户名,您会看到一条信息,说明系统中没有该用户。

图 9 :不能将不存在的用户添加到角色中(单击此处查看实际大小的图像

现在,再试着输入一个正确的用户。重新将Tito 添加到Supervisors 角色中。

图 10 :Tito 又成为Supervisor 了!(单击此处查看实际大小的图像

步骤3 :交互更新“ 按用户” 和“ 按角色” 界面

UsersAndRoles.aspx 网页提供了两个不同的界面来管理用户和角色。目前,这两个界面是单独工作的,所以在一个界面上所做的改变并不会立即反映到另一个界面上。例如,设想页面访问者先从RoleList DropDownList 中选择了 Supervisors 角色,它列出 Bruce 和 Tito 是它的成员。然后,访问者又在UserList DropDownList 中选择了 Tito ,看到 UsersRoleList Repeater 上的 Administrators 和 Supervisors 复选框都已勾选。之后,访问者又在该Repeater 上去掉Supervisor 角色的勾选,将 Tito 从 Supervisors 角色中移除,但该修改并没有反映在“按角色”界面中。GridView 中显示 Tito 仍然是 Supervisors 角色的成员。

为解决该问题,我们需要在 UsersRoleList Repeater 上的角色勾选或去掉勾选时,都刷新 GridView 。同样,每当在“按角色”界面上为一个角色移除或添加了一个用户时,Repeater 也需要刷新。

刷新“按用户”界面上的 Repeater ,是通过调用CheckRolesForSelectedUser 方法。这需要修改“按角色”界面,即修改RolesUserList GridView 的 RowDeleting 事件处理程序和AddUserToRoleButton 按钮的 Click 事件处理程序。因此,在这些方法中我们都需要调用CheckRolesForSelectedUser 方法。

protected void RolesUserList_RowDeleting(object sender, GridViewDeleteEventArgs e) 
{ 
     ... Code removed for brevity ... 
     // Refresh the "by user" interface 
     CheckRolesForSelectedUser(); 
} 
protected void AddUserToRoleButton_Click(object sender, EventArgs e) 
{ 
     ... Code removed for brevity ... 
     // Refresh the "by user" interface 
     CheckRolesForSelectedUser(); 
}

与上面类似,“按角色”界面上的 GridView 的刷新,是通过调用 DisplayUsersBelongingToRole 方法,这需要修改“按用户”界面的RoleCheckBox_CheckChanged 事件处理程序。因此,我们需要在该事件处理程序中调用DisplayUsersBelongingToRole 方法。

protected void RoleCheckBox_CheckChanged(object sender, EventArgs e) 
{ 
     ... Code removed for brevity... 
     // Refresh the "by role" interface 
     DisplayUsersBelongingToRole(); 
}

做了这些微小的代码修改之后,“按用户”和“按角色”界面现在就可以正确地交互更新了。为证实这一点,打开浏览器访问本页,从UserList 和 RoleList 下拉框中分别选择 Tito 和 Supervisors 。注意到,当您在“按用户”界面的 Repeater 上去掉 Tito 的Supervisors 角色的勾选时,在“按角色”界面上的GridView 里,Tito 自动地被移除了。在“按角色”界面上将Tito 加回到 Supervisors 角色中,则“按用户”界面上的 Supervisors 复选框又自动重新被勾选。

步骤4 :定制CreateUserWizard ,加入一个“指定角色”步骤

在《创建用户帐户 》教程中,我们学习了如何使用CreateUserWizard Web 控件来提供一个界面,创建新的用户帐户。可以有两种方式使用CreateUserWizard 控件:

  • 作为访问者的工具,创建他们自己在该网站上的用户帐户。
  • 作为管理员的工具,创建新的用户帐户。

在第一种情况下,一个访问者来到网站,利用CreateUserWizard 输入他们自己的信息,在网站上注册。第二种情况是管理员为别人创建一个新的帐户。

当管理员为别人创建一个新帐户时,让管理员同时为该新用户指定他所属的角色是一件很有意义的事。在《存储其它用户信息 》教程中,我们学习了怎样通过加入附加的向导步骤定制CreateUserWizard 。现在让我们看一下如何向CreateUserWizard 中加入附加的步骤,以便为新用户指定角色。

打开 CreateUserWizardWithRoles.aspx 页,并添加一个名为 RegisterUserWithRoles 的 CreateUserWizard 控件。设置该控件的 ContinueDestinationPageUrl 属性为 “~/Default.aspx” 。因为这里是想让管理员使用该 CreateUserWizard 控件创建新的用户帐户,所以设置控件的LoginCreatedUser 属性为 False 。该LoginCreatedUser 属性决定,是否让一个访问者登录上来就自动成为一个刚刚创建的用户,而其默认值是True 。我们设它为 False 是因为当一个管理员创建新帐户时,我们想让他以他自己的身份登录。

下面,从 CreateUserWizard 的智能标记中选择 “Add/Remove WizardSteps…” 选项,并加入一个新的 WizardStep ,设其 ID 为 “SpecifyRolesStep” 。移动 “SpecifyRolesStep” 向导步骤,让它排在 “Sign Up for Your New Account” 步骤之后,”Complete” 步骤之前。设置 WizardStep 的 Title 属性为 “Specify Roles” ,其StepType 属性为Step ,而其 AllowReturn 属性为 False 。

图 11 :添加“Specify Roles” 向导步骤到 CreateUserWizard (单击此处查看实际大小的图像

做完这些设置后,您的 CreateUserWizard 的声明标记应该如下所示:

<asp:CreateUserWizard ID="RegisterUserWithRoles" runat="server" 
     ContinueDestinationPageUrl="~/Default.aspx" LoginCreatedUser="False"> 
     <WizardSteps> 
          <asp:CreateUserWizardStep ID="CreateUserWizardStep1" runat="server"> 
          </asp:CreateUserWizardStep> 
          <asp:WizardStep ID="SpecifyRolesStep" runat="server" StepType="Step" 
               Title="Specify Roles" AllowReturn="False"> 
          </asp:WizardStep> 
          <asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server"> 
          </asp:CompleteWizardStep> 
     </WizardSteps> 
</asp:CreateUserWizard>

在 “Specify Roles” 向导步骤中,添加一个名为 RoleList 的CheckBoxList 。该 CheckBoxList 将列出所有的角色,让页面访问者通过勾选确认新创建的用户属于哪个角色。

我们还有两项编写代码的任务:一是我们必须将系统中的角色列在RoleList CheckBoxList 里;二是当用户从 “Specify Roles” 步骤走到 “Complete” 步骤时,我们需要向新创建的用户指定所选定的角色。我们在Page_Load 事件处理程序中执行第一项任务。下列代码通过程序在第一次调出页面时定位RoleList CheckBox ,并将系统中的角色绑定到它上面。

protected void Page_Load(object sender, EventArgs e) 
{ 
     if (!Page.IsPostBack) 
     { 
          // Reference the SpecifyRolesStep WizardStep 
          WizardStep SpecifyRolesStep = RegisterUserWithRoles.FindControl("SpecifyRolesStep") as WizardStep; 
          // Reference the RoleList CheckBoxList 
          CheckBoxList RoleList = SpecifyRolesStep.FindControl("RoleList") as CheckBoxList; 
          // Bind the set of roles to RoleList 
          RoleList.DataSource = Roles.GetAllRoles(); 
          RoleList.DataBind(); 
     } 
}

这些代码看上去很眼熟吧。在《存储其它用户信息 》教程中,我们曾使用两个FindControl 语句在一个定制的向导步骤中定位一个Web 控件。另外绑定角色到CheckBoxList 的语句在这部教程的前面也曾使用过。

为了执行第二项编程任务,我们必须知道“Specify Roles” 步骤何时完成了。回想到 CreateUserWizard 中有一个 ActiveStepChanged 事件,每当访问者从一个步骤移到下一步骤时,这一事件便激发。使用它,在这里我们就能够知道用户是否已经到达了“Complete” 步骤;一旦到达,我们就为用户指定所选定的角色。

为ActiveStepChanged 事件创建一个事件处理程序 , 并添加以下代码 :

protected void RegisterUserWithRoles_ActiveStepChanged(object sender, EventArgs e) 
{ 
     // Have we JUST reached the Complete step? 
     if (RegisterUserWithRoles.ActiveStep.Title == "Complete") 
     { 
          // Reference the SpecifyRolesStep WizardStep 
          WizardStep SpecifyRolesStep = RegisterUserWithRoles.FindControl("SpecifyRolesStep") as WizardStep; 
          // Reference the RoleList CheckBoxList 
          CheckBoxList RoleList = SpecifyRolesStep.FindControl("RoleList") as CheckBoxList; 
          // Add the checked roles to the just-added user 
          foreach (ListItem li in RoleList.Items) 
          { 
               if (li.Selected) 
                    Roles.AddUserToRole(RegisterUserWithRoles.UserName, li.Text); 
          } 
     } 
}

当用户刚一到达 “Completed” 步骤,该事件处理程序就在RoleList CheckBoxList 中列出各个角色,并将选定的角色指定给该新创建的用户。

打开浏览器访问此页面。CreateUserWizard 的第一步是标准的 “Sign Up for Your New Account” 步骤,要求输入新用户的用户名、密码、邮箱和其他关键信息。我们在这里输入信息,创建一个名为Wanda 的新用户。

图 12 :创建一个名为 Wanda 的新用户(单击此处查看实际大小的图像

单击 Create User 按钮。CreateUserWizard 在内部调用 Membership.CreateUser 方法,创建一个新的用户帐户,然后移到下一步骤“Specify Roles” 。系统中的角色列了出来。勾选 Supervisors 复选框并单击 Next 。

图 13 :让 Wanda 成为 Supervisors 角色的成员(单击此处查看实际大小的图像

单击 Next 引发了一个回传,并将ActiveStep 更新为 “Complete” 步骤。这样,ActiveStepChanged 事件处理程序就向新建的用户帐户指定了Supervisors 角色。为验证这一点,回到 UsersAndRoles.aspx 页面,并从 RoleList DropDownList 中选择 Supervisors 。如图 14 所示,Supervisors 目前已由三个用户组成:Bruce 、Tito 和 Wanda 。

图 14 :Bruce 、Tito 和 Wanda 都是 Supervisors (单击此处查看实际大小的图像

小结

Roles framework 所提供的的方法,可以检索出某一特定用户所属的角色,也可以列出某一特定角色所包含的用户。此外,还有很多方法,可用来在一个或多个角色中添加和移除一个或多个用户。在本教程中,我们只关注到这些方法中的两个:AddUserToRole 和 RemoveUserFromRole 。另外还有一些变异的方法,设计为用来向多个用户指定单个角色,或向单个用户指定多个角色。

本教程还讨论了如何扩展 CreateUserWizard 控件,使其包含一个向导步骤,向新建的用户指定角色。这样一个步骤能够帮助管理员精简为新用户创建帐户的过程。

目前,我们已经知道如何创建和删除角色,以及如何从角色中添加和移除用户。但是,我们还必须关注基于角色的授权。在下一篇教程 中,我们将探讨如何基于各个角色,定义URL 授权规则,以及如何基于目前登录用户的角色,限制页面上的功能。

快乐编程!

 

下一篇教程