创建用户帐户

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

在本教程中,我们将探讨使用 Membership 框架(通过SqlMembershipProvider )来创建新的用户帐户。我们将了解如何通过编码以及通过ASP.NET 的内置CreateUserWizard 控件来创建新用户。

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

简介

前一篇教程中,我们在数据库中安装了应用服务架构(application services schema) ,从而添加了SqlMembershipProvider 和SqlRoleProvider 所需的表、视图和存储过程。这创建了我们在本系列教程的后续教程中所需的基础架构。在本教程中,我们将探讨使用Membership 框架(通过SqlMembershipProvider )来创建新的用户帐户。我们将了解如何通过编码以及通过ASP.NET 的内置CreateUserWizard 控件来创建新用户。

除了学习如何创建新用户帐户外,还将对我们在《 表单身份验证概述 》教程中创建、在《表单身份验证配置和高级主题 》教程中增强的演示网站进行改动。我们的演示web 应用有一个登录页面,在此处对照硬性赋值的用户名/ 密码对,验证用户的凭据。此外,Global.asax 包含了为已验证身份的用户创建定制IPrincipal 和IIdentity 对象的代码。我们将对登录页面进行改动,使用Membership 框架来验证用户的凭据并删除定制的principal 和identity 逻辑。

让我们开始吧 !

表单身份验证和Membership 检查表

在使用 Membership 框架之前,我们先花点时间来回顾一下前面几篇教程的重要步骤。在基于表单身份验证的情景下使用具有SqlMembershipProvider 的 Membership 框架时,在 web 应用中实现Membership 功能之前,需要先执行下面的步骤:

  1. 启用基于表单的身份验证。正如我们在《表单身份验证概述》中所讨论的,通过编辑Web.config 并设置<authentication> 元素的mode 属性为Forms 来启用表单身份验证。启用表单身份验证后,对每个进入请求检查表单身份验证票证,如果有票证,则确定请求者的身份。
  2. 在适当的数据库中添加应用服务架构。使用SqlMembershipProvider 时,我们需要在数据库中安装应用服务架构。通常将该架构添加到保存应用数据模型的那个数据库中。 在《在SQL Server 中创建Membership Schema》 教程中,我们探讨了使用aspnet_regsql.exe 工具来完成此工作。
  3. 定制Web应用的设置以使用步骤2中的数据库。在 SQL Server 中创建 Membership Schema》教程给出了两种方法来配置 web 应用以使 SqlMembershipProvider 使用步骤 2 中选择的数据库:通过修改 LocalSqlServer 连接字符串名称;或在 Membership 框架提供者列表中添加新注册的提供者,并定义该提供者使用步骤2 中的数据库。

当构建使用 SqlMembershipProvider 和基于表单身份验证的 web 应用时,在使用 Membership 类或 ASP.NET Login Web 控件之前,需要执行以上三个步骤。由于我们在前面的教程中已经执行了这些步骤,现在就可以使用Membership 框架了!

步骤1 :添加新的ASP.NET 页面

在本教程及后面三个教程中,我们将探讨各种与Membership 有关的函数和功能。我们需要一系列 ASP.NET 页面来实现这些教程中探讨的主题。让我们来创建这些页面,然后创建一个站点地图文件(Web.sitemap) 。

首先 , 在项目中创建一个名为 Membership 的新文件夹。 然后,在Membership 文件夹中添加 5 个 ASP.NET 页面,每个页面都与 Site.master 母版页相关联。对页面进行命名:

  • CreatingUserAccounts.aspx
  • UserBasedAuthorization.aspx
  • EnhancedCreateUserWizard.aspx
  • AdditionalUserInfo.aspx
  • Guestbook.aspx

此时,项目的解决方案资源管理器看起来应与图1 所示的截屏类似。

图1 :在 Membership 文件夹中添加了 5 个新页面(单击此处查看实际大小的图像

此时,每个页面应该有两个 Content 控件,对应于母版页的两个 ContentPlaceHolder :MainContent 和 LoginContent 。

 

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

想一想,根据用户是否已验证身份,LoginContent ContentPlaceHolder 的默认标记将显示登录或注销网站的链接。然而,Content2 Content 控件的出现覆盖了母版页的默认标记。正如我们在《表单身份验证概述 》教程中所讨论的,如果我们不希望在页面左栏显示与登录有关的选项,这样做就很有用。

不过对于这 5 个页面,我们希望显示母版页 LoginContent ContentPlaceHolder 的默认标记。因此,删除 Content2 Content 控件的声明标记。完成后,这 5 个页面的标记应该只包含一个 Content 控件。

步骤2 :创建站点地图

大多数普通网站差不多都需要实现导航形式的用户界面。导航用户界面可以是一个链接到网站各部分的简单列表。也可以用菜单或树型结构来排列这些链接。作为页面开发人员,创建导航用户界面只完成了任务的一半。我们还需以可维护和可更新的方式来定义网站的逻辑结构。当添加新页面或删除已有页面时,我们希望只更新一个源– 站点地图 – 然后就可以在网站的导航用户界面反映这些变动。

这两个任务(定义站点地图和基于站点地图实现导航用户界面)很容易完成,因为ASP.NET 2.0 版本中新增了 Site Map 框架和 Web 导航控件。Site Map 框架使得开发人员可以定义一个站点地图,然后通过编程API (SiteMap 类 )访问。内置的 Web 导航控件包括Menu 控件 、TreeView 控件 和 SiteMapPath 控件 。

与 Membership 和角色框架一样,Site Map 框架构建在提供者模式 之上。Site Map 提供者类的任务是从永久数据存储(如XML 文件或数据库表)中生成 SiteMap 类使用的内存结构。.NET 框架有一个默认的 Site Map 提供者,它从 XML 文件 (XmlSiteMapProvider) 中读取站点地图数据。这也是我们在本教程中使用的提供者。有关其它的Site Map 提供者实现方法,请参考本教程末尾处的更多阅读部分。

默认的 Site Map 提供者需要根目录下有一个名为 Web.sitemap 的格式正确的 XML 文件。由于我们要使用该默认提供者,需要添加这样一个文件,以适当的XML 格式定义站点地图的结构。要添加文件,在Solution Explorer 中右键单击项目名称,然后选择 Add New Item 。在该对话框中,选择添加 Site Map 类型的名为 Web.sitemap 的文件。

图2 :在项目的根目录下添加名为 Web.sitemap 的文件(单击此处查看实际大小的图像

该 XML 站点地图文件将网站的结构定义为层次结构。该层次结构关系在XML 文件中通过顶层 <siteMapNode> 元素来建模。Web.sitemap 必须以一个 <siteMap> 父节点开始,节点中正好有一个 <siteMapNode> 子节点。该顶层 <siteMapNode> 元素代表了层次结构的根节点,它可以有任意多个派生节点。每个<siteMapNode> 元素必须包含一个 title 属性,可以选择性地包含 url 和 description 属性以及其它属性;但每个非空的 url 属性必须是唯一的。

在 Web.sitemap 文件中输入如下的 XML 标记:

 

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="https://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
     <siteMapNode url="~/Default.aspx" title="Home">
          <siteMapNode title="Membership">
               <siteMapNode url="~/Membership/CreatingUserAccounts.aspx" title="Creating User Accounts" />
               <siteMapNode url="~/Membership/UserBasedAuthorization.aspx" title="User-Based Authorization" />
               <siteMapNode url="~/Membership/Guestbook.aspx" title="Storing Additional User Information" />
          </siteMapNode>
     </siteMapNode>
</siteMap>

上述站点地图标记定义的层次结构如图3 所示。

图3 : 站点地图表示分层的导航结构 (单击此处查看实际大小的图像

步骤3 :更新母版页以包含导航用户界面

ASP.NET 包含一些用于设计用户界面的与导航有关的Web 控件。包括 Menu 、TreeView 和 SiteMapPath 控件。Menu 和 TreeView 控件分别以菜单和树形结构的形式呈现站点地图结构。而SiteMapPath 显示一个痕迹导航 (breadcrumb) ,显示正被访问的节点及其祖先节点。可以使用SiteMapDataSource 将站点地图数据绑定到其它 Web 控件上,然后通过 SiteMap 类以编码的方式访问这些数据。

由于对 Site Map 框架和导航控件的深入探讨不在本系列教程的范畴内,所以不用花时间精心构造自己的导航用户界面,让我们借用ASP.NET 2.0 中的数据处理 教程系列中的界面,它使用一个 Repeater 控件来展示两级导航链接列表,如图 4 所示。

在左栏添加一个两级链接列表

要创建该界面,请在 Site.master 母版页的左栏,也就是文本 “TODO:Menu will go here...” 所在的地方添加如下的声明标记。

<ul>
     <li>
          <asp:HyperLink runat="server" ID="lnkHome" NavigateUrl="~/Default.aspx">Home</asp:HyperLink>
     </li>
     <asp:Repeater runat="server" ID="menu" DataSourceID="SiteMapDataSource1">
          <ItemTemplate>
               <li>
                    <asp:HyperLink ID="lnkMenuItem" runat="server" 
                         NavigateUrl='<%# Eval("Url") %>'><%# Eval("Title") %></asp:HyperLink>
                    <asp:Repeater ID="submenu" runat="server" DataSource="<%#
                         ((SiteMapNode) Container.DataItem).ChildNodes %>">
                         <HeaderTemplate>
                              <ul>
                         </HeaderTemplate>
                         <ItemTemplate>
                              <li>
                                   <asp:HyperLink ID="lnkMenuItem" runat="server" NavigateUrl='<%#
                                        Eval("Url") %>'><%# Eval("Title") %></asp:HyperLink>
                              </li>
                         </ItemTemplate>
                         <FooterTemplate>
                              </ul>
                         </FooterTemplate>
                    </asp:Repeater>
               </li>
          </ItemTemplate>
     </asp:Repeater>
</ul>
    
<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" ShowStartingNode="false" />

上述标记将一个名为 menu 的Repeater 控件绑定到一个 SiteMapDataSource 控件(该控件返回的是 Web.sitemap 中定义的站点地图层次结构)。由于 SiteMapDataSource 控件的 ShowStartingNode 属性 设置为 False ,因此它返回的站点地图层次结构是以“Home” 节点的派生节点开始的。该 Repeater 控件将每个节点(目前就只有 “Membership” )显示在一个 <li> 元素中。此外,在 Repeater 内部,还将当前节点的子节点也显示在一个嵌套的无序列表中。

图 4 显示了上述标记对我们在步骤 2 中创建的站点地图结构进行呈现的效果。Repeater 控件呈现的是普通的无序列表标记;Styles.css 中定义的层叠式样式表规则负责呈现美观设计布局。有关上述标记工作原理的详细说明,请参阅《母版页和网站导航 》教程。

图4 :使用嵌套的无序列表呈现导航用户界面(单击此处查看实际大小的图像

添加Breadcrumb 导航

除了左栏的链接列表外,我们还可在每个页面显示一个breadcrumb 。breadcrumb 是一个导航用户界面元素,它可以快速为用户显示目前在网站层次结构中的位置。SiteMapPath 控件使用 Site Map 框架来确定当前页面在站点地图中的位置,然后根据该信息显示一个breadcrumb 。

具体来说,在母版页的标题 <div> 元素中添加一个<span> 元素,将该 <span> 元素的 class 属性设置为 “breadcrumb” 。(Styles.css 类包含“breadcrumb” 类的规则。)接下来,在该 <span> 元素中添加一个 SiteMapPath 。

<div id="header">
     <span class="title">User Account Tutorials</span><br />
     <span class="breadcrumb">
          <asp:SiteMapPath ID="SiteMapPath1" runat="server">
          </asp:SiteMapPath>
     </span>
</div>

图 5 显示了访问~/Membership/CreatingUserAccounts.aspx 时 SiteMapPath 的输出。

图5 :Breadcrumb 显示站点地图中的当前页及其祖先(单击此处查看实际大小的图像

步骤4 :删除定制的Principal 和Identity 逻辑

在《表单身份验证配置和高级主题 》教程中,我们探讨了如何将定制的principal 和identity 对象关联到已验证用户。我们的做法是,在Global.asax 中为应用的PostAuthenticateRequest 事件(该事件是在FormsAuthenticationModule 验证了用户身份之后触发的)创建事件处理程序。在该事件处理程序中,我们将FormsAuthenticationModule 添加的GenericPrincipal 和FormsIdentity 对象替换为在该教程中创建的CustomPrincipal 和CustomIdentity 对象。

尽管在某些情况下,定制的 principal 和identity 对象很有用,不过大多数情况下有 GenericPrincipal 和 FormsIdentity 对象就足够了。因此,我觉得使用默认的处理方式就可以了。为此,我们可以删除PostAuthenticateRequest 事件处理程序或将其注释掉,或者删除整个Global.asax 文件。

步骤5 :通过编码创建新用户

要通过 Membership 框架创建新用户帐户,可使用 Membership 类的 CreateUser 方法 。该方法的输入参数用于用户名、密码及其它与用户相关的字段。调用该方法时,它将新用户帐户的创建委托给配置的Membership 提供者,然后返回一个代表刚创建的用户帐户的MembershipUser 对象

CreateUser 方法有四种重载,每个都接受不同数目的输入参数:

这四种重载的不同在于收集的信息量不同。例如,第一种重载只需要用户帐户的用户名和密码,而第二个还需要用户的电子邮箱。

为什么有这些重载方法呢?因为创建一个新用户帐户所需要的信息取决于Membership 提供者的配置设置。在《在SQL Server 中创建Membership Schema 》教程中,我们探讨了如何在Web.config 中指定Membership 提供者配置设置。表2 包含了配置设置的完整列表。

决定着应该使用哪种 CreateUser 重载的Membership 提供者配置设置是 requiresQuestionAndAnswer 设置。如果requiresQuestionAndAnswer 设置为 true (默认值),则创建一个新用户帐户时,我们必须指定一个安全问题和答案。以后用户需要重置或修改密码时需要该信息。具体来说,他们看到安全问题时必须输入正确的答案,这样才能重置或修改他们的密码。因此,如果requiresQuestionAndAnswer 设置为 true ,那么调用前两种 CreateUser 重载时将抛出一个异常,因为没有安全问题和答案。由于我们的应用目前配置为需要安全问题和答案,因此通过编码创建用户帐户时,我们需要使用后面的两种重载之一。

为演示 CreateUser 方法的使用,让我们创建一个用户界面,提示用户输入名称、密码、电子邮箱以及预定义安全问题的答案。打开Membership 文件夹中的 CreatingUserAccounts.aspx 页面,然后向 Content 控件添加如下的Web 控件:

  • 一个名为 Username 的文本框
  • 一个名为 Password 的文本框,其 TextMode 属性设置为 Password
  • 一个名为 Email 的文本框
  • 一个名为 SecurityQuestion 的标签,清除其 Text 属性
  • 一个名为 SecurityAnswer 的文本框
  • 一个名为 CreateAccountButton 的按钮,其 Text 属性设置为 “Create the User Account”
  • 一个名为 CreateAccountResults 的标签控件,清除其 Text 属性

此时,屏幕看起来应该类似于图6 所示的截屏。

图6 :添加各种 Web 控件到 CreatingUserAccounts.aspx 页面(单击此处查看实际大小的图像

SecurityQuestion 标签和SecurityAnswer 文本框用于显示预定义的安全问题和收集用户的答案。注意,安全问题和答案都按用户存储的,因此,允许每个用户定义自己的安全问题。然而,在本示例中,我决定使用一般的安全问题,即:“您最喜欢的颜色是?”

为实现该预定义安全问题,在页面的代码文件类中添加一个名为passwordQuestion 的常量,用于存放安全问题。然后,在Page_Load 事件处理程序中,将该常量赋值给 SecurityQuestion 标签的 Text 属性:

const string passwordQuestion = "What is your favorite color";
    
protected void Page_Load(object sender, EventArgs e)
{
     if (!Page.IsPostBack)
          SecurityQuestion.Text = passwordQuestion;
}

接下来 ,为CreateAccountButton 的 Click 事件创建一个事件处理程序 , 并添加以下代码 :

protected void CreateAccountButton_Click(object sender, EventArgs e)
{
     MembershipCreateStatus createStatus;
     MembershipUser newUser = Membership.CreateUser(Username.Text, Password.Text, Email.Text,
                                                                                          passwordQuestion, SecurityAnswer.Text, true, out createStatus);
     switch (createStatus)
     {
          case MembershipCreateStatus.Success:
               CreateAccountResults.Text = "The user account was successfully created!";
               break;
          case MembershipCreateStatus.DuplicateUserName:
               CreateAccountResults.Text = "There already exists a user with this username.";
               break;
          case MembershipCreateStatus.DuplicateEmail:
               CreateAccountResults.Text = "There already exists a user with this email address.";
               break;
          case MembershipCreateStatus.InvalidEmail:
               CreateAccountResults.Text = "There email address you provided in invalid.";
               break;
          case MembershipCreateStatus.InvalidAnswer:
               CreateAccountResults.Text = "There security answer was invalid.";
               break;
          case MembershipCreateStatus.InvalidPassword:
               CreateAccountResults.Text = "The password you provided is invalid.
                                                              It must be seven characters long and have at least one non-alphanumeric character.";
               break;
          default:
               CreateAccountResults.Text = "There was an unknown error; the user account was NOT created.";
               break;
     }
}

Click 事件处理程序首先定义一个名为 createStatus 的变量,类型为 MembershipCreateStatus 。MembershipCreateStatus 是一个枚举类型,指示 CreateUser 操作的状态。例如,如果用户帐户创建成功,生成的MembershipCreateStatus 实例将被赋值Success ;另一方面,如果由于系统中已存在相同用户名的用户而失败,将为其赋值DuplicateUserName 的值。在我们使用的 CreateUser 重载中,我们需要将MembershipCreateStatus 实例传入该方法作为输出参数。该参数在CreateUser 方法中赋值为恰当的值,所以我们可以检查调用该方法之后该参数的值,来确定是否成功创建用户帐户。

调用 CreateUser 并传入createStatus 后,使用一个 switch 语句基于 createStatus 的赋值来输出相应的消息。图 7 显示的是成功创建新用户帐户时的输出。图8 和 9 显示的是用户帐户创建失败时的输出。在图8 中,访问者输入 5 个字母的密码,这不符合 Membership 提供者的配置设置中指定的密码长度要求。在图9 中,访问者尝试创建一个已存在用户名(图7 中创建的)的用户帐户。

图7 : 成功创建 新用户帐户(单击此处查看实际大小的图像

图8 :由于提供的密码保密性太差而未能创建用户帐户(单击此处查看实际大小的图像

图9 :由于用户名已使用而未能创建用户帐户(单击此处查看实际大小的图像

注意 : 您可能想知道,使用前两种 CreateUser 方法重载时,它们都没有类型为 MembershipCreateStatus 的参数,如何判断成功或失败?如果创建失败,这两种重载将抛出MembershipCreateUserException异常 ,该异常包含有MembershipCreateStatus 类型的 StatusCode 属性

创建了几个用户帐户后,列出SecurityTutorials.mdf 数据库中 aspnet_Users 和 aspnet_Membership 表的内容,验证这些帐户是否已创建。如图10 所示,我通过 CreatingUserAccounts.aspx 页面添加了两个用户帐户:Tito 和Bruce 。

图10 :Membership 用户存储中有两个用户:Tito 和 Bruce (单击此处查看实际大小的图像

虽然Membership 用户存储现在包含了Bruce 和Tito 的帐户信息,但我们还没有实现允许Bruce 或Tito 登录网站的功能。目前,Login.aspx 对照硬编码的用户名/ 密码对来验证用户的凭据,– 而不是对照Membership 框架来验证提供的凭据。现在,在aspnet_Users 和aspnet_Membership 表中看到新用户帐户就可以了。在下一篇教程《根据 Membership 用户存储验证用户凭据 》中,我们将更改登录页面对照Membership 存储进行验证。

注意 如果您在SecurityTutorials.mdf 数据库中没有看到任何用户,很可能是您的web 应用使用的是默认的Membership 提供者AspNetSqlMembershipProvider ,它使用ASPNETDB.mdf 数据库作为用户存储。要确定是否是这个问题,在Solution Explorer 中单击Refresh 按钮。如果在App_Data 文件夹中增加了一个名为ASPNETDB.mdf 的数据库,那就是这个问题。有关如何正确配置Membership 提供者的说明,请参考《在SQL Server 中创建Membership Schema 》教程中的步骤4 。

大多数创建用户帐户的情况是,访问者在某个界面中输入他们的用户名、密码、电子邮箱以及其它必要信息,然后基于这些信息来创建用户帐户。在这一步中,我们了解了如何手动构建这样的界面,如何通过编码使用Membership.CreateUser 方法来基于用户的输入创建新用户帐户。但我们的代码只是创建了新用户帐户。它没有进行其它处理,比如用刚创建的用户帐户登录到站点,或给用户发送一封确认电子邮件。这些额外的步骤需要在按钮的Click 事件处理程序中添加相应的代码。

ASP.NET 有一个CreateUserWizard 控件,它就是设计用于处理用户创建过程的,从呈现创建新用户帐户的用户界面,到在Membership 框架中创建帐户和执行帐户创建后的任务,比如发送确认电子邮件和用刚创建的帐户登录到站点等。使用CreateUserWizard 控件很简单,只需从 Toolbox 拖一个 CreateUserWizard 控件到页面,然后设置几个属性。大多数情况下,不用写一行代码。我们将在步骤6 中详细探讨这个极棒的控件。

如果只是通过普通的创建帐户 web 页面创建新用户帐户,似乎不需要使用CreateUser 方法编写代码,因为 CreateUserWizard 控件很可能满足您的需要。不过,如果您需要特别定制的创建帐户用户体验,或者您需要通过其它界面编程创建新用户帐户时,CreateUser 方法就很方便。比如,您可能有一个页面,它允许用户上传包含来自其它应用的用户信息的XML 文件。该页面对上传的 XML 文件内容进行解析,然后调用 CreateUser 方法为 XML 文件中的每个用户创建一个新帐户。

步骤6 :使用CreateUserWizard 控件创建新用户

ASP.NET 有许多Web 登录控件。在很多常见的与用户帐户和与登录有关的情况下,这些控件都很有用。CreateUserWizard 控件 就是这样的一个控件,它是设计用于呈现在Membership 框架中添加新用户帐户的用户界面。

和其它与登录有关的 Web 控件一样,不用写一行代码就可以使用CreateUserWizard 。它根据 Membership 提供者的配置设置呈现一个直观的用户界面,用户输入必要的信息并单击“Create User” 按钮后,它就在内部调用 Membership 类的CreateUser 方法。 CreateUserWizard 控件是非常容易定制的。在帐户创建的不同阶段可以触发很多的事件。如果需要,我们可以创建事件处理程序,在帐户创建流程中加入定制的逻辑。此外,CreateUserWizard 的外观也很灵活。有很多定义默认界面外观的属性;如果有必要,可以将控件转化为一个模板,或添加其它的用户注册“步骤”。

我们先探讨如何使用 CreateUserWizard 控件的默认界面和行为。然后我们再探讨如何通过控件的属性和事件来定制外观。

探讨CreateUserWizard 的默认界面和行为

返回 Membership 文件夹中的CreatingUserAccounts.aspx 页面,切换到 Design 或 Split 模式,然后在页面顶部添加一个 CreateUserWizard 控件。CreateUserWizard 控件放在Toolbox 的 Login 控件区。添加控件后,将其 ID 属性设置为 RegisterUser 。如图 11 中的截屏所示,CreateUserWizard 使用一些文本框呈现界面,这些文本框用于输入新用户的用户名、密码、电子邮箱以及安全问题和答案。

图11 :CreateUserWizard 控件呈现一般的创建用户界面(单击此处查看实际大小的图像

我们花点时间对比一下 CreateUserWizard 控件生成的默认用户界面和我们在步骤5 中创建的界面。首先,CreateUserWizard 控件允许访问者指定安全问题和答案,而我们手动创建的界面使用预定义的安全问题。CreateUserWizard 控件的界面还包含验证控件,而我们的界面还未实现对表单域的验证。 另外,CreateUserWizard 控件界面包含一个 “Confirm Password” 文本框(以及一个CompareValidator ,用于确保 “Password” 和 “Compare Password” 文本框中输入的文本是相同的)。

有趣的是,CreateUserWizard 控件将根据Membership 提供者的配置设置来呈现其用户界面。比如,只有当requiresQuestionAndAnswer 设置为 True 时,才会显示安全问题和答案。同样地,CreateUserWizard 还会自动添加一个 RegularExpressionValidator 控件来确保满足密码长度要求,根据 minRequiredPasswordLength 、minRequiredNonalphanumericCharacters 和passwordStrengthRegularExpression 配置设置来设置其 ErrorMessage 和ValidationExpressions 属性。

正如其名称所暗示的那样,CreateUserWizard 控件源于Wizard 控件 。Wizard 控件用于提供完成多步骤任务的界面。Wizard 控件可以有任意多个 WizardStep ,每个 WizardStep 都是一个模板,定义该步骤的 HTML 和 Web 控件。Wizard 控件最开始显示第一个 WizardStep ,以及允许用户从一个步骤进入下一个步骤,或返回前一步骤的导航控件。

如图 11 中的声明标记所示,CreateUserWizard 控件的默认界面包含两个 WizardStep :

  • CreateUserWizardStep – 呈现的界面用于收集创建新用户帐户需要的信息。这也是图11 中所示的步骤。
  • CompleteWizardStep – 显示一条消息,指示已成功创建新帐户。

可将这些步骤转换为模板或添加自己的WizardSteps ,从而改变 CreateUserWizard 的外观和行为。我们将在《存储其它用户信息》教程中探讨如何在注册界面中添加WizardStep 。

我们来看一个实际的 CreateUserWizard 控件。通过浏览器访问CreatingUserAccounts.aspx 页面。首先在CreateUserWizard 的界面中输入一些无效的值。尝试输入一个不满足密码强度要求的密码,或将“User Name” 文本框置空。CreateUserWizard 将显示相应的错误消息。图 12 显示的是尝试创建密码强度不足的用户时的输出。

图12 :CreateUserWizard 自动加入验证控件(单击此处查看实际大小的图像

接下来,在 CreateUserWizard 中输入恰当的值,然后单击 “Create User” 按钮。假如已输入必需的字段且密码的强度满足要求,CreateUserWizard 将通过 Membership 框架创建一个新用户帐户,然后显示CompleteWizardStep 的界面(参见图13 )。在后台,CreateUserWizard 调用 Membership.CreateUser 方法,和我们在步骤 5 中做的一样。

图13 :已成功创建新用户帐户(单击此处查看实际大小的图像

注意 : 如图13 所示,CompleteWizardStep 的界面包含一个 Continue 按钮。然而,如果此时单击它,仅执行一个回传,访问者仍停留在同一页面。在“通过CreateUserWizard 的属性定制其外观和行为”一节中,我们将了解如何让该按钮将访问者导航到Default.aspx (或某个其它页面)。

创建新用户帐户后,返回 Visual Studio ,检查aspnet_Users 和 aspnet_Membership 表,就像我们在图 10 中做的那样,验证是否成功创建帐户。

通过CreateUserWizard 的属性定制其外观和行为

我们可通过多种途径定制 CreateUserWizard ,比如属性、WizardSteps 和事件处理程序。在本节,我们将了解如何通过其属性来定制控件的外观;在下一节,我们将了解如何通过事件处理程序扩展控件的行为。

实际上,在 CreateUserWizard 控件的默认用户界面上所显示的所有文本都可以通过其众多的属性进行定制。例如,可分别通过UserNameLabelTextPasswordLabelTextConfirmPasswordLabelTextEmailLabelTextQuestionLabelTextAnswerLabelText 属性来定制显示在文本框左边的 “User Name” 、“Password” 、“Confirm Password” 、“E-mail” 、“Security Question” 和 “Security Answer” 标签。同样,也有指定 CreateUserWizardStep 和CompleteWizardStep 中 “Create User” 和 “Continue” 按钮的文本的属性,以及可指定这些按钮呈现为Buttons 、LinkButtons 或 ImageButtons 的属性。

对颜色、边框、字体以及其它的视觉元素,我们可以通过一系列的风格属性来进行设置。CreateUserWizard 控件本身有一些通用的 Web 控件风格属性 – BackColor 、BorderStyle 、CssClass 、Font 等等– 也有一些用于定义 CreateUserWizard 界面特定部分的外观的风格属性。例如,TextBoxStyle 属性 定义 CreateUserWizardStep 中文本框的风格,而 TitleTextStyle 属性 定义标题的风格(“Sign Up for Your New Account” )。

除了这些与外观有关的属性外,还有一些属性可以影响CreateUserWizard 控件的行为。如果将 DisplayCancelButton 属性 设置为True ,就会在 “Create User” 按钮的旁边显示一个 Cancel 按钮(默认值为 False )。如果您要显示 Cancel 按钮,也一定要设置 CancelDestinationPageUrl属性 ,它指定了在用户单击 Cancel 后将用户导航到的页面。正如我们在前一节中所提到的,CompleteWizardStep 界面的 Continue 按钮产生一个回传,但访问者仍停留在同一页面。要在单击Continue 按钮后将访问者导航某个其它页面,只需要在ContinueDestinationPageUrl属性 中指定该页面的URL 。

我们更新一下 RegisterUser CreateUserWizard 控件,以显示 Cancel 按钮,并在用户单击 Cancel 或 Continue 按钮时将他导航到 Default.aspx 。为此,需要将DisplayCancelButton 属性设置为 True ,并将 CancelDestinationPageUrl 和ContinueDestinationPageUrl 属性都设置为 “~/Default.aspx” 。图 14 显示了通过浏览器查看更新后的 CreateUserWizard 。

图14 :CreateUserWizardStep 包含一个 Cancel 按钮(单击此处查看实际大小的图像

当访问者输入用户名、密码、电子邮箱以及安全问题和答案并单击“Create User” 时,将创建一个新用户帐户,访问者将以刚创建的用户进行登录。假如访问该页面的人要创建一个新的帐户,这就是希望的行为。不过,您可能想让管理员来创建新用户帐户。如果是这样,那么将创建用户帐户,但Administrator 仍然以 Administrator 登录(而不是刚创建的帐户)。可通过修改LoginCreatedUser 属性 的布尔值来改变此行为。

Membership 框架中的用户帐户包含一个已批准标记;未被批准的用户不能登录网站。默认情况下,刚创建的帐户被标记为已批准;允许用户立即登录网站。不过,也有可能要将新用户帐户标记为未批准。可能您想先让管理员手动批准这些新用户,然后允许他们登录;也可能您想先确认验证用户注册时提供的电子邮箱是有效的,然后才允许用户登录。无论是哪种情况,都可以通过设置CreateUserWizard 控件的 DisableCreatedUser 属性 为 True (默认值为False ),将新创建的用户帐户标记为未批准。

其它还应关注的与行为有关的属性包括AutoGeneratePassword 和 MailDefinition 。如果 AutoGeneratePassword 属性 设置为True ,则 CreateUserWizardStep 不显示 “Password” 和 “Confirm Password” 文本框;而是使用Membership 类的GeneratePassword 方法 自动生成新创建用户的密码。GeneratePassword 方法构造指定长度的密码,该密码有足够数目的非字母数字字符以满足配置的密码强度要求。

如果您想给帐户创建过程中指定的电子邮箱发送邮件,MailDefinition 属性 就很有用。MailDefinition 属性包含一系列子属性,用于定义有关构造邮件消息的信息。这些子属性包括Subject 、Priority 、IsBodyHtml 、From 、CC 和 BodyFileName 等选项。BodyFileName 属性 指向一个文本或者包含邮件消息主体的一个HTML 文件。主体支持两个预定义的占位符:<%UserName%> 和 <%Password%> 。如果这些占位符出现在 BodyFileName 文件中,将被替换为刚创建的用户的名称和密码。

注意 :CreateUserWizard 控件的MailDefinition 属性只是指定新用户创建时发送的邮件消息的细节。它并不包含邮件消息实际如何发送的细节(即,是使用SMTP 服务器还是邮箱目录,是否有身份验证信息等等)。需要在Web.config 的 <system.net> 节点中定义这些底层细节。关于这些配置设置的更多信息,以及在ASP.NET 2.0 中发送邮件的更多信息,请参考FAQs at SystemNetMail.com ,以及我的文章Sending Email in ASP.NET 2.0

使用事件处理程序扩展CreateUserWizard 的行为

CreateUserWizard 控件在处理过程中会触发一些事件。例如,访问者输入其用户名、密码和其它有关信息并单击“Create User” 按钮后,CreateUserWizard 控件将触发 CreatingUser 事件 。如果在创建过程中出现问题,则触发CreateUserError 事件 ;如果用户创建成功,则触发 CreatedUser 事件 。还会触发其它的 CreateUserWizard 控件事件,但这三个是关系最为密切的事件。

在某些情况下,我们可能需要介入CreateUserWizard 流程,为特定事件创建事件处理程序。为了解释这种情况,让我们增强RegisterUser CreateUserWizard 控件,对用户名和密码进行定制验证。具体来说,增强我们的CreateUserWizard ,使得用户名前后不能有空格,且用户名不能出现在密码中。简单的说,我们想防止某个用户创建用户名为"Scott " ,或用户名/ 密码组合为 “Scott” 和 “Scott.1234” 的帐户。

为此,我们需要创建 CreatingUser 事件的事件处理程序以执行额外的验证检查。如果提供的数据无效,我们需要取消创建过程。我们还需要在页面上添加一个Web 标签控件,用于显示一条消息,说明用户名或密码无效。首先,在CreateUserWizard 控件的下面添加一个标签控件,将其 ID 属性设置为InvalidUserNameOrPasswordMessage ,其 ForeColor 属性设置为Red 。 清空其 Text 属性 , 并将 EnableViewState 和 Visible 属性设为 False 。

<asp:Label runat="server" id="InvalidUserNameOrPasswordMessage"
     Visible="false" ForeColor="Red" EnableViewState="false">
</asp:Label>

接下来,为 CreateUserWizard 控件的CreatingUser 事件创建一个事件处理程序。要创建一个事件处理程序,请在设计器中选择该控件,然后进入属性窗口。在该窗口中,单击闪烁的箭头图标,然后双击相应的事件来创建事件处理器。

在 CreatingUser 事件处理程序中添加以下代码:

protected void RegisterUser_CreatingUser(object sender, LoginCancelEventArgs e)
{
     string trimmedUserName = RegisterUser.UserName.Trim();
     if (RegisterUser.UserName.Length != trimmedUserName.Length)
     {
          // Show the error message
          InvalidUserNameOrPasswordMessage.Text = "The username cannot contain leading or trailing spaces.";
          InvalidUserNameOrPasswordMessage.Visible = true;           // Cancel the create user workflow           e.Cancel = true;    
      }     
     else  
    {  
         // Username is valid, make sure that the password does not contain the username  
         if (RegisterUser.Password.IndexOf(RegisterUser.UserName, StringComparison.OrdinalIgnoreCase) >= 0)  
         {  
              // Show the error message  
              InvalidUserNameOrPasswordMessage.Text = "The username may not appear anywhere in the password.";  
              InvalidUserNameOrPasswordMessage.Visible = true;  
              // Cancel the create user workflow  
              e.Cancel = true;  
         }  
    }
 }

请注意,在 CreateUserWizard 控件中输入的用户名和密码可分别通过其UserNamePassword 属性 来获取。我们在上述的事件处理程序中使用这些属性来确定提供的用户名前后是否有空格,以及在密码中是否有用户名。如果出现上述情况之一,将在InvalidUserNameOrPasswordMessage 标签中显示一个错误消息,并将事件处理程序的e.Cancel 属性设置为 true 。如果 e.Cancel 设置为 true ,CreateUserWizard 结束其处理流程,从而取消用户帐户创建过程。

图 15 显示的是用户输入的用户名前面有空格时CreatingUserAccounts.aspx 的截屏。

图15 :用户名前后不允许有空格(单击此处查看实际大小的图像

注意 我们将在《存储其它用户信息 》教程中看到使用CreateUserWizard 控件的CreatedUser 事件的示例。

小结          

Membership 类的CreateUser 方法在 Membership 框架中创建一个新用户帐户。它的做法是将调用委托给配置的Membership 提供者来完成创建。如果是 SqlMembershipProvider ,CreateUser 方法在 aspnet_Users 和aspnet_Membership 数据库表中添加一条记录。

也可以通过编码创建新用户帐户(如我们在步骤5 中所见),比较快速、方便的方法是使用CreateUserWizard 控件。该控件呈现多步骤的用户界面收集用户信息,并在Membership 框架中创建新用户。在内部,该控件使用Membership.CreateUser 方法,也就是我们在步骤 5 中探讨的那个方法。不过使用该控件时,不用写一行代码就可以创建用户界面、验证控件以及响应用户帐户创建错误。

此时,我们完成了创建新用户帐户的功能。然而,登录页面仍然对照我们在第二个教程中指定的硬编码凭据进行验证。在下一篇教程 中,我们将对Login.aspx 进行更改,对照Membership 框架验证验证用户提供的凭据。

快乐编程!

 

下一篇教程