创建用户帐户 (C#)

作者 :Scott Mitchell

注意

自本文撰写以来,ASP.NET 成员资格提供程序已被 ASP.NET Identity 取代。 强烈建议更新应用以使用 ASP.NET 标识 平台,而不是本文撰写时提供的成员资格提供程序。 与 ASP.NET 成员身份系统 ASP.NET 标识具有许多优势,包括:

  • 性能更好
  • 改进了扩展性和可测试性
  • 支持 OAuth、OpenID Connect 和双因素身份验证
  • 基于声明的标识支持
  • 与 ASP.Net Core 更好的互操作性

下载代码下载 PDF

在本教程中,我们将探讨如何通过 SqlMembershipProvider) 使用成员资格框架 (创建新的用户帐户。 我们将了解如何通过编程方式和 ASP 创建新用户。NET 的内置 CreateUserWizard 控件。

简介

前面的教程中,我们在数据库中安装了应用程序服务架构,该架构添加了 和 SqlRoleProvider所需的SqlMembershipProvider表、视图和存储过程。 这为本系列教程的其余部分创建了所需的基础结构。 在本教程中,我们将探讨如何通过 SqlMembershipProvider) 使用成员资格框架 (来创建新的用户帐户。 我们将了解如何通过编程方式和 ASP 创建新用户。NET 的内置 CreateUserWizard 控件。

除了了解如何创建新用户帐户外,我们还需要更新我们首先在表单身份验证概述教程中创建的演示网站,然后在表单身份验证配置和高级主题教程中对其进行增强。 我们的演示 Web 应用程序有一个登录页,用于根据硬编码的用户名/密码对验证用户的凭据。 此外, Global.asax 还包括为经过身份验证的用户创建自定义 IPrincipalIIdentity 对象的代码。 我们将更新登录页,以根据成员资格框架验证用户的凭据,并删除自定义主体和标识逻辑。

让我们开始吧!

表单身份验证和成员身份清单

在开始使用成员资格框架之前,让我们花点时间回顾一下为达到这一点而采取的重要步骤。 在基于表单的身份验证方案中将成员资格框架与 SqlMembershipProvider 结合使用时,需要在 Web 应用程序中实现成员身份功能之前执行以下步骤:

  1. 启用基于表单的身份验证。 正如我们在表单身份验证概述中所述,表单身份验证是通过编辑Web.config元素的 属性并将其设置为 <authentication>Forms启用的mode。 启用表单身份验证后,将检查每个传入请求以获取 表单身份验证票证,如果存在,该票证将标识请求者。
  2. 将应用程序服务架构添加到相应的数据库。 使用 时, SqlMembershipProvider 需要将应用程序服务架构安装到数据库。 通常,此架构将添加到保存应用程序数据模型的同一数据库中。 本教程介绍了在 SQL Server 中创建成员资格架构,了解如何使用 aspnet_regsql.exe 该工具来实现此目的。
  3. 自定义 Web 应用程序的“设置”以引用步骤 2 中的数据库。 在 SQL Server 中创建成员资格架构教程演示了配置 Web 应用程序以便 SqlMembershipProvider 使用步骤 2 中选择的数据库的两种方法:修改LocalSqlServer连接字符串名称;或将新注册的提供程序添加到成员资格框架提供程序列表,并将新提供程序自定义为使用步骤 2 中的数据库。

生成使用 SqlMembershipProvider 和 基于表单的身份验证的 Web 应用程序时,需要先执行这三个步骤,然后再使用 Membership 类或登录 Web 控件 ASP.NET。 由于我们已在前面的教程中执行这些步骤,因此我们已准备好开始使用成员资格框架!

步骤 1:添加新 ASP.NET 页

在本教程和接下来的三个教程中,我们将研究各种与成员身份相关的功能和功能。 我们需要一系列 ASP.NET 页面来实现这些教程中介绍的主题。 让我们创建这些页面,然后创建站点地图文件 (Web.sitemap)

首先,在名为 Membership的项目中创建一个新文件夹。 接下来,将五个新 ASP.NET 页添加到 Membership 文件夹,将每个页面与 Site.master 母版页链接。 为页面命名:

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

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

已向成员身份文件夹添加了五个新页面

图 1:已向 Membership 文件夹添加了五个新页面 (单击以查看全尺寸图像)

此时,每个页面应具有两个 Content 控件,一个用于母版页的 ContentPlaceHolders: MainContentLoginContent

<asp:Content ID="Content1" ContentPlaceHolderID="MainContent"

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

回想一下, LoginContent ContentPlaceHolder 的默认标记显示用于登录或注销网站的链接,具体取决于用户是否经过身份验证。 但是,Content 控件的存在 Content2 会替代母版页的默认标记。 如表单身份验证概述教程中所述,这在不希望在左列中显示登录相关选项的页面非常有用。

但是,对于这五个页面,我们希望显示 ContentPlaceHolder 的母版页的默认标记 LoginContent 。 因此,请删除 Content 控件的 Content2 声明性标记。 执行此操作后,五个页面的标记中的每个标记应仅包含一个 Content 控件。

步骤 2:创建站点地图

除了最普通的网站,其他所有网站都需要实现某种形式的导航用户界面。 导航用户界面可以是指向网站各个部分的简单链接列表。 或者,这些链接可以排列为菜单或树视图。 作为页面开发人员,创建导航用户界面只是故事的一半。 我们还需要一些方法来以可维护且可更新的方式定义站点的逻辑结构。 添加新页面或删除现有页面时,我们希望能够更新单个源(网站地图),并在网站的导航用户界面中反映这些修改。

由于网站地图框架和 ASP.NET 版本 2.0 中添加的导航 Web 控件,这两项任务(定义站点地图和实现基于站点地图的导航用户界面)很容易完成。 站点地图框架允许开发人员定义站点地图,然后通过编程 API (SiteMap) 访问它。 内置导航 Web 控件包括 菜单控件TreeView 控件SiteMapPath 控件

与成员资格和角色框架一样,站点地图框架是在 提供程序模型之上构建的。 Site Map 提供程序类的工作是从永久性数据存储(如 XML 文件或数据库表)生成类使用的 SiteMap 内存中结构。 .NET Framework附带一个默认的站点地图提供程序,该提供程序从 XML 文件 (XmlSiteMapProvider) 读取站点地图数据,这是我们将在本教程中要使用的提供程序。 有关一些备用站点地图提供程序实现,请参阅本教程末尾的“进一步读取”部分。

默认的站点地图提供程序需要一个名为 正确格式的 Web.sitemap XML 文件存在于根目录。 由于我们使用此默认提供程序,因此需要添加此类文件,并用适当的 XML 格式定义站点地图的结构。 若要添加文件,请在 解决方案资源管理器 中右键单击项目名称,然后选择“添加新项”。 在对话框中,选择添加名为 Web.sitemap的 Site Map 类型的文件。

将名为 Web.sitemap 的文件添加到项目的根目录

图 2:将名为 Web.sitemap 的文件添加到项目的根目录 (单击以查看全尺寸图像)

XML 站点地图文件将网站的结构定义为层次结构。 此分层关系通过元素的 <siteMapNode> 祖先在 XML 文件中建模。 Web.sitemap必须从恰好有一个<siteMap><siteMapNode>子节点的父节点开始。 此顶级 <siteMapNode> 元素表示层次结构的根,并且可能具有任意数量的后代节点。 每个<siteMapNode>元素必须包含一个属性,并且可以选择性地包含 urldescription 属性等;每个非空url属性必须是唯一title的。

在 文件中输入以下 XML Web.sitemap

<?xml version="1.0" encoding="utf-8" ?>

<siteMap xmlns="http://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 则显示一个痕迹导航,显示当前正在访问的节点及其上级。 可以使用 SiteMapDataSource 将站点地图数据绑定到其他数据 Web 控件,并且可以通过 类以编程方式 SiteMap 访问。

由于对站点地图框架和导航控件的深入讨论超出了本教程系列的范围,因此,与其花时间制作我们自己的导航用户界面,不如借用 ASP.NET 2.0 教程系列中的“我处理数据”中使用的 用户界面,该系列使用 Repeater 控件显示导航链接的两深项目符号列表, 如图 4 所示。

若要创建此接口,请将以下声明性标记添加到 Site.master 母版页的左侧列中,其中文本“TODO:菜单将转到此处...”。当前驻留。

<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,因此它开始返回从“主页”节点的后代开始的站点地图层次结构。 中继器显示每个节点 (当前只是元素中的 <li> “成员身份”) 。 然后,另一个内部中继器在嵌套的无序列表中显示当前节点的子级。

图 4 显示了上述标记使用我们在步骤 2 中创建的站点地图结构呈现的输出。 Repeater 呈现 vanilla 无序列表标记;中 Styles.css 定义的级联样式表规则负责美观的布局。 有关上述标记工作原理的更详细说明,请参阅 母版页和网站导航 教程。

导航用户界面是使用嵌套的无序列表呈现的

图 4:使用嵌套的无序列表呈现导航用户界面 (单击以查看全尺寸图像)

添加痕迹导航

除了左侧列中的链接列表外,我们还让每个页面都显示一个 痕迹导航。 痕迹导航是一种导航用户界面元素,可快速向用户显示其在站点层次结构中的当前位置。 SiteMapPath 控件使用站点地图框架来确定当前页在站点地图中的位置,然后基于此信息显示痕迹导航。

具体而言,将元素 <span> 添加到母版页的标头 <div> 元素,并将新 <span> 元素的 class 属性设置为“痕迹导航”。 (类 Styles.css 包含“痕迹导航”类的规则。) 接下来,将 SiteMapPath 添加到此新 <span> 元素。

<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 显示了访问 时 SiteMapPath 的 ~/Membership/CreatingUserAccounts.aspx输出。

痕迹导航在站点地图中显示当前页及其上级

图 5:痕迹导航在站点地图中显示当前页及其上级 (单击以查看全尺寸图像)

步骤 4:删除自定义主体和标识逻辑

自定义主体和标识对象可以关联到经过身份验证的用户。 为此,我们在 中 Global.asax 为应用程序 PostAuthenticateRequest 的事件创建事件处理程序,该事件处理程序在 对用户进行身份验证后 FormsAuthenticationModule 触发。 在此事件处理程序中,GenericPrincipal我们将 添加的 FormsAuthenticationModuleCustomPrincipalFormsIdentity 对象替换为在该教程中创建的 和 CustomIdentity 对象。

虽然自定义主体和标识对象在某些情况下很有用,但在大多数情况下, GenericPrincipalFormsIdentity 对象就足够了。 因此,我认为返回默认行为是值得的。 通过删除或注释掉 PostAuthenticateRequest 事件处理程序或完全删除 Global.asax 文件进行此更改。

步骤 5:以编程方式创建新用户

若要通过成员资格框架创建新的用户帐户, Membership 请使用 类的 CreateUser 方法。 此方法具有用户名、密码和其他与用户相关的字段的输入参数。 在调用时,它将新用户帐户的创建委托给配置的成员资格提供程序,然后返回表示MembershipUser刚刚创建的用户帐户的对象。

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

这四个重载在收集的信息量上有所不同。 例如,第一个重载只需要新用户帐户的用户名和密码,而第二个重载也需要用户的电子邮件地址。

之所以存在这些重载,是因为创建新用户帐户所需的信息取决于成员资格提供程序的配置设置。 SQL Server 中创建成员资格架构教程中,我们检查了在 中Web.config指定成员资格提供程序配置设置。 表 2 包含配置设置的完整列表。

影响可能使用的重载的 CreateUser 此类成员资格提供程序配置设置之一是 requiresQuestionAndAnswer 设置。 如果 requiresQuestionAndAnswer 设置为 true (默认) ,则在创建新用户帐户时,必须指定安全问题和答案。 如果用户需要重置或更改其密码,稍后将使用此信息。 具体来说,此时会显示安全问题,并且必须输入正确的答案才能重置或更改密码。 因此,如果 设置为 truerequiresQuestionAndAnswer则调用前两CreateUser个重载之一都会导致异常,因为缺少安全问题和答案。 由于应用程序当前配置为需要安全问题和答案,因此在以编程方式创建用户的 时,需要使用后两个重载之一。

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

  • 名为 的 TextBox Username
  • 名为 Password的 TextBox,其 TextMode 属性设置为 Password
  • 名为 的 TextBox Email
  • 已清除其Text属性的名为 SecurityQuestion 的标签
  • 名为 的 TextBox SecurityAnswer
  • 名为 CreateAccountButton 的按钮,其 Text 属性设置为“创建用户帐户”
  • 一个名为 CreateAccountResults 的 Label 控件,其 Text 属性已清除

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

将各种 Web 控件添加到 CreatingUserAccounts.aspx 页

图 6:将各种 Web 控件添加到 CreatingUserAccounts.aspx 页面 (单击以查看全尺寸图像)

SecurityQuestion Label 和 SecurityAnswer TextBox 旨在显示预定义的安全问题并收集用户的答案。 请注意,安全问题和答案都是按用户存储的,因此可以允许每个用户定义自己的安全问题。 但是,对于此示例,我决定使用一个通用的安全问题,即:“你最喜欢的颜色是什么?

若要实现此预定义的安全问题,请向页面 passwordQuestion的代码隐藏类添加一个名为 的常量,为其分配安全问题。 然后,在事件处理程序中 Page_Load ,将此常量分配给 SecurityQuestion Label 的 Text 属性:

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

接下来,为 CreateAccountButtonClick 事件创建事件处理程序,并添加以下代码:

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首先定义类型为 的MembershipCreateStatus变量createStatusMembershipCreateStatus 是指示操作状态的 CreateUser 枚举。 例如,如果成功创建用户帐户,则生成的 MembershipCreateStatus 实例将设置为 一个值 Success;另一方面,如果操作失败,因为存在具有相同用户名的用户,则会将其设置为 值 DuplicateUserName。 在CreateUser使用的重载中,需要将 实例作为out参数传递到 MembershipCreateStatus 方法中。 此参数在 方法中 CreateUser 设置为适当的值,我们可以在方法调用后检查其值,以确定是否已成功创建用户帐户。

调用 CreateUser后,传入 createStatusswitch 语句用于输出相应的消息,具体取决于分配给 createStatus的值。 图 7 显示了成功创建新用户时的输出。 图 8 和图 9 显示了未创建用户帐户时的输出。 在图 8 中,访问者输入了一个五个字母的密码,该密码不符合成员资格提供程序配置设置中说明的密码强度要求。 在图 9 中,访问者尝试使用现有用户名创建用户帐户, (图 7) 中创建的用户名。

已成功创建新用户帐户

图 7:已成功创建新用户帐户 (单击以查看全尺寸图像)

未创建用户帐户,因为提供的密码太弱

图 8:未创建用户帐户,因为提供的密码太弱 (单击以查看全尺寸图像)

未创建用户帐户,因为用户名已在使用中

图 9:未创建用户帐户,因为用户名已在使用中 (单击以查看全尺寸图像)

注意

你可能想知道在使用前两 CreateUser 个方法重载之一时如何确定成功或失败,这两个重载都不具有 类型的 MembershipCreateStatus参数。 前两个MembershipCreateUserException重载在失败时引发异常,其中包括 StatusCode 类型MembershipCreateStatus为 的属性。

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

成员资格用户存储中有两个用户:Tito 和 Bruce

图 10:成员资格用户存储中有两个用户:Tito 和 Bruce (单击以查看全尺寸图像)

虽然成员资格用户存储现在包含 Bruce 和 Tito 的帐户信息,但我们尚未实现允许 Bruce 或 Tito 登录站点的功能。 目前, Login.aspx 根据一组硬编码的用户名/密码对来验证用户的凭据 - 它 不会 针对成员资格框架验证提供的凭据。 现在,在 和 aspnet_Membership 表中看到新的用户帐户aspnet_Users就足够了。 在下一教程针对成员身份用户存储验证用户凭据”中,我们将更新登录页以针对成员资格存储进行验证。

注意

如果在数据库中看不到任何用户 SecurityTutorials.mdf ,可能是因为 Web 应用程序使用的是默认成员资格提供程序 , AspNetSqlMembershipProvider该提供程序使用 ASPNETDB.mdf 数据库作为其用户存储。 若要确定此问题是否是问题,请单击解决方案资源管理器中的“刷新”按钮。 如果名为 ASPNETDB.mdf 的数据库已添加到文件夹中 App_Data ,则这就是问题。 返回到在 SQL Server 教程中创建成员资格架构的步骤 4,获取有关如何正确配置成员资格提供程序的说明。

在大多数创建用户帐户方案中,访问者会获得一些界面,用于输入其用户名、密码、电子邮件和其他基本信息,此时会创建新帐户。 在此步骤中,我们介绍了如何手动生成此类接口,然后了解如何使用 Membership.CreateUser 方法根据用户的输入以编程方式添加新用户帐户。 但是,我们的代码刚刚创建了新的用户帐户。 它未执行任何后续操作,例如在刚刚创建的用户帐户下将用户登录网站,或向用户发送确认电子邮件。 这些附加步骤需要 Button Click 的事件处理程序中的其他代码。

ASP.NET 随 CreateUserWizard 控件一起提供,该控件旨在处理用户帐户创建过程,从呈现用于创建新用户帐户的用户界面,到在成员资格框架中创建帐户,以及执行帐户创建后任务,例如发送确认电子邮件并将刚刚创建的用户记录到网站中。 使用 CreateUserWizard 控件非常简单,只需将 CreateUserWizard 控件从工具箱拖到页面上,然后设置一些属性即可。 在大多数情况下,无需编写一行代码。 我们将在步骤 6 中详细探讨此漂亮的控件。

如果只能通过典型的“创建帐户”网页创建新用户帐户,则不太可能需要编写使用 CreateUser 方法的代码,因为 CreateUserWizard 控件可能会满足你的需求。 但是, CreateUser 在需要高度自定义的“创建帐户”用户体验或需要通过备用界面以编程方式创建新用户帐户时,方法非常方便。 例如,你可能有一个页面,允许用户上传 XML 文件,其中包含来自其他某个应用程序的用户信息。 该页面可能会分析上传的 XML 文件的内容,并通过调用 CreateUser 方法为 XML 中表示的每个用户创建一个新帐户。

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

ASP.NET 附带许多登录 Web 控件。 这些控件在许多与用户帐户和登录相关的常见方案中提供帮助。 CreateUserWizard 控件是此类控件之一,旨在提供用于向成员资格框架添加新用户帐户的用户界面。

与许多其他与登录相关的 Web 控件一样,CreateUserWizard 无需编写一行代码即可使用。 它基于成员资格提供程序的配置设置直观地提供用户界面,并在用户输入必要信息并单击“创建用户”按钮后在内部调用 MembershipCreateUser 的 方法。 CreateUserWizard 控件非常可自定义。 在帐户创建过程的各个阶段会触发大量事件。 我们可以根据需要创建事件处理程序,以将自定义逻辑注入到帐户创建工作流中。 此外,CreateUserWizard 的外观非常灵活。 有许多属性用于定义默认接口的外观;如有必要,可以将控件转换为模板,也可以添加其他用户注册“步骤”。

让我们首先看看使用 CreateUserWizard 控件的默认接口和行为。 然后,我们将探索如何通过控件的属性和事件自定义外观。

检查 CreateUserWizard 的默认接口和行为

返回到 CreatingUserAccounts.aspx 文件夹中的页面 Membership ,切换到“设计”或“拆分”模式,然后将 CreateUserWizard 控件添加到页面顶部。 CreateUserWizard 控件在“工具箱的登录控件”部分下归档。 添加控件后,将其 ID 属性设置为 RegisterUser。 如图 11 中的屏幕截图所示,CreateUserWizard 呈现一个界面,其中包含新用户的用户名、密码、电子邮件地址和安全问答的文本框。

CreateUserWizard 控件呈现泛型创建用户界面

图 11:CreateUserWizard 控件呈现泛型创建用户界面 (单击以查看全尺寸图像)

让我们花点时间将 CreateUserWizard 控件生成的默认用户界面与步骤 5 中创建的接口进行比较。 对于初学者,CreateUserWizard 控件允许访问者同时指定安全问题和答案,而手动创建的界面使用预定义的安全问题。 CreateUserWizard 控件的接口还包括验证控件,而我们尚未对接口的表单字段实现验证。 CreateUserWizard 控件界面包括“确认密码”文本框 (以及 CompareValidator,以确保输入“密码”和“比较密码”文本框的文本相等) 。

有趣的是,CreateUserWizard 控件在呈现其用户界面时会查阅成员资格提供程序的配置设置。 例如,仅当 设置为 True 时 requiresQuestionAndAnswer ,才会显示安全问题和答案文本框。 同样,CreateUserWizard 会自动添加 RegularExpressionValidator 控件,以确保满足密码强度要求,并根据 、 minRequiredNonalphanumericCharactersValidationExpression 配置设置其 ErrorMessagepasswordStrengthRegularExpression 属性minRequiredPasswordLength

顾名思义,CreateUserWizard 控件派生自 Wizard 控件。 向导控件旨在提供用于完成多步骤任务的界面。 向导控件可能具有任意数量的 WizardSteps,其中每个控件都是定义该步骤的 HTML 和 Web 控件的模板。 向导控件最初显示第一个 WizardStep,以及允许用户从一个步骤转到下一个步骤或返回到前面的步骤的导航控件。

如图 11 中的声明性标记所示,CreateUserWizard 控件的默认接口包括两个 WizardSteps:

  • CreateUserWizardStep – 呈现接口,以收集用于创建新用户帐户的信息。 这是图 11 所示的步骤。
  • CompleteWizardStep - 呈现一条消息,指示已成功创建帐户。

可以通过将上述任一步骤转换为模板或添加自己的 WizardSteps来修改 CreateUserWizard 的外观和行为。 我们将在存储其他用户信息教程中了解如何将 添加到WizardStep注册界面。

让我们看看 CreateUserWizard 控件的运行情况。 CreatingUserAccounts.aspx通过浏览器访问页面。 首先,在 CreateUserWizard 的接口中输入一些无效值。 尝试输入不符合密码强度要求的密码,或将“用户名”文本框留空。 CreateUserWizard 将显示相应的错误消息。 图 12 显示了尝试创建密码不够强的用户时的输出。

CreateUserWizard 自动注入验证控件

图 12:CreateUserWizard 自动注入验证控件 (单击以查看全尺寸图像)

接下来,在 CreateUserWizard 中输入适当的值,然后单击“创建用户”按钮。 假设已输入必填字段并且密码的强度已足够,则 CreateUserWizard 将通过成员资格框架创建新的用户帐户,然后显示 CompleteWizardStep的接口 (请参阅图 13) 。 在后台,CreateUserWizard 调用 Membership.CreateUser 方法,就像我们在步骤 5 中所做的那样。

已成功创建新的用户帐户

图 13:已成功创建新用户帐户 (单击以查看全尺寸图像)

注意

如图 13 所示, CompleteWizardStep的 界面包含“继续”按钮。 但是,此时单击它只会执行回发,使访问者位于同一页面上。 在“通过其属性自定义 CreateUserWizard 的外观和行为”部分中,我们将了解如何让此按钮将访问者发送到 Default.aspx (或其他页面) 。

创建新的用户帐户后,返回到 Visual Studio 并检查 和 aspnet_Membership 表,aspnet_Users如图 10 中所示,验证帐户是否已成功创建。

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

可以通过属性、 WizardSteps和 事件处理程序以多种方式自定义 CreateUserWizard。 在本部分中,我们将了解如何通过控件的属性自定义控件的外观;下一部分将介绍如何通过事件处理程序扩展控件的行为。

几乎,CreateUserWizard 控件的默认用户界面中显示的所有文本都可以通过其大量属性进行自定义。 例如,文本框左侧显示的“用户名”、“密码”、“确认密码”、“电子邮件”、“安全问题”和“安全答案”标签可以分别由 UserNameLabelTextEmailLabelTextQuestionLabelTextPasswordLabelTextConfirmPasswordLabelText、 和 AnswerLabelText 属性进行自定义。 同样,还有一些属性用于指定 和 CompleteWizardStep中的CreateUserWizardStep“创建用户”和“继续”按钮的文本,以及这些按钮是否呈现为 Buttons、LinkButtons 或 ImageButtons。

颜色、边框、字体和其他视觉元素可通过大量样式属性进行配置。 CreateUserWizard 控件本身具有常见的 Web 控件样式属性 (、BackColorBorderStyleCssClassFont、 等),并且有许多样式属性用于定义 CreateUserWizard 接口的特定部分的外观。 例如, 属性TextBoxStyle定义 中CreateUserWizardStep文本框的样式,而 TitleTextStyle 属性定义标题 (“注册新帐户”) 的样式。

除了与外观相关的属性外,还有许多属性会影响 CreateUserWizard 控件的行为。 属性 DisplayCancelButton如果设置为 True)在“创建用户”按钮旁边显示“取消”按钮, (默认值为 False) 。 如果显示“取消”按钮,请务必同时设置 CancelDestinationPageUrl 属性,该属性指定在单击“取消”后将用户发送到的页面。 如上一部分所述,界面中的 CompleteWizardStep“继续”按钮会导致回发,但使访问者位于同一页面上。 若要在单击“继续”按钮后将访问者发送到其他页面,只需在 属性中ContinueDestinationPageUrl指定 URL 即可。

让我们更新 RegisterUser CreateUserWizard 控件以显示“取消”按钮,并在单击“取消”或“继续”按钮时将访问者 Default.aspx 发送到 。 为此,请将 DisplayCancelButton 属性设置为 True, CancelDestinationPageUrl 并将 和 ContinueDestinationPageUrl 属性都设置为“~/Default.aspx”。 图 14 显示了通过浏览器查看时更新的 CreateUserWizard。

CreateUserWizardStep 包括取消按钮

图 14:包括 CreateUserWizardStep “取消”按钮 (单击以查看全尺寸图像)

当访问者输入用户名、密码、电子邮件地址和安全问题和答案并单击“创建用户”时,将创建一个新的用户帐户,并且访问者将作为新创建的用户登录。 假设访问页面的人员正在为自己创建新帐户,这可能是所需行为。 但是,你可能希望允许管理员添加新的用户帐户。 执行此操作时,将创建用户帐户,但管理员将保持以管理员 (身份登录,而不是作为新创建的帐户) 登录。 可以通过布尔 LoginCreatedUser 属性修改此行为。

成员资格框架中的用户帐户包含已批准的标志;未获批准的用户无法登录到站点。 默认情况下,新创建的帐户标记为“已批准”,允许用户立即登录到站点。 但是,有可能将新用户帐户标记为未批准。 你可能希望管理员先手动批准新用户才能登录;或者,在允许用户登录之前,你可能想要验证在注册时输入的电子邮件地址是否有效。 无论情况如何,通过将 CreateUserWizard 控件的 DisableCreatedUser 属性 设置为 True,可以将新创建的用户帐户标记为未批准, (默认值为 False) 。

注意的其他行为相关属性包括 AutoGeneratePasswordMailDefinitionAutoGeneratePassword如果 属性设置为 True,则 CreateUserWizardStep 不会显示“密码”和“确认密码”文本框;而是使用 MembershipGeneratePassword的 方法自动生成新创建的用户的密码。 方法 GeneratePassword 构造一个具有指定长度且具有足够数量的非字母数字字符的密码,以满足配置的密码强度要求。

如果要将电子邮件发送到帐户创建过程中指定的电子邮件地址,则 属性非常有用。MailDefinition 属性 MailDefinition 包括一系列子属性,用于定义有关构造的电子邮件的信息。 这些子属性包括 、、PriorityIsBodyHtmlFromCCBodyFileNameSubject选项。 属性BodyFileName指向包含电子邮件正文的文本或 HTML 文件。 正文支持两个预定义的占位符: <%UserName%><%Password%>。 如果文件中存在 BodyFileName 这些占位符,则将替换为刚刚创建的用户名和密码。

注意

控件 CreateUserWizardMailDefinition 属性仅指定有关创建新帐户时发送的电子邮件的详细信息。 它不包括电子邮件的实际发送方式的任何详细信息, (,即是否使用 SMTP 服务器或邮件放置目录,任何身份验证信息,等等) 。 需要在 中的 <system.net>Web.config部分定义这些低级别详细信息。 有关这些配置设置以及从 ASP.NET 2.0 发送电子邮件的一般信息,请参阅 SystemNetMail.com 中的常见问题解答和我的文章“在 ASP.NET 2.0 中发送Email”。

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

CreateUserWizard 控件在其工作流期间引发大量事件。 例如,在访问者输入其用户名、密码和其他相关信息并单击“创建用户”按钮后,CreateUserWizard 控件将引发其 CreatingUser 事件。 如果在创建过程中出现问题,则会CreateUserError触发事件;但是,如果成功创建用户,则会引发CreatedUser事件。 会引发其他 CreateUserWizard 控件事件,但这些事件是最一元的三个。

在某些情况下,我们可能需要利用 CreateUserWizard 工作流,为此,我们可以为相应事件创建事件处理程序。 为了说明这一点,让我们增强 RegisterUser CreateUserWizard 控件,以包含对用户名和密码的一些自定义验证。 具体而言,让我们增强 CreateUserWizard,以便用户名不能包含前导空格或尾随空格,并且用户名不能出现在密码中的任何位置。 简言之,我们希望阻止某人创建“Scott”之类的用户名,或创建“Scott”和“Scott.1234”等用户名/密码组合。

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

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

</asp:Label>

接下来,为 CreateUserWizard 控件的 CreatingUser 事件创建事件处理程序。 若要创建事件处理程序,请在Designer中选择控件,然后转到属性窗口。 在此处,单击闪电图标,然后双击相应的事件以创建事件处理程序。

将以下代码添加到 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 属性提供。 我们在上述事件处理程序中使用这些属性来确定提供的用户名是否包含前导空格或尾随空格,以及用户名是否在密码中找到。 如果满足上述任一条件,则 Label 中 InvalidUserNameOrPasswordMessage 会显示一条错误消息,并且事件处理程序的 e.Cancel 属性设置为 true。 如果 e.Cancel 设置为 true,则 CreateUserWizard 会使其工作流短路,从而有效地取消用户帐户创建过程。

图 15 显示了用户输入带前导空格的用户名时的屏幕截图 CreatingUserAccounts.aspx

不允许使用带前导空格或尾随空格的用户名

图 15:不允许具有前导空格或尾随空格的用户名 (单击以查看全尺寸图像)

注意

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

摘要

MembershipCreateUser 方法在 Membership 框架中创建新的用户帐户。 它通过委托对已配置的成员资格提供程序的调用来执行此操作。 对于 , SqlMembershipProvider方法将 CreateUser 记录添加到 aspnet_Usersaspnet_Membership 数据库表。

虽然可以在步骤 5) 中以编程方式 (创建新用户帐户,但使用 CreateUserWizard 控件是更快、更简单的方法。 此控件呈现一个多步骤用户界面,用于收集用户信息和在成员资格框架中创建新用户。 在后台,此控件使用步骤 5 中检查的相同 Membership.CreateUser 方法,但控件创建用户界面、验证控件并响应用户帐户创建错误,而无需编写代码。

此时,我们已具备创建新用户帐户的功能。 但是,登录页仍在针对我们在第二个教程中指定的那些硬编码凭据进行验证。 在下一教程中,我们将更新 Login.aspx 以根据成员资格框架验证用户提供的凭据。

编程愉快!

深入阅读

有关本教程中讨论的主题的详细信息,请参阅以下资源:

关于作者

Scott Mitchell 是多本 ASP/ASP.NET 书籍的作者,4GuysFromRolla.com 的创始人,自 1998 年以来一直从事 Microsoft Web 技术工作。 Scott 担任独立顾问、培训师和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 2.0自学。 可在 上或通过他的博客http://ScottOnWriting.NET联系 mitchell@4guysfromrolla.com Scott。

特别感谢...

本教程系列由许多有用的审阅者查看。 本教程的首席审阅者是 Teresa Murphy。 有兴趣查看我即将发布的 MSDN 文章? 如果是,请在 处放置一行 mitchell@4GuysFromRolla.com