存储其他用户信息 (VB)

作者 :Scott Mitchell

注意

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

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

下载代码下载 PDF

在本教程中,我们将通过构建一个非常基本的来宾簿应用程序来回答这个问题。 在执行此操作时,我们将查看用于在数据库中为用户信息建模的不同选项,然后了解如何将此数据与成员资格框架创建的用户帐户相关联。

简介

Asp。NET 的成员资格框架提供了用于管理用户的灵活界面。 成员资格 API 包括用于验证凭据、检索有关当前登录用户的信息、创建新用户帐户和删除用户帐户等的方法。 成员资格框架中的每个用户帐户仅包含验证凭据和执行与用户帐户相关的基本任务所需的属性。 类的方法和属性证明了这一点,这些方法和属性MembershipUser为成员资格框架中的用户帐户建模。 此类具有 、、 和 IsLockedOutEmailUserName属性,以及 和 UnlockUserGetPassword方法。

通常,应用程序需要存储未包含在成员资格框架中的其他用户信息。 例如,在线零售商可能需要让每个用户存储她的送货和帐单邮寄地址、付款信息、送货首选项和联系人电话号码。 此外,系统中的每个订单都与特定的用户帐户相关联。

MembershipUser不包括 或 DeliveryPreferencesPastOrdersPhoneNumber属性。 那么,我们如何跟踪应用程序所需的用户信息并将其与成员资格框架集成? 在本教程中,我们将通过构建一个非常基本的来宾簿应用程序来回答这个问题。 在执行此操作时,我们将查看用于在数据库中为用户信息建模的不同选项,然后了解如何将此数据与成员资格框架创建的用户帐户相关联。 现在就开始吧!

步骤 1:创建来宾簿应用程序的数据模型

可以使用多种技术来捕获数据库中的用户信息,并将其与成员资格框架创建的用户帐户相关联。 为了说明这些技术,我们需要扩充教程 Web 应用程序,使其捕获某种与用户相关的数据。 (应用程序的数据模型目前仅 SqlMembershipProvider包含 .)

让我们创建一个非常简单的留言簿应用程序,经过身份验证的用户可在其中留下批注。 除了存储留言簿评论之外,还允许每个用户存储其家乡、主页和签名。 如果提供,用户的家乡、主页和签名将显示在留言簿中留下的每条消息上。

GuestbookComments添加表

为了捕获留言簿注释,我们需要创建一个名为 GuestbookComments 的数据库表,其中包含 、、 SubjectBodyCommentDateCommentId列。 我们还需要让表中的每条记录 GuestbookComments 引用留下批注的用户。

若要将此表添加到数据库,请转到 Visual Studio 中的“数据库资源管理器”并向下钻取到数据库 SecurityTutorials 。 右键单击“表”文件夹,然后选择“添加新表”。 这会显示一个接口,该接口允许我们定义新表的列。

向 SecurityTutorials 数据库添加新表

图 1:向数据库添加新表 SecurityTutorials (单击以查看全尺寸图像)

接下来,定义 GuestbookComments的 列。 首先添加类型uniqueidentifier为 的列CommentId。 此列将唯一标识来宾簿中的每个注释,因此不允许 NULL 将其标记为表的主键。 我们可以通过将列的默认值设置为 NEWID()来指示应为此字段INSERT自动生成新值,而不是为每个 INSERT上的字段提供CommentId一个uniqueidentifier值。 添加第一个字段,将其标记为主键并设置其默认值后,屏幕应类似于图 2 所示的屏幕截图。

添加名为 CommentId 的主列

图 2:添加名为 CommentId 的主列 (单击以查看全尺寸图像)

接下来,添加类型nvarchar(50)为 的列Subject和类型 nvarchar(MAX)的名为 Body 的列,不允许NULL在两列中使用 。 之后,添加类型为 的datetimeCommentDate。 禁止 NULL ,并将 CommentDate 列的默认值设置为 getdate()

剩下的就是添加一个列,将用户帐户与每个留言簿注释相关联。 一种选择是添加类型为 的nvarchar(256)UserName。 当使用成员资格提供程序(而不是 )时, SqlMembershipProvider这是一个合适的选择。 但是,使用 时,SqlMembershipProvider如本教程系列中所述,表中的UserNameaspnet_Users列不保证是唯一的。 表 aspnet_Users 的主键的类型为 UserIduniqueidentifier。 因此,该 GuestbookComments 表需要一个名为 的列 UserId 类型 uniqueidentifier (不允许 NULL 值) 。 继续并添加此列。

注意

在 SQL Server 中创建成员资格架构教程中所述,成员资格框架旨在使具有不同用户帐户的多个 Web 应用程序能够共享同一个用户存储。 它通过将用户帐户分区到不同的应用程序来执行此操作。 虽然保证每个用户名在应用程序中都是唯一的,但同一用户名可以在使用同一用户存储的不同应用程序中使用。 和 ApplicationId 字段的aspnet_Users表中UserName有一个复合约束,但不只是字段上有UserName一个约束UNIQUE。 因此,aspnet_Users表可能会有两个 (或更多个具有相同 UserName 值的) 记录。 但是,表的UserId字段有一个UNIQUE约束 aspnet_Users (,因为它是主键) 。 约束UNIQUE很重要,因为没有约束,我们就无法在 和 aspnet_Users 表之间建立GuestbookComments外键约束。

添加列 UserId 后,通过单击工具栏中的“保存”图标来保存表。 将新表 GuestbookComments命名为 。

最后一个问题需要处理GuestbookComments表:需要在列和aspnet_Users.UserId列之间GuestbookComments.UserId创建外键约束。 若要实现此目的,请单击工具栏中的“关系”图标以启动“外键关系”对话框。 (或者,可以通过转到“表Designer”菜单并选择“关系”来启动此对话框 )

单击“外键关系”对话框左下角的“添加”按钮。 这将添加新的外键约束,尽管我们仍然需要定义参与关系的表。

使用“外键关系”对话框管理表的外键约束

图 3:使用“外键关系”对话框管理表的外键约束 (单击以查看全尺寸图像)

接下来,单击右侧“表和列规范”行中的省略号图标。 这将启动“表和列”对话框,我们可以从中指定主键表和列以及表中的 GuestbookComments 外键列。 具体而言,选择 aspnet_UsersUserId 作为主键表和列,并从UserIdGuestbookComments表作为外键列 (请参阅图 4) 。 定义主键表和外键表和列后,单击“确定”返回到“外键关系”对话框。

在 aspnet_Users 和 GuesbookComments 表之间建立外键约束

图 4:在 和 GuesbookComments 表之间建立aspnet_Users外键约束 (单击 以查看全尺寸图像)

此时,已建立外键约束。 此约束的存在通过保证永远不会有引用不存在用户帐户的 guestbook 条目来确保两个表之间的关系 完整性 。 默认情况下,如果存在相应的子记录,则外键约束将禁止删除父记录。 也就是说,如果用户发表了一个或多个来宾簿注释,然后我们尝试删除该用户帐户,除非先删除他的来宾簿注释,否则删除将失败。

外键约束可以配置为在删除父记录时自动删除关联的子记录。 换句话说,我们可以设置此外键约束,以便在删除用户的用户帐户时自动删除用户的来宾簿条目。 若要完成此操作,请展开“INSERT and UPDATE 规范”部分,并将“删除规则”属性设置为 Cascade。

配置外键约束以级联删除

图 5:将外键约束配置为级联删除 (单击以查看全尺寸图像)

若要保存外键约束,请单击“关闭”按钮退出外键关系。 然后单击工具栏中的“保存”图标以保存表和此关系。

存储用户的家乡、主页和签名

GuestbookComments 表说明了如何存储与用户帐户共享一对多关系的信息。 由于每个用户帐户可能具有任意数量的关联注释,因此此关系通过创建一个表来建模,用于保存包含将每个注释链接回特定用户的列的注释集。 使用 时,SqlMembershipProvider最好通过创建类型uniqueidentifier为 的UserId列以及此列和 aspnet_Users.UserId之间的外键约束来建立此链接。

现在,我们需要将三列与每个用户帐户相关联,以存储用户的家乡、主页和签名,这些内容将显示在他的留言簿注释中。 可通过多种不同方法实现此目的:

  • 将新列添加到aspnet_Usersaspnet_Membership表。 我不建议使用此方法,因为它会修改 使用的 SqlMembershipProvider架构。 这个决定可能会回来困扰你。 例如,如果 ASP.NET 的未来版本使用不同的 SqlMembershipProvider 架构,该怎么办。 Microsoft 可能包含用于将 ASP.NET 2.0 SqlMembershipProvider 数据迁移到新架构的工具,但如果修改了 ASP.NET 2.0 SqlMembershipProvider 架构,则可能无法进行此类转换。

  • 使用 ASP。NET 的配置文件框架,用于定义家乡、主页和签名的配置文件属性。 ASP.NET 包括用于存储其他用户特定数据的配置文件框架。 与成员资格框架一样,配置文件框架是在提供程序模型之上生成的。 .NET Framework附带一个 ,SqlProfileProvider用于将配置文件数据存储在SQL Server数据库中。 事实上,我们的数据库已经有 () 使用的SqlProfileProvider表,就像我们在创建成员aspnet_Profile资格架构教程中重新添加应用程序服务时添加的表SQL Server一样。
    配置文件框架main优点是,它允许开发人员在 中Web.config定义配置文件属性 ,无需编写代码即可将配置文件数据序列化到基础数据存储区以及从基础数据存储区序列化。 简言之,定义一组配置文件属性并在代码中使用这些属性非常简单。 但是,在版本控制方面,配置文件系统留下很多需要,因此,如果你有一个应用程序,其中你希望以后添加新的用户特定属性,或者现有属性要删除或修改,那么配置文件框架可能不是最佳选择。 此外,以 SqlProfileProvider 高度非规范化的方式存储配置文件属性,因此几乎不可能直接针对配置文件数据 (运行查询,例如,有多少用户拥有纽约的家乡) 。
    有关配置文件框架的详细信息,请参阅本教程末尾的“进一步阅读”部分。

  • 将这三列添加到数据库中的新表中,并建立此表与aspnet_Users 此方法涉及的工作比使用配置文件框架要多一点,但在如何在数据库中建模其他用户属性方面提供了最大的灵活性。 这是我们将在本教程中使用的选项。

我们将创建一个名为 UserProfiles 的新表,用于保存每个用户的家乡、主页和签名。 右键单击“数据库资源管理器”窗口中的“表”文件夹,然后选择创建新表。 将第一列 UserId 命名为 ,并将其类型设置为 uniqueidentifier。 禁止 NULL 值并将列标记为主键。 接下来,添加名为 的列:HomeTown,类型 HomepageUrlnvarchar(50)nvarchar(100);类型为 的,以及 类型nvarchar(500)为 的签名。 这三列中的每一列都可以接受一个 NULL 值。

创建 UserProfiles 表

图 6:创建 UserProfiles 表 (单击以查看全尺寸图像)

保存表并将其命名为 UserProfiles。 最后,在表的UserId字段和 aspnet_Users.UserId 字段之间建立UserProfiles外键约束。 正如我们对 和 aspnet_Users 表之间的GuestbookComments外键约束所做的那样,请删除此约束级联。 由于 中的 UserIdUserProfiles 字段是主键,这可确保表中每个用户帐户的记录 UserProfiles 不超过一条。 这种类型的关系称为一对一。

创建数据模型后,就可以使用它了。 在步骤 2 和 3 中,我们将了解当前登录的用户如何查看和编辑其家乡、主页和签名信息。 在步骤 4 中,我们将为经过身份验证的用户创建界面,以便向来宾簿提交新评论并查看现有评论。

步骤 2:显示用户的家乡、主页和签名

有多种方法允许当前登录的用户查看和编辑其家乡、主页和签名信息。 可以使用 TextBox 和 Label 控件手动创建用户界面,也可以使用其中一个数据 Web 控件,例如 DetailsView 控件。 若要执行数据库 SELECTUPDATE 语句,我们可以在页面的代码隐藏类中编写 ADO.NET 代码,或者对 SqlDataSource 使用声明性方法。 理想情况下,应用程序将包含分层体系结构,可以从页面的代码隐藏类以编程方式调用该体系结构,也可以通过 ObjectDataSource 控件以声明方式调用该体系结构。

由于本教程系列侧重于表单身份验证、授权、用户帐户和角色,因此不会全面讨论这些不同的数据访问选项,或者为何分层体系结构优先于直接从 ASP.NET 页执行 SQL 语句。 我将演练如何使用 DetailsView 和 SqlDataSource(最快速、最简单的选项),但讨论的概念当然可以应用于替代 Web 控件和数据访问逻辑。 有关在 ASP.NET 中使用数据的详细信息,请参阅我在 ASP.NET 2.0 中使用数据 教程系列。

AdditionalUserInfo.aspx打开 文件夹中的页面Membership,将 DetailsView 控件添加到页面,将其 ID 属性UserProfile设置为 ,并清除其 WidthHeight 属性。 展开 DetailsView 的智能标记,然后选择将其绑定到新的数据源控件。 这将启动 DataSource 配置向导 (请参阅图 7) 。 第一步要求指定数据源类型。 由于我们将直接连接到 SecurityTutorials 数据库,因此请选择“数据库”图标,并将 ID 指定为 UserProfileDataSource

添加新的 SqlDataSource 控件,名为 UserProfileDataSource

图 7:添加名为 UserProfileDataSource 的新 SqlDataSource 控件 (单击以查看全尺寸图像)

下一个屏幕提示数据库使用。 我们已在 中Web.configSecurityTutorials数据库定义了一个连接字符串。 此连接字符串名称 – SecurityTutorialsConnectionString 应位于下拉列表中。 选择此选项并单击“下一步”。

从 Drop-Down 列表中选择 SecurityTutorialsConnectionString

图 8:从“Drop-Down 列表” SecurityTutorialsConnectionString 中选择 (单击以查看全尺寸图像)

后续屏幕要求我们指定要查询的表和列。 UserProfiles从下拉列表中选择表,检查所有列。

从 UserProfiles 表恢复所有列

图 9:从 UserProfiles 表恢复所有列 (单击以查看全尺寸图像)

图 9 中的当前查询返回 中的所有UserProfiles记录,但我们只对当前登录的用户的记录感兴趣。 若要添加 WHERE 子句,请单击 WHERE 按钮打开“添加 WHERE 子句”对话框, (请参阅图 10) 。 可以在此处选择要筛选的列、运算符和筛选器参数的源。 选择作为列,选择 UserId “=”作为运算符。

遗憾的是,没有内置参数源可返回当前登录的用户 UserId 的值。 我们需要以编程方式获取此值。 因此,将“源”下拉列表设置为“无”,单击“添加”按钮添加参数,然后单击“确定”。

在 UserId 列上添加筛选器参数

图 10:在 UserId 列上添加筛选器参数 (单击以查看全尺寸图像)

单击“确定”后,将返回到图 9 所示的屏幕。 但是,这一次,屏幕底部的 SQL 查询应包含 子 WHERE 句。 单击“下一步”转到“测试查询”屏幕。 在这里,可以运行查询并查看结果。 单击“完成”以完成向导。

完成 DataSource 配置向导后,Visual Studio 会基于向导中指定的设置创建 SqlDataSource 控件。 此外,它还手动将 BoundFields 添加到由 SqlDataSource 返回的每个列的 SelectCommandDetailsView。 无需在 DetailsView 中显示 UserId 字段,因为用户不需要知道此值。 可以直接从 DetailsView 控件的声明性标记中删除此字段,也可以单击其智能标记中的“编辑字段”链接。

此时,页面的声明性标记应如下所示:

<asp:DetailsView ID="UserProfile" runat="server"
          AutoGenerateRows="False" DataKeyNames="UserId"

          DataSourceID="UserProfileDataSource">
     <Fields>
          <asp:BoundField DataField="HomeTown" HeaderText="HomeTown"
               SortExpression="HomeTown" />
          <asp:BoundField DataField="HomepageUrl" HeaderText="HomepageUrl"

               SortExpression="HomepageUrl" />
          <asp:BoundField DataField="Signature" HeaderText="Signature"
               SortExpression="Signature" />
     </Fields>

</asp:DetailsView>
<asp:SqlDataSource ID="UserProfileDataSource" runat="server"
          ConnectionString="<%$ ConnectionStrings:SecurityTutorialsConnectionString %>"
          SelectCommand="SELECT [UserId], [HomeTown], [HomepageUrl], [Signature] FROM
          [UserProfiles] WHERE ([UserId] = @UserId)">
     <SelectParameters>

          <asp:Parameter Name="UserId" Type="Object" />
     </SelectParameters>
</asp:SqlDataSource>

在选择数据之前,我们需要以编程方式将 SqlDataSource 控件 UserId 的参数设置为当前登录用户的 UserId 。 为此,可以为 SqlDataSource 的事件 Selecting 创建事件处理程序,并在其中添加以下代码:

Protected Sub UserProfileDataSource_Selecting(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.SqlDataSourceSelectingEventArgs) Handles UserProfileDataSource.Selecting
     ' Get a reference to the currently logged on user
     Dim currentUser As MembershipUser = Membership.GetUser()

     ' Determine the currently logged on user's UserId value
     Dim currentUserId As Guid = CType(currentUser.ProviderUserKey, Guid)

     ' Assign the currently logged on user's UserId to the @UserId parameter
     e.Command.Parameters("@UserId").Value = currentUserId
End Sub

上述代码首先通过调用 Membership 类的 GetUser 方法获取对当前登录用户的引用。 这将返回一个 MembershipUser 对象,其 ProviderUserKey 属性包含 UserIdUserId然后将该值分配给 SqlDataSource 的 @UserId 参数。

注意

方法 Membership.GetUser() 返回有关当前登录用户的信息。 如果匿名用户访问页面,它将返回值 Nothing。 在这种情况下,在尝试读取 ProviderUserKey 属性时,这将导致NullReferenceException以下代码行上的 。 当然,我们不必担心在页面中返回任何内容AdditionalUserInfo.aspxMembership.GetUser()因为我们在上一教程中配置了 URL 授权,以便只有经过身份验证的用户才能访问此文件夹中的 ASP.NET 资源。 如果需要在允许匿名访问的页面中访问有关当前登录用户的信息,请确保检查MembershipUser从 方法返回GetUser()的对象不是 Nothing,然后再引用其属性。

如果通过浏览器访问页面, AdditionalUserInfo.aspx 你将看到一个空白页,因为我们尚未向 UserProfiles 表添加任何行。 在步骤 6 中,我们将了解如何自定义 CreateUserWizard 控件,以在创建新用户帐户时自动向 UserProfiles 表添加新行。 但是,目前需要在表中手动创建记录。

在 Visual Studio 中导航到“数据库资源管理器”并展开“表”文件夹。 右键单击 aspnet_Users 表并选择“显示表数据”以查看表中的记录;对 UserProfiles 表执行相同操作。 图 11 显示了垂直平铺时的这些结果。 在我的数据库中,目前 aspnet_Users 有 Bruce、Fred 和 Tito 的记录,但表中没有记录 UserProfiles

显示aspnet_Users和 UserProfiles 表的内容

图 11:显示 和 UserProfiles 表的内容 aspnet_Users (单击以查看全尺寸图像)

通过手动键入 、 HomepageUrlSignature 字段的值HomeTown,将新记录添加到UserProfiles表中。 在新记录中获取有效UserId值的最简单方法是从表中的特定用户帐户aspnet_Users中选择UserId字段,并将其复制并粘贴到 UserId 中的 UserProfiles字段中。UserProfiles 图 12 显示了 UserProfiles 为 Bruce 添加新记录后的表。

已将记录添加到 Bruce 的 UserProfiles

图 12:已为 Bruce 添加了 UserProfiles 记录 (单击以查看全尺寸图像)

返回到 AdditionalUserInfo.aspx page以 Bruce 身份登录的 。 如图 13 所示,显示了 Bruce 的设置。

显示当前正在访问的用户的设置

图 13:显示当前正在访问的用户的设置 (单击以查看全尺寸图像)

注意

继续为每个成员身份用户手动添加表中的记录 UserProfiles 。 在步骤 6 中,我们将了解如何自定义 CreateUserWizard 控件,以在创建新用户帐户时自动向 UserProfiles 表添加新行。

步骤 3:允许用户编辑其家乡、主页和签名

此时,当前登录的用户可以查看其家乡、主页和签名设置,但尚无法对其进行修改。 让我们更新 DetailsView 控件,以便可以编辑数据。

我们需要做的第一件事是为 SqlDataSource 添加 UpdateCommand ,指定要 UPDATE 执行的语句及其相应的参数。 选择“SqlDataSource”,然后从属性窗口单击 UpdateQuery 属性旁边的省略号,打开“命令和参数编辑器”对话框。 在文本框中输入以下 UPDATE 语句:

UPDATE UserProfiles SET
     HomeTown = @HomeTown,
     HomepageUrl = @HomepageUrl,
     Signature = @Signature
WHERE UserId = @UserId

接下来,单击“刷新参数”按钮,这将在 SqlDataSource 控件的 UpdateParameters 集合中为 语句中的每个 UPDATE 参数创建一个参数。 将所有参数的源保留为“无”,然后单击“确定”按钮完成对话框。

指定 SqlDataSource 的 UpdateCommand 和 UpdateParameters

图 14:指定 SqlDataSource 的 UpdateCommandUpdateParameters (单击以查看全尺寸图像)

由于我们对 SqlDataSource 控件进行了添加,DetailsView 控件现在可以支持编辑。 在 DetailsView 的智能标记中,检查“启用编辑”复选框。 这会将 CommandField 添加到控件的 Fields 集合,其 ShowEditButton 属性设置为 True。 当 DetailsView 在只读模式下显示时,这将呈现一个“编辑”按钮,在编辑模式下显示“更新”和“取消”按钮。 不过,通过将 DetailsView 控件的 DefaultMode 属性Edit设置为 ,我们可以使 DetailsView 呈现处于“始终可编辑”状态,而不是要求用户单击“编辑”。

通过这些更改,DetailsView 控件的声明性标记应如下所示:

<asp:DetailsView ID="UserProfile" runat="server"

          AutoGenerateRows="False" DataKeyNames="UserId"
          DataSourceID="UserProfileDataSource" DefaultMode="Edit">
     <Fields>
          <asp:BoundField DataField="HomeTown" HeaderText="HomeTown"

               SortExpression="HomeTown" />
          <asp:BoundField DataField="HomepageUrl" HeaderText="HomepageUrl"
               SortExpression="HomepageUrl" />
          <asp:BoundField DataField="Signature" HeaderText="Signature"

               SortExpression="Signature" />
          <asp:CommandField ShowEditButton="True" />
     </Fields>
</asp:DetailsView>

请注意 CommandField 和 属性的 DefaultMode 添加。

继续通过浏览器测试此页面。 访问的用户时,在 中 UserProfiles具有相应记录,该用户的设置将显示在可编辑界面中。

DetailsView 呈现可编辑的接口

图 15:详细信息视图呈现可编辑的界面 (单击以查看全尺寸图像)

尝试更改值并单击“更新”按钮。 似乎什么也没发生。 有一个回发,值保存到数据库,但没有视觉反馈发生保存。

若要解决此问题,请返回到 Visual Studio 并在 DetailsView 上方添加一个 Label 控件。 将其 ID 设置为 ,其Text属性设置为“已更新设置”,其 VisibleEnableViewState 属性设置为 FalseSettingsUpdatedMessage

<asp:Label ID="SettingsUpdatedMessage" runat="server"
     Text="Your settings have been updated."
     EnableViewState="false"
     Visible="false">
</asp:Label>

每当更新 DetailsView 时,都需要显示 SettingsUpdatedMessage 标签。 为此,请为 DetailsView 的事件 ItemUpdated 创建事件处理程序并添加以下代码:

Protected Sub UserProfile_ItemUpdated(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.DetailsViewUpdatedEventArgs) Handles UserProfile.ItemUpdated
     SettingsUpdatedMessage.Visible = True
End Sub

通过浏览器返回到 AdditionalUserInfo.aspx 页面并更新数据。 这一次会显示一条有用的状态消息。

更新设置时显示一条短消息

图 16:更新设置时显示一条简短消息 (单击以查看全尺寸图像)

注意

DetailsView 控件的编辑界面需要很多内容。 它使用标准大小的文本框,但“签名”字段可能是多行文本框。 应使用 RegularExpressionValidator 来确保主页 URL(如果输入)以“http://”或“https://”开头。 此外,由于 DetailsView 控件的 DefaultMode 属性设置为 Edit,因此“取消”按钮不执行任何操作。 应将其删除,或者在单击时将用户重定向到其他页面 (,例如 ~/Default.aspx) 。 我将这些增强功能保留为读者练习。

目前,该网站不提供指向该 AdditionalUserInfo.aspx 页面的任何链接。 访问它的唯一方法是直接在浏览器的地址栏中输入页面的 URL。 让我们在 Site.master 母版页中添加指向此页的链接。

回想一下,母版页在其 LoginContent ContentPlaceHolder 中包含 LoginView Web 控件,该控件为经过身份验证的访问者和匿名访问者显示不同的标记。 更新 LoginView 控件的 LoggedInTemplate 以包含指向 AdditionalUserInfo.aspx 页面的链接。 进行这些更改后,LoginView 控件的声明性标记应如下所示:

<asp:LoginView ID="LoginView1" runat="server">
     <LoggedInTemplate>
          Welcome back,
          <asp:LoginName ID="LoginName1" runat="server" />.

          <br />
          <asp:HyperLink ID="lnkUpdateSettings" runat="server" 
               NavigateUrl="~/Membership/AdditionalUserInfo.aspx">
               Update Your Settings</asp:HyperLink>
     </LoggedInTemplate>
     <AnonymousTemplate>

          Hello, stranger.
     </AnonymousTemplate>
</asp:LoginView>

请注意,将 HyperLink 控件添加到 lnkUpdateSettingsLoggedInTemplate 建立此链接后,经过身份验证的用户可以快速跳转到页面以查看和修改其家乡、主页和签名设置。

步骤 4:添加新的留言簿注释

通过 Guestbook.aspx 身份验证的用户可在该页面查看留言簿并留下评论。 让我们先创建接口来添加新的来宾簿注释。

Guestbook.aspx在 Visual Studio 中打开页面并构造一个由两个 TextBox 控件组成的用户界面,一个用于新批注的主题,一个用于其正文。 将第一个 TextBox 控件的 ID 属性设置为 Subject ,将其Columns属性设置为 40;将第二IDBody个 的 设置为 ,将其 TextMode 设置为 MultiLine,将 它的 WidthRows 属性分别设置为“95%”和 8。 若要完成用户界面,请添加名为 PostCommentButton 的 Button Web 控件,并将其 Text 属性设置为“发布注释”。

由于每个留言簿注释都需要主题和正文,因此请为每个 TextBox 添加 RequiredFieldValidator。 将这些 ValidationGroup 控件的 属性设置为“EnterComment”;同样,将 PostCommentButton 控件的 ValidationGroup 属性设置为“EnterComment”。 有关 ASP 的详细信息。NET 的验证控件在 ASP.NET 中检查表单验证

创建用户界面后,页面的声明性标记应如下所示:

<h3>Leave a Comment</h3>
<p>
     <b>Subject:</b>
     <asp:RequiredFieldValidator ID="SubjectReqValidator" runat="server"

          ErrorMessage="You must provide a value for Subject"
          ControlToValidate="Subject" ValidationGroup="EnterComment">
     </asp:RequiredFieldValidator><br />
     <asp:TextBox ID="Subject" Columns="40" runat="server"></asp:TextBox>

</p>
<p>
     <b>Body:</b>
     <asp:RequiredFieldValidator ID="BodyReqValidator" runat="server"
          ControlToValidate="Body"

          ErrorMessage="You must provide a value for Body" ValidationGroup="EnterComment">
     </asp:RequiredFieldValidator><br />
     <asp:TextBox ID="Body" TextMode="MultiLine" Width="95%"

          Rows="8" runat="server"></asp:TextBox>
</p>
<p>
     <asp:Button ID="PostCommentButton" runat="server" 

          Text="Post Your Comment"
          ValidationGroup="EnterComment" />
</p>

用户界面完成后,下一个任务是在单击 时PostCommentButtonGuestbookComments新记录插入表中。 这可以通过多种方式实现:我们可以在 Button 的Click事件处理程序中编写 ADO.NET 代码;我们可以将 SqlDataSource 控件添加到页面,配置它InsertCommand,然后从Click事件处理程序调用其Insert方法;或者我们可以生成一个负责插入新来宾簿注释的中间层,并从事件处理程序调用此功能Click。 由于我们在步骤 3 中介绍了如何使用 SqlDataSource,因此让我们在此处使用 ADO.NET 代码。

注意

用于以编程方式访问 Microsoft SQL Server 数据库中数据的 ADO.NET 类位于 命名空间中System.Data.SqlClient。 可能需要将此命名空间导入页面的代码隐藏类 (Imports System.Data.SqlClient 即) 。

PostCommentButton的事件 Click 创建事件处理程序,并添加以下代码:

Protected Sub PostCommentButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles PostCommentButton.Click
     If Not Page.IsValid Then Exit Sub

     ' Determine the currently logged on user's UserId
     Dim currentUser As MembershipUser = Membership.GetUser()
     Dim currentUserId As Guid = CType(currentUser.ProviderUserKey, Guid)

     ' Insert a new record into GuestbookComments
     Dim connectionString As String = 
          ConfigurationManager.ConnectionStrings("SecurityTutorialsConnectionString").ConnectionString
     Dim insertSql As String = "INSERT INTO GuestbookComments(Subject, Body, UserId)
          VALUES(@Subject, @Body, @UserId)"

     Using myConnection As New SqlConnection(connectionString)

          myConnection.Open()
          Dim myCommand As New SqlCommand(insertSql, myConnection)
          myCommand.Parameters.AddWithValue("@Subject", Subject.Text.Trim())
          myCommand.Parameters.AddWithValue("@Body", Body.Text.Trim())
          myCommand.Parameters.AddWithValue("@UserId", currentUserId)
          myCommand.ExecuteNonQuery()
          myConnection.Close()
     End Using

     ' "Reset" the Subject and Body TextBoxes

     Subject.Text = String.Empty
     Body.Text = String.Empty
End Sub

事件处理程序 Click 首先检查用户提供的数据是否有效。 否则,事件处理程序在插入记录之前退出。 假设提供的数据有效,则会检索当前登录用户 UserId 的值并将其存储在局部变量中 currentUserId 。 需要此值,因为在 将记录插入 时GuestbookComments必须提供UserId值。

之后,从Web.config中检索数据库的连接字符串SecurityTutorialsINSERT并指定 SQL 语句。 SqlConnection然后创建并打开对象。 接下来,构造一个 SqlCommand 对象,并分配查询中使用的 INSERT 参数的值。 INSERT然后执行 语句并关闭连接。 在事件处理程序结束时, Subject 将清除 和 Body TextBoxs 的属性 Text ,以便用户的值不会在整个回发中保留。

继续在浏览器中测试此页面。 由于此页面位于 文件夹中, Membership 因此匿名访问者无法访问它。 因此,如果尚未) ,则需要先登录 (。 在 SubjectBody TextBox 中输入一个值,然后单击该 PostCommentButton 按钮。 这将导致将新记录添加到 GuestbookComments。 回发时,将从 TextBox 中擦除你提供的主题和正文。

单击 PostCommentButton 该按钮后,没有将注释添加到来宾簿的视觉反馈。 我们仍然需要更新此页面以显示现有的留言簿注释,我们将在步骤 5 中执行此操作。 完成此操作后,刚刚添加的注释将显示在批注列表中,提供足够的视觉反馈。 现在,通过检查表的内容 GuestbookComments 确认是否已保存您的留言簿注释。

图 17 显示了在留下两条注释后表格的内容 GuestbookComments

可以在 GuestbookComments 表中查看 Guestbook 注释

图 17:可以在表中查看留言簿注释 GuestbookComments (单击以查看全尺寸图像)

注意

如果用户尝试插入包含潜在危险标记(如 HTML)的留言簿注释,ASP.NET 将引发 HttpRequestValidationException。 若要详细了解此异常、引发异常的原因以及如何允许用户提交潜在危险值,请参阅 请求验证白皮书

步骤 5:列出现有来宾簿注释

除了留下批注外,访问 Guestbook.aspx 页面的用户还应能够查看留言簿的现有批注。 为此,请将名为 的 CommentList ListView 控件添加到页面底部。

注意

ListView 控件是新 ASP.NET 版本 3.5。 它旨在以非常可自定义和灵活的布局显示项列表,但仍提供内置的编辑、插入、删除、分页和排序功能,如 GridView。 如果使用 ASP.NET 2.0,则需要改用 DataList 或 Repeater 控件。 有关使用 ListView 的详细信息,请参阅 Scott Guthrie 的博客文章 Asp:ListView 控件和我的文章 使用 ListView 控件显示数据

打开 ListView 的智能标记,然后从“选择数据源”下拉列表中将控件绑定到新的数据源。 正如我们在步骤 2 中看到的,这将启动数据源配置向导。 选择“数据库”图标,将生成的 SqlDataSource CommentsDataSource命名为 ,然后单击“确定”。 接下来,从下拉列表中选择SecurityTutorialsConnectionString连接字符串,然后单击“下一步”。

此时,在步骤 2 中,我们通过从下拉列表中选取 UserProfiles 表并选择要返回的列来指定要查询的数据, (参考图 9) 。 但是,这一次,我们希望创建一个 SQL 语句,该语句不仅从 中拉回记录,还提取 GuestbookComments注释者的家乡、主页、签名和用户名。 因此,选择“指定自定义 SQL 语句或存储过程”单选按钮,然后单击“下一步”。

此时会显示“定义自定义语句或存储过程”屏幕。 单击“查询生成器”按钮以图形方式生成查询。 查询生成器首先提示我们指定要从中查询的表。 选择 GuestbookCommentsUserProfilesaspnet_Users 表,然后单击“确定”。 这会将所有三个表添加到设计图面。 由于 、 UserProfilesaspnet_Users 表之间GuestbookComments存在外键约束,因此查询生成器会自动JOIN执行这些表。

剩下的就是指定要返回的列。 GuestbookComments从表中选择 SubjectBodyCommentDate 列;从UserProfiles表中返回 HomeTownHomepageUrlSignature 列;从 aspnet_Users返回 UserName 。 此外,在查询末尾SELECT添加“ORDER BY CommentDate DESC”,以便首先返回最新的帖子。 进行这些选择后,查询生成器界面应类似于图 18 中的屏幕截图。

构造的查询联接 GuestbookComments、UserProfiles 和 aspnet_Users 表

图 18:构造的查询 JOINGuestbookCommentsUserProfilesaspnet_Users 表 (单击以查看全尺寸图像)

单击“确定”关闭“查询生成器”窗口并返回到“定义自定义语句或存储过程”屏幕。 单击“下一步”转到“测试查询”屏幕,单击“测试查询”按钮可在此处查看查询结果。 准备就绪后,单击“完成”以完成“配置数据源”向导。

完成步骤 2 中的“配置数据源”向导后,关联的 DetailsView 控件的 Fields 集合已更新,以包含由 返回的每个列的 SelectCommandBoundField。 但是,ListView 保持不变;我们仍然需要定义其布局。 ListView 的布局可以通过其声明性标记或从其智能标记中的“配置 ListView”选项手动构造。 我通常更喜欢手动定义标记,但使用你最自然的方法。

我最终对 ListView 控件使用以下 LayoutTemplateItemTemplateItemSeparatorTemplate

<asp:ListView ID="CommentList" runat="server" DataSourceID="CommentsDataSource">

     <LayoutTemplate>
          <span ID="itemPlaceholder" runat="server" />
          <p>
               <asp:DataPager ID="DataPager1" runat="server">

                    <Fields>
                         <asp:NextPreviousPagerField ButtonType="Button" 
                              ShowFirstPageButton="True"
                              ShowLastPageButton="True" />
                    </Fields>
               </asp:DataPager>

          </p>
     </LayoutTemplate>
     <ItemTemplate>
          <h4>
               <asp:Label ID="SubjectLabel" runat="server" 
                    Text='<%# Eval("Subject") %>' />

          </h4>
          <asp:Label ID="BodyLabel" runat="server" 
               Text='<%# Eval("Body").ToString().Replace(Environment.NewLine, "<br />") %>' />
          <p>

          ---<br />
          <asp:Label ID="SignatureLabel" Font-Italic="true" runat="server"
               Text='<%# Eval("Signature") %>' />

          <br />
          <br />
          My Home Town:
          <asp:Label ID="HomeTownLabel" runat="server" 
               Text='<%# Eval("HomeTown") %>' />

          <br />
          My Homepage:
          <asp:HyperLink ID="HomepageUrlLink" runat="server" 
               NavigateUrl='<%# Eval("HomepageUrl") %>' 
               Text='<%# Eval("HomepageUrl") %>' />

          </p>
          <p align="center">
          Posted by
          <asp:Label ID="UserNameLabel" runat="server" 
               Text='<%# Eval("UserName") %>' /> 

          on
          <asp:Label ID="CommentDateLabel" runat="server" 
               Text='<%# Eval("CommentDate") %>' />
          </p>
     </ItemTemplate>

     <ItemSeparatorTemplate>
          <hr />
     </ItemSeparatorTemplate>
</asp:ListView>

定义 LayoutTemplate 控件发出的标记,而 ItemTemplate 呈现 SqlDataSource 返回的每个项。 ItemTemplate的 生成的标记放置在 的 itemPlaceholder 控件中LayoutTemplate。 除了 之外,itemPlaceholderLayoutTemplate还包括 DataPager 控件,该控件将 ListView 限制为每页仅显示 10 条留言簿注释, (默认) 并呈现分页界面。

“我 ItemTemplate ”在元素中 <h4> 显示每个留言簿注释的主题,其正文位于主题下方。 请注意,用于显示正文的语法采用 databinding 语句返回 Eval("Body") 的数据,将其转换为字符串,并将换行符 <br /> 替换为 元素。 需要此转换才能显示提交注释时输入的换行符,因为 HTML 会忽略空格。 用户的签名以斜体显示在正文下方,后跟用户的家乡、指向其主页的链接、评论的日期和时间以及留下评论的人员的用户名。

花点时间通过浏览器查看页面。 应在此处显示步骤 5 中添加到留言簿的批注。

Guestbook.aspx现在显示留言簿的批注

图 19Guestbook.aspx 现在显示留言簿的注释 (单击以查看全尺寸图像)

尝试向留言簿添加新批注。 单击按钮 PostCommentButton 后,页面会回发,注释将添加到数据库,但 ListView 控件不会更新以显示新注释。 可通过以下任一方法修复此问题:

  • 更新 PostCommentButton 按钮的 Click 事件处理程序,以便在将新注释插入数据库后调用 ListView 控件的 DataBind() 方法,或者
  • 将 ListView 控件的 EnableViewState 属性设置为 False。 此方法之所以有效,是因为通过禁用控件的视图状态,它必须在每次回发时重新绑定到基础数据。

可从本教程下载的教程网站演示了这两种方法。 ListView 控件的 EnableViewState 属性 False 以及以编程方式将数据重新绑定到 ListView 所需的代码存在于事件处理程序中 Click ,但被注释掉。

注意

目前,该 AdditionalUserInfo.aspx 页面允许用户查看和编辑其家乡、主页和签名设置。 更新以显示已登录用户的留言簿评论可能很好 AdditionalUserInfo.aspx 。 也就是说,除了检查和修改她的信息外,用户还可以访问页面 AdditionalUserInfo.aspx 以查看她过去所做的留言簿评论。 我留给感兴趣的读者做练习。

步骤 6:自定义 CreateUserWizard 控件以包括主页、主页和签名的接口

页面SELECT使用的Guestbook.aspx查询使用 INNER JOIN 来合并 、 UserProfilesaspnet_Users 表之间的GuestbookComments相关记录。 如果在 中UserProfiles没有记录的用户进行留言簿注释,则批注将不会显示在 ListView 中,因为 INNER JOIN 当 和 aspnet_UsersUserProfiles存在匹配的记录时, 仅返回GuestbookComments记录。 正如我们在步骤 3 中看到的,如果用户在中 UserProfiles 没有记录,则她无法在页面中查看或编辑她的设置 AdditionalUserInfo.aspx

不用说,由于我们的设计决策,成员资格系统中的每个用户帐户在表中都有匹配的 UserProfiles 记录非常重要。 我们希望在通过 CreateUserWizard 创建新的成员身份用户帐户时,将相应的记录添加到 UserProfiles

创建用户帐户 教程中所述,创建新的成员资格用户帐户后,CreateUserWizard 控件将引发其 CreatedUser 事件。 我们可以为此事件创建事件处理程序,获取刚创建的用户的 UserId,然后在表中插入具有 、 HomepageUrlSignature 列的默认值的HomeTown记录UserProfiles。 此外,还可以通过自定义 CreateUserWizard 控件的界面来包括其他 TextBox 来提示用户输入这些值。

让我们首先看看如何使用默认值向事件处理程序中的UserProfilesCreatedUser表添加新行。 之后,我们将了解如何自定义 CreateUserWizard 控件的用户界面,以包含其他表单字段来收集新用户的家乡、主页和签名。

将默认行添加到UserProfiles

创建用户帐户 教程中,我们向 CreatingUserAccounts.aspx 文件夹中的页面 Membership 添加了 CreateUserWizard 控件。 为了使 CreateUserWizard 控件在创建用户帐户时将记录添加到 UserProfiles 表中,我们需要更新 CreateUserWizard 控件的功能。 与其对 CreatingUserAccounts.aspx 页面进行这些更改,不如向页面添加新的 CreateUserWizard 控件 EnhancedCreateUserWizard.aspx ,并在那里对本教程进行修改。

EnhancedCreateUserWizard.aspx在 Visual Studio 中打开页面,并将 CreateUserWizard 控件从“工具箱”拖到该页上。 将 CreateUserWizard 控件的 ID 属性设置为 NewUserWizard。 正如我们在 创建用户帐户 教程中所述,CreateUserWizard 的默认用户界面会提示访问者输入必要的信息。 提供此信息后,控件会在成员资格框架中内部创建一个新用户帐户,无需编写一行代码。

CreateUserWizard 控件在其工作流期间引发大量事件。 访问者提供请求信息并提交表单后,CreateUserWizard 控件最初会触发其 CreatingUser 事件。 如果在创建过程中出现问题,则会CreateUserError触发事件;但是,如果成功创建用户,则会引发CreatedUser事件。 在 创建用户帐户 教程中,我们为事件创建了事件处理程序 CreatingUser ,以确保提供的用户名不包含任何前导空格或尾随空格,并且用户名未出现在密码中的任何位置。

为了在表中为刚刚创建的用户添加行 UserProfiles ,我们需要为 CreatedUser 事件创建事件处理程序。 触发事件时 CreatedUser ,已在成员资格框架中创建用户帐户,使我们能够检索帐户的 UserId 值。

NewUserWizardCreatedUser 事件创建事件处理程序,并添加以下代码:

Protected Sub NewUserWizard_CreatedUser(ByVal sender As Object, ByVal e As System.EventArgs) Handles NewUserWizard.CreatedUser
     ' Get the UserId of the just-added user
     Dim newUser As MembershipUser = Membership.GetUser(NewUserWizard.UserName)
     Dim newUserId As Guid = CType(newUser.ProviderUserKey, Guid)

     ' Insert a new record into UserProfiles
     Dim connectionString As String = 

          ConfigurationManager.ConnectionStrings("SecurityTutorialsConnectionString").ConnectionString
     Dim insertSql As String = "INSERT INTO UserProfiles(UserId, HomeTown, HomepageUrl,
          Signature) VALUES(@UserId, @HomeTown, @HomepageUrl, @Signature)"

     Using myConnection As New SqlConnection(connectionString)
          myConnection.Open()
          Dim myCommand As New SqlCommand(insertSql, myConnection)
          myCommand.Parameters.AddWithValue("@UserId", newUserId)
          myCommand.Parameters.AddWithValue("@HomeTown", DBNull.Value)

          myCommand.Parameters.AddWithValue("@HomepageUrl", DBNull.Value)
          myCommand.Parameters.AddWithValue("@Signature", DBNull.Value)
          myCommand.ExecuteNonQuery()
          myConnection.Close()
     End Using
End Sub

上述代码通过检索刚刚添加的用户帐户的 UserId。 这是通过使用 Membership.GetUser(username) 方法返回有关特定用户的信息,然后使用 ProviderUserKey 属性检索其 UserId 来实现的。 用户在 CreateUserWizard 控件中输入的用户名通过其 UserName 属性提供。

接下来,从 Web.config 中检索连接字符串并INSERT指定 语句。 将实例化必要的 ADO.NET 对象并执行命令。 代码将 实例分配给 、 和 参数,该参数的作用是HomeTown插入 、 HomepageUrlSignature 字段的数据库NULL值。@Signature@HomepageUrlDBNull@HomeTown

EnhancedCreateUserWizard.aspx通过浏览器访问页面并创建新的用户帐户。 执行此操作后,返回到 Visual Studio 并检查 和 UserProfiles 表的内容aspnet_Users, (如图 12) 所示。 你应该会在 中看到aspnet_Users新的用户帐户,并且 UserProfiles (、 NULLSignature) 的值HomeTownHomepageUrl对应的行。

添加了新的用户帐户和 UserProfiles 记录

图 20:添加新的用户帐户和 UserProfiles 记录 (单击以查看全尺寸图像)

访问者提供其新帐户信息并单击“创建用户”按钮后,将创建用户帐户,并将一行添加到 UserProfiles 表中。 然后,CreateUserWizard 会显示其 CompleteWizardStep,其中显示成功消息和“继续”按钮。 单击“继续”按钮会导致回发,但不执行任何操作,使用户停滞在 EnhancedCreateUserWizard.aspx 页面上。

通过 CreateUserWizard 控件的 属性单击“继续”按钮时,我们可以指定将用户发送到的 ContinueDestinationPageUrlURL。 将 ContinueDestinationPageUrl 属性设置为“~/Membership/AdditionalUserInfo.aspx”。 这会将新用户转到 AdditionalUserInfo.aspx,他们可以在其中查看和更新其设置。

自定义 CreateUserWizard 的界面以提示新用户的家乡、主页和签名

CreateUserWizard 控件的默认界面足以满足只需收集用户名、密码和电子邮件等核心用户帐户信息的简单帐户创建方案。 但是,如果我们想要提示访问者在创建帐户时输入她的家乡、主页和签名,该怎么办? 可以自定义 CreateUserWizard 控件的 接口,以在注册时收集其他信息,并且此信息可用于事件处理程序将 CreatedUser 其他记录插入基础数据库中。

CreateUserWizard 控件扩展了 ASP.NET 向导控件,该控件允许页面开发人员定义一系列有序 WizardSteps的 。 向导控件呈现活动步骤,并提供导航界面,使访问者能够浏览这些步骤。 向导控件非常适合将长任务分解为几个简短步骤。 有关向导控件的详细信息,请参阅 使用 ASP.NET 2.0 向导控件创建分步用户界面

CreateUserWizard 控件的默认标记定义两个 WizardStepsCreateUserWizardStepCompleteWizardStep

<asp:CreateUserWizard ID="NewUserWizard" runat="server"
          ContinueDestinationPageUrl="~/Membership/AdditionalUserInfo.aspx">
     <WizardSteps>
          <asp:CreateUserWizardStep ID="CreateUserWizardStep1" runat="server">

          </asp:CreateUserWizardStep>
          <asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server">
          </asp:CompleteWizardStep>
     </WizardSteps>
</asp:CreateUserWizard>

第一个 WizardStep呈现 CreateUserWizardStep提示输入用户名、密码、电子邮件等的接口。 访问者提供此信息并单击“创建用户”后,会显示 CompleteWizardStep,其中显示了成功消息和“继续”按钮。

若要自定义 CreateUserWizard 控件的界面以包含其他窗体字段,我们可以:

  • 创建一个或多个新WizardStep用于包含其他用户界面元素的 。 若要向 CreateUserWizard 添加新项WizardStep,请单击其智能标记中的“添加/删除WizardStep”链接以启动WizardStep集合编辑器。 在此处,可以添加、删除或重新排序向导中的步骤。 这是我们将在本教程中使用的方法。

  • CreateUserWizardStep转换转换为可编辑的WizardStep这会将 CreateUserWizardStep 替换为等效WizardStep的 ,其标记定义与 匹配 CreateUserWizardStep的用户界面。 通过将 转换为 CreateUserWizardStepWizardStep 我们可以重新定位控件或向此步骤添加其他用户界面元素。 若要将 CreateUserWizardStepCompleteWizardStep 转换为可编辑的 WizardStep,请单击控件的智能标记中的“自定义创建用户步骤”或“自定义完成步骤”链接。

  • 使用上述两个选项的某种组合。

需要记住的一个重要事项是,当从其 CreateUserWizardStep内部单击“创建用户”按钮时,CreateUserWizard 控件将执行其用户帐户创建过程。 后是否有额外的 WizardStepCreateUserWizardStep 并不重要。

将自定义 WizardStep 添加到 CreateUserWizard 控件以收集其他用户输入时,可以将自定义 WizardStep 放置在 之前或之后 CreateUserWizardStep。 如果它位于 之前 CreateUserWizardStep ,则从自定义 WizardStep 收集的其他用户输入可用于 CreatedUser 事件处理程序。 但是,如果自定义 WizardStep 在此之后 CreateUserWizardStep 显示自定义 WizardStep ,则已创建新的用户帐户, CreatedUser 并且事件已触发。

图 21 显示了在 之前添加 WizardStepCreateUserWizardStep时的工作流。 由于在事件触发时 CreatedUser 收集了其他用户信息,因此只需更新 CreatedUser 事件处理程序以检索这些输入,并将这些输入用于 INSERT 语句的参数值 (而不是 DBNull.Value) 。

当其他向导步骤位于 CreateUserWizardStep 之前时,CreateUserWizard 工作流

图 21:CreateUserWizard 工作流当附加 WizardStep 项位于 CreateUserWizardStep (单击以查看全尺寸图像)

但是,如果将自定义WizardStep放置在 之后CreateUserWizardStep,则创建用户帐户过程发生在用户有机会输入其家乡、主页或签名之前。 在这种情况下,需要在创建用户帐户后将此附加信息插入数据库,如图 22 所示。

当其他向导步骤在 CreateUserWizardStep 之后出现时,CreateUserWizard 工作流

图 22: (单击以查看全尺寸图像CreateUserWizardStep,CreateUserWizard 工作流 WizardStep)

图 22 中显示的工作流将等待将记录插入表中, UserProfiles 直到步骤 2 完成。 但是,如果访问者在步骤 1 后关闭其浏览器,我们将已达到创建用户帐户的状态,但未向 UserProfiles添加任何记录。 一种解决方法是在事件处理程序 (中插入UserProfilesCreatedUser一条记录,NULL该记录在步骤 1) 后触发,然后在步骤 2 完成后更新此记录。 这可确保 UserProfiles 即使用户中途退出注册过程,也会为用户帐户添加记录。

在本教程中,让我们创建一个新的 WizardStep ,它发生在 之后 CreateUserWizardStep ,但在 之前 CompleteWizardStep。 让我们先将 WizardStep 设置到位并配置,然后查看代码。

从 CreateUserWizard 控件的智能标记中,选择“添加/删除WizardStep”,随即显示WizardStep“集合编辑器”对话框。 添加一个新的 WizardStep,将其 ID 设置为 UserSettings,将其 Title 设置为“你的设置”,将 设置为 StepTypeStep。 然后将它 CreateUserWizardStep 置于 (“注册新帐户”) 之后,并在 (“完成”) 之前 CompleteWizardStep ,如图 23 所示。

向 CreateUserWizard 控件添加新向导步骤

图 23:向 CreateUserWizard 控件添加新 WizardStep (单击以查看全尺寸图像)

单击“确定”关闭“WizardStep集合编辑器”对话框。 新的 WizardStep 由 CreateUserWizard 控件的更新声明性标记证明:

<asp:CreateUserWizard ID="NewUserWizard" runat="server"

          ContinueDestinationPageUrl="~/Membership/AdditionalUserInfo.aspx">
     <WizardSteps>
          <asp:CreateUserWizardStep ID="CreateUserWizardStep1" runat="server">
          </asp:CreateUserWizardStep>
          <asp:WizardStep runat="server" ID="UserSettings" StepType="Step"

               Title="Your Settings">
          </asp:WizardStep>
          <asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server">
          </asp:CompleteWizardStep>
     </WizardSteps>
</asp:CreateUserWizard>

请注意新 <asp:WizardStep> 元素。 我们需要添加用户界面,以便在此处收集新用户的家乡、主页和签名。 可以使用声明性语法或通过Designer输入此内容。 若要使用Designer,请从智能标记的下拉列表中选择“你的设置”步骤,以查看Designer中的步骤。

注意

通过智能标记的下拉列表选择一个步骤会更新 CreateUserWizard 控件的 ActiveStepIndex 属性,该属性指定起始步骤的索引。 因此,如果使用此下拉列表编辑Designer中的“你的设置”步骤,请务必将其设置回“注册新帐户”,以便在用户首次访问EnhancedCreateUserWizard.aspx页面时显示此步骤。

在“你的设置”步骤中创建一个用户界面,其中包含名为 、 HomepageUrlSignature的三个 HomeTownTextBox 控件。 构造此接口后,CreateUserWizard 的声明性标记应如下所示:

<asp:CreateUserWizard ID="NewUserWizard" runat="server"

          ContinueDestinationPageUrl="~/Membership/AdditionalUserInfo.aspx">
     <WizardSteps>
          <asp:CreateUserWizardStep ID="CreateUserWizardStep1" runat="server">
          </asp:CreateUserWizardStep>
          <asp:WizardStep runat="server" ID="UserSettings" StepType="Step"

                    Title="Your Settings">
               <p>
                    <b>Home Town:</b><br />
                    <asp:TextBox ID="HomeTown" runat="server"></asp:TextBox>

               </p>
               <p>
                    <b>Homepage URL:</b><br />
                    <asp:TextBox ID="HomepageUrl" Columns="40" runat="server"></asp:TextBox>

               </p>
               <p>
                    <b>Signature:</b><br />
                    <asp:TextBox ID="Signature" TextMode="MultiLine" Width="95%"

                         Rows="5" runat="server"></asp:TextBox>
               </p>
          </asp:WizardStep>
          <asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server">

          </asp:CompleteWizardStep>
     </WizardSteps>
</asp:CreateUserWizard>

继续通过浏览器访问此页面,并创建新的用户帐户,为家乡、主页和签名指定值。 完成后, CreateUserWizardStep 将在成员资格框架中创建用户帐户,并 CreatedUser 运行事件处理程序,这会向 UserProfiles添加新行,但数据库 NULL 值为 HomeTownHomepageUrlSignature。 永远不会使用为家乡、主页和签名输入的值。 最终结果是一个新的用户帐户,其中包含一个 UserProfiles 记录,其 HomeTownHomepageUrlSignature 字段尚未指定。

我们需要在“你的设置”步骤之后执行代码,该步骤采用用户输入的家乡、磨练页和签名值,并更新相应的 UserProfiles 记录。 每当用户在 Wizard 控件中的步骤之间移动时,向导的事件ActiveStepChanged都会触发。 我们可以为此事件创建事件处理程序, UserProfiles 并在完成“你的设置”步骤时更新表。

为 CreateUserWizard 的 ActiveStepChanged 事件添加事件处理程序,并添加以下代码:

Protected Sub NewUserWizard_ActiveStepChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles NewUserWizard.ActiveStepChanged
     ' Have we JUST reached the Complete step?
     If NewUserWizard.ActiveStep.Title = "Complete" Then
          Dim UserSettings As WizardStep = CType(NewUserWizard.FindControl("UserSettings"),WizardStep)

          ' Programmatically reference the TextBox controls
          Dim HomeTown As TextBox = CType(UserSettings.FindControl("HomeTown"), TextBox)
          Dim HomepageUrl As TextBox = CType(UserSettings.FindControl("HomepageUrl"), TextBox)
          Dim Signature As TextBox = CType(UserSettings.FindControl("Signature"), TextBox)

          ' Update the UserProfiles record for this user
          ' Get the UserId of the just-added user
          Dim newUser As MembershipUser = Membership.GetUser(NewUserWizard.UserName)
          Dim newUserId As Guid = CType(newUser.ProviderUserKey, Guid)

          ' Insert a new record into UserProfiles
          Dim connectionString As String = ConfigurationManager.ConnectionStrings("SecurityTutorialsConnectionString").ConnectionString

          Dim updateSql As String = "UPDATE UserProfiles SET HomeTown = @HomeTown, HomepageUrl
               = @HomepageUrl, Signature = @Signature WHERE UserId = @UserId"

          Using myConnection As New SqlConnection(connectionString)
               myConnection.Open()
               Dim myCommand As New SqlCommand(updateSql, myConnection)
               myCommand.Parameters.AddWithValue("@HomeTown", HomeTown.Text.Trim())
               myCommand.Parameters.AddWithValue("@HomepageUrl", HomepageUrl.Text.Trim())
               myCommand.Parameters.AddWithValue("@Signature", Signature.Text.Trim())

               myCommand.Parameters.AddWithValue("@UserId", newUserId)
               myCommand.ExecuteNonQuery()
               myConnection.Close()
          End Using
     End If
End Sub

上述代码首先确定我们是否刚刚到达了“完成”步骤。 由于“完成”步骤紧接在“你的设置”步骤之后,那么当访问者到达“完成”步骤时,这意味着她刚刚完成了“你的设置”步骤。

在这种情况下,我们需要以编程方式引用 中的 UserSettings WizardStepTextBox 控件。 为此,首先使用 FindControl 方法以编程方式引用 UserSettings WizardStep,然后再次从 中 WizardStep引用 TextBox。 引用 TextBox 后,即可执行 UPDATE 语句。 语句 UPDATE 的参数数目 INSERT 与事件处理程序中的 CreatedUser 语句相同,但此处我们使用用户提供的家乡、主页和签名值。

完成此事件处理程序后,通过浏览器访问 EnhancedCreateUserWizard.aspx 页面,并创建一个新的用户帐户,指定家乡、主页和签名的值。 创建新帐户后,应重定向到 AdditionalUserInfo.aspx 页面,其中显示了刚刚输入的家乡、主页和签名信息。

注意

我们的网站目前有两个页面,访问者可以从中创建新帐户: CreatingUserAccounts.aspxEnhancedCreateUserWizard.aspx。 网站的站点地图和登录页指向该 CreatingUserAccounts.aspx 页面,但 CreatingUserAccounts.aspx 页面不会提示用户输入其家乡、主页和签名信息,并且不会向 UserProfiles添加相应的行。 因此,更新 CreatingUserAccounts.aspx 页面以使其提供此功能,或更新站点地图和登录页以引用 EnhancedCreateUserWizard.aspx 而不是 CreatingUserAccounts.aspx。 如果选择后一个选项,请务必更新 Membership 文件夹的文件 Web.config ,以便允许匿名用户访问页面 EnhancedCreateUserWizard.aspx

总结

在本教程中,我们了解了对成员资格框架中与用户帐户相关的数据建模的技术。 具体而言,我们了解了与用户帐户共享一对多关系的实体以及共享一对一关系的数据的建模。 此外,我们还了解了如何显示、插入和更新此相关信息,其中一些示例使用 SqlDataSource 控件,另一些示例使用 ADO.NET 代码。

本教程完成对用户帐户的了解。 从下一教程开始,我们将把注意力转向角色。 在接下来的几个教程中,我们将了解角色框架、如何创建新角色、如何将角色分配给用户、如何确定用户所属的角色以及如何应用基于角色的授权。

编程愉快!

深入阅读

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

关于作者

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

特别感谢...

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