Security Tutorials系列文章第八章:Storing Additional User Information

本文英文原版及代码下载:http://www.asp.net/learn/security/tutorial-08-cs.aspx
Security Tutorials系列文章第八章:Storing Additional User Information

导言:

ASP.NET的Membership framework为管理用户提供了灵活的接口.Membership API包含的方法有验证用户登录信息、检索当前登录帐户的信息、创建新帐户、删除某个帐户等.Membership framework里的每一个用户帐户都有一些基本的与执行用户帐户相关任务的属性,这些都由MembershipUser class类里的方法和属性体现出来, 该类定义了Membership framework里的用户帐户的模型.比如属性有UserName, Email, 以及IsLockedOut, 方法有GetPassword 和 UnlockUser.

有时候,应用程序需要存储额外的信息,但这些信息又没有包含在Membership framework里.比如,对在线零售商希望让每个用户存储个人的shipping and billing addresses、payment information, delivery preferences,contact phone number等信息,并且系统里的每个订单都与某个用户帐户对应起来.

MembershipUser class并没有包含诸如PhoneNumber 或 DeliveryPreferences 或 PastOrders的属性,那么我们如何来获取这些应用程序所需的信息,并与Membership framework结合起来使用呢?本文通过构架一个简单的guestbook应用程序来解答这个问题,我们将看到在数据库构建用户信息的多种方式,然后考察如何将之与Membership framework创建的用户帐户关联起来。


Step 1: Creating the Guestbook Application’s Data Model

有很多的技术都可以达到从数据库检索数据,并与Membership framework里的用户帐户关联起来的目的.为了进行演示,我们需要对本系列文章的web application进行扩充以获取某些与用户相关的数据(目前,该application的数据模型仅仅包括SqlMembershipProvider所需的tables)

让我们来创建一个很简单的guestbook application,在该应用程序里通过认证的用户都可以留言.为了存储留言,我们允许每个用户存储其home town, homepage,signature等信息.这样一来,当用户在guestbook留言时,其home town, homepage,signature等信息都会一起显示出来.


Adding the GuestbookComments Table

为了获取guestbook留言,我们需要创建一个数据库表—GuestbookComments,其包含的字段包括CommentId, Subject, Body, 以及CommentDate.同时我们还要在每条记录里包含是哪个用户留的言.

在Visual Studio的Database Explorer里选择数据库SecurityTutorials,在Tables上右击,选“Add New Table”.这将打开一个界面,允许我们对新表定义字段,如下图.


图1:

Security Tutorials系列文章第八章:Storing Additional User Information
接下来为GuestbookComments定义字段。最开始添加一个名为CommentId,类型为uniqueidentifier的字段.该字段为唯一标识的,因此不能为NULLs,且做为主键.另外,添加新记录时该字段为自增的,因此将其默认值设为NEWID().这样你的界面看起来和下面的差不多:

Security Tutorials系列文章第八章:Storing Additional User Information
图2

接下来,添加一个类型为nvarchar(50)的“ Subject ”字段,一个类型为nvarchar(MAX)的“Body”字段,这2个字段都不能为NULLs .然后,添加一个类型为datetime的“CommentDate”字段,也不能为NULLs,且设其默认值为getdate().


剩下的是添加一个字段以表明是哪个用户留的言.一种方法是添加一个类型为nvarchar(256)的“UserName”.如果是使用非SqlMembershipProvider的其他Membership provider的话这是一个比较好的办法,但如果使用SqlMembershipProvider的话,正如我们在前面所提到的那样,aspnet_Users表里的UserName字段是无法保证是唯一标识的.因为aspnet_Users表的主键是类型为uniqueidentifier的UserId字段.因此,我们的GuestbookComments表需要一个类型为uniqueidentifier(不接受NULL值)的“UserId”字段,就这样做吧.

注意:
正如我们在文章《Creating the Membership Schema in SQL Server》里探讨的那样,Membership framework是设计来便于多个应用程序的众多不同的用户帐户共享用户存储(user store),方法是将用户帐户划分给不同的应用程序.虽然对某个应用程序来说每个username都是唯一的,但共享用户存储的不同应用程序的username却可能是相同的.在aspnet_Users表里,有一个由UserName 和 ApplicationId字段组成的composite UNIQUE约束,而不是建立在UserName字段上的.因此,对aspnet_Users表来说,可能有2条甚至更多记录的UserName值是相同的.因此,aspnet_Users表的UserId字段有一个UNIQUE约束(因为该字段为主键).而拥有一个UNIQUE约束是很重要的,因为,没有的话我们就不能在GuestbookComments和aspnet_Users表之间建立一个外键约束.添加UserId字段后保存表,并将表命名为GuestbookComments

对GuestbookComments表,我们至少有2方面要引起注意:我们要在GuestbookComments.UserId和aspnet_Users.UserId字段之间建立一个外键约束.因此,在工具栏里点Relationship图标以打开Foreign Key Relationships对话框(当然,你也可以在Table Designer菜单里选Relationships).

点击左下角的Add按钮,以便于我们添加一个新的外键约束.

Security Tutorials系列文章第八章:Storing Additional User Information
图3

接下来,点“Table and Columns Specifications”行右边的椭圆形图标.这将打开Tables and Columns对话框,我们可以选主键表和外键表,以及字段。具体来说,选aspnet_Users为primary key table,列选UserId;而GuestbookComments为foreign key table,列也选为UserId(见图4).定义好表和列后,点OK返回ign Key Relationships对话框.

Security Tutorials系列文章第八章:Storing Additional User Information
图4


这样就建立好外键约束了.它保证了这2个表之间的相关完整性(relational integrity).也就是说一个guestbook不可能引用一个不存在的用户帐户.默认,外键约束将使一个有子记录的父记录无法删除.比如,一个用户发表了一个或多个guestbook留言,那么我们尝试删除该用户帐户的事件就会失败,除非先删除该用户的留言.

外键约束也可以配置成删除父记录时自动删除子记录.换句话说,当我们删除某用户记录时也自动的删除了该用户的留言.为此,可以展开“INSERT And UPDATE Specification”节点,设“Delete Rule”属性为Cascade.

Security Tutorials系列文章第八章:Storing Additional User Information
图5

点Close按钮推出Foreign Key Relationships,再点工具栏上的Save图标就保存了.


Storing the User’s Home Town, Homepage, and Signature


表GuestbookComments演示了如何与用户帐户以one-to-many的关系存储信息.因为每个用户帐户可以有有任意多个留言.我们可以这样来定义该关系模型:创建一个表以存储留言,且有一个字段指名是某个用户帐户留的言.当使用SqlMembershipProvider的时候,很好办,创建一个类型为uniqueidentifier的UserId字段,且将该字段与aspnet_Users.UserId之间建立一个外键约束.

现在我们需要将这3个字段与用户帐户对应起来以存储用户的home town, homepage, signature信息.我们有很多种方法:

.向数据库表aspnet_Users 或 aspnet_Membership新添加一个字段.我们不推荐这样做,因为这样修改了SqlMembershipProvider要用到的构架,你以后可能会遇到一些问题.比如,以后的ASP.NET新版本使用一个新的SqlMembershipProvider构架时,微软自然会推出一个新工具以将ASP.NET 2.0 SqlMembershipProvider数据迁移到新的构架.如果你修改了ASP.NET 2.0 SqlMembershipProvider的构架,那么这种迁移可能会失败.


.使用ASP.NET的Profile framework,并为home town, homepage, 和signature定义profile属性.ASP.NET包含的Profile framework就是设计来存储与用户相关的数据的.与Membership framework一样,Profile framework也是采用的provider model.我们知道.NET Framework里的SqlProfileProvider用于在SQL Server数据库里存储数据.实际上我们已经有了供SqlProfileProvider使用的表(也就是aspnet_Profile).我们在前面的文章《Creating the Membership Schema in SQL Server》里添加application services的时候就已经同时添加了该表.使用Profile framework的主要好处是允许开发人员在Web.config里定义profile properties——不要写一行代码就可以序列化profile data.简单的说,很简单的就可以定义一系列的profile properties并在代码里使用.然而,在某些情况下,比如在某个应用程序里,如果你在稍后的某个时间想添加一个新的与用户相关的属性,或对现有的属性进行修改或删除,使用Profile system可能就不是最好的选择了.此外,SqlProfileProvider以高度格式化的形式(in a highly denormalized fashion)来存储profile properties.这样我们就几乎不可能直接对profile data进行查询(比如,home town为“New York”的用户有多少个).对Profile framework的更多详细了解,请参阅本文结尾处的“Further Readings”内容.

.在数据库里新建一个包含这3个字段的表,并将该表与aspnet_Users建立one-to-one的关系.与使用Profile framework相比,该方法要做的工作多一些,但对这些与用户属性如何在数据库里建立模型提供了最大程度上的灵活性.本文就采用的这个方式.

我们会创建一个名为UserProfiles的数据库表,为每个用户存储其home town, homepage, 以及signature信息.因此在Database Explorer窗口里右击Tables文件夹选“创建新表”.第一个列为“UserId”,类型为uniqueidentifier,不允许为 NULL值,且标记为主键;然后添加一个“HomeTown”列,类型为nvarchar(50);再添加一个“HomepageUrl”列,类型为nvarchar(100);列“Signature”的类型为nvarchar(500). 最后这3个列都允许为NULL值.

Security Tutorials系列文章第八章:Storing Additional User Information
图6


保存该表,命名为UserProfiles.最后,为UserProfiles表的UserId字段和aspnet_Users.UserId字段之间建立一个外键约束.同时,像GuestbookComments 和aspnet_Users之间的外键约束一样,允许删除父记录时自动删除子记录. 由于UserId字段是UserProfiles表的主键,这就保证了每个用户帐户在UserProfiles里最多对应一条记录,这种关系就叫one-to-one.

现在我们创建好了数据模型.在第2步和第3步里我们将看到当前登录的用户如何查看和修改他的home town, homepage,以及signature信息.在第4步里,我们将创建一个界面允许认证用户提交并查看他的留言.


第2步:Displaying the User’s Home Town, Homepage, and Signature

有很多种方式来允许当前登录用户查看及修改其home town, homepage,以及 signature信息.比如我们可以动手用TextBox 和 Label控件或某个Web控件,比如DetailsView来创建一个用户界面. 为了执行数据库的SELECT 和 UPDATE命令,我们可以在页面的后台代码里写ADO.NET代码,或使用SqlDataSource控件.不过最理想的做法是在应用程序里专门分一层来实现,然后要么在页面的后台代码里进行编程调用,要么显式的通过ObjectDataSource控件来调用.

由于本系列文章专注于forms authentication, authorization, user accounts,以及roles,因此这里不会探讨数据访问的各种方法,以及为什么体系分层比直接在ASP.NET页面里执行SQL命名要好.我打算用最快最方便的方法——使用DetailsView 和 SqlDataSource控件.如何在ASP.NET里处理数据的更多详细内容,请参阅我的《Working with Data in ASP.NET 2.0》系列文章.

打开Membership文件夹里的AdditionalUserInfo.aspx页面,添加一个DetailsView控件,设其ID属性为UserProfile,清除其Width 和 Height属性.打开DetailsView的智能标签,选择把它绑定到一个新的数据源控件,这样将打开DataSource Configuration Wizard向导(见图7)。首先要你指定数据源的类型,我们选Database图标,设其ID为UserProfileDataSource.

Security Tutorials系列文章第八章:Storing Additional User Information
图7


然后要我们选要使用的数据库.我们已经在Web.config文件里定义了一个连接到SecurityTutorials数据库的连接字符串——SecurityTutorialsConnectionString;因此在下拉列表里选择它,点下一步.

Security Tutorials系列文章第八章:Storing Additional User Information
图8


接下来指定要进行查询的表和列.选择UserProfiles表,并选中其所有的列.

Security Tutorials系列文章第八章:Storing Additional User Information
图9

图9里的查询是返回所有的记录,但我们只对当前用户的记录感兴趣。点WHERE按钮,开启Add WHERE Clause对话框(见图10).这里,我们选UserId列,Operator为“=”.

很遗憾,没有内置的参数源来返回当前登录用户的UserId值.我们需要编程获取该值.因此,将Source下拉列表选“None”,点Add按钮添加参数,再点OK按钮.

Security Tutorials系列文章第八章:Storing Additional User Information
图10

这样你将返回图9所示的界面,此时底部的SQL查询应该包含一个WHERE字句.点Next转到“Test Query”界面,在这里你可以允许查询并看到结果.点Finish完成向导.

完成DataSource配置向导后,Visual Studio会根据配置情况生成SqlDataSource控件.此外,它还会在DetailsView里为SqlDataSource的SelectCommand返回的每个字段创建对应的BoundFields.我们没必要在DetailsView里显示UserId字段,因为用户没有必要知道.你可以从DetailsView的声明代码里删除该字段,也可以通过在其智能标签里的“Edit Fields”里进行操作.

此时,你的页面声明代码看起来和下面的差不多:

<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)">


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


我们需要在检索数据之前,通过编程的方式将SqlDataSource控件的UserId参数设置为当前登录用户的UserId.为此,我们为SqlDataSource控件的Selecting事件创建一个事件处理器,如下:

protected void UserProfileDataSource_Selecting(object sender, SqlDataSourceSelectingEventArgs e)
{
// Get a reference to the currently logged on user
MembershipUser currentUser = Membership.GetUser();
// Determine the currently logged on user's UserId value
Guid currentUserId = (Guid)currentUser.ProviderUserKey;
// Assign the currently logged on user's UserId to the @UserId
parameter e.Command.Parameters["@UserId"].Value = currentUserId;
}

上述代码最开始通过调用Membership class的GetUser()方法获取当前登录用户的一个引用.返回的为一个MembershipUser对象,该对象的ProviderUserKey属性就包含了UserId.再将该值赋值给SqlDataSource控件的@UserId参数.

注意:
Membership.GetUser()方法返回的是当前登录用户的信息.如果是一个匿名用户访问该页面的话,该方法就会返回一个null值.在这时,当下一句在读取ProviderUserKey属性时, 就会抛出一个NullReferenceException的异常.当然,我们不必担心Membership.GetUser()方法返回一个null值的情况,因为我们在前面的文章里配置为只有通过认证的用户才能访问AdditionalUserInfo.aspx页面.当然,如果你配置的情况是允许匿名用户也可以访问,那么在返回MembershipUser object对象的属性之前,我们需要检查GetUser()返回的是非null的MembershipUser object对象.

如果你在浏览器里访问AdditionalUserInfo.aspx页面的话,你将看到空白的页面,因为我们还没有向UserProfiles表里添加记录.在第六步,我们将看如何对CreateUserWizard进行定制,当创建一个新用户帐户时自动的向UserProfiles表添加一个记录.不过现在,我们需要手动的添加记录.

在Visual Studio的Database Explorer里,展开Tables文件夹.右击aspnet_Users表,选“Show Table Data” 以查看记录.同时以相同的方式查看UserProfiles表里的记录.如图11所示,在我的数据库里aspnet_Users表里有Bruce, Fred,Tito等用户,而在UserProfiles表里没有记录.

Security Tutorials系列文章第八章:Storing Additional User Information
图11

手动在UserProfiles表里的HomeTown, HomepageUrl,以及Signature字段里键入值.而获取UserId值最简单的方法是在aspnet_Users表里select某个用户的UserId 值,再将该值拷贝到UserProfiles表的UserId字段即可.如12显示的是为用户Bruce在UserProfiles表里添加一条记录是情况.

Security Tutorials系列文章第八章:Storing Additional User Information
图12

返回AdditionalUserInfo.aspx页面,以Bruce的名义登录,如图13所示,用户Bruce的相关信息就显示出来了.

Security Tutorials系列文章第八章:Storing Additional User Information
图13

注意:
继续为每一个Membership user在UserProfiles表里添加对应的记录. 在第6步,我们将看到如何如何对CreateUserWizard控件进行定制,以自动的向UserProfiles表添加新记录.


Step 3: Allowing the User to Edit His Home Town, Homepage, and Signature


目前,用户登录后可看到其town, homepage,以及signature信息.但还不能进行编辑.让我们对DetailsView进行改动以允许用户修改其信息.

首先要为SqlDataSource添加一个UpdateCommand,指定要执行的UPDATE命令.选中SqlDataSource,在其Properties窗口里点UpdateQuery属性,打开Command and Parameter对话框和Parameter Editor对话框.键入如下的UPDATE statement:

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

接下来,点击“Refresh Parameters”按钮,这将为UPDATE statement的每个参数创建一个对应的参数。将这些参数的参数源都设为None,点OK按钮关闭对话框.

Security Tutorials系列文章第八章:Storing Additional User Information
图14


对SqlDataSource做了这些改动后我们就可以在DetailsView里进行改动了.在DetailsView的智能标签里,选中“Enable Editing”,这将为DetailsView的Fields collection添加一个CommandField,且其ShowEditButton属性为True.这样,在read-only模式里,将显示一个Edit按钮,而在edit模式里将显示Update 和 Cancel按钮. 与其让用户点击Edit按钮进入编辑状态还不如我们使DetailsView一直呈现为一个编辑状态.

为此,我们将DetailsView控件的DefaultMode属性设置为Edit.这样一来,你的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属性在浏览器里进行测试,当登录后,该用户的信息就显示在编辑界面里了.

Security Tutorials系列文章第八章:Storing Additional User Information
图15

你可以尝试改变一个值,再点Update按钮.好像什么事情都没发生.事件上发生了一个页面回传,值被改动到数据库了.但是在视觉上没有提示信息指出发生save了.

来做些修改.返回到Visual Studio,在DetailsView里添加一个Label控件.设其 ID为SettingsUpdatedMessage,设其Text属性为“Your settings have been updated”;而Visible 和 EnableViewState属性都为false,如下:

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

任何时候DetailsView进行了更新后我们都需要显示SettingsUpdatedMessage.因此,为DetailsView的ItemUpdated事件创建一个事件处理器:

protected void UserProfile_ItemUpdated(object sender, DetailsViewUpdatedEventArgs e)
{
SettingsUpdatedMessage.Visible = true;
}

返回AdditionalUserInfo.aspx页面,在浏览器里更新数据。这次,消息就显示出来了.

Security Tutorials系列文章第八章:Storing Additional User Information
图16

注意:
DetailsView控件的编辑界面还有很多要完善.它使用的是标准的extboxes,而Signature字段可能是一个多行的文本.另外还要使用一个RegularExpressionValidator控件来确保输入的homepage URL以“http://”或 “https://”开头.此外,由于DetailsView的DefaultMode属性为Edit,那么Cancel按钮就没有什么用了。因此要么将它删除,要么当点击它时将用户导航到某个页面去(比如 ~/Default.aspx).我将这些作为练习留给读者.


Adding a Link to the AdditionalUserInfo.aspx Page in the Master Page

目前,站点没有提供到AdditionalUserInfo.aspx页面的链接.唯一的访问该页面的方式是在浏览器的地址栏里输入该页面的URL.让我们在Site.master母版页面里添加一个到该页面的链接.

记得母版页面里有一个LoginView控件,位于LoginContent ContentPlaceHolder里.该控件对认证用户和匿名用户分别显示不同的内容.更新LoginView控件的LoggedInTemplate以包含一个到AdditionalUserInfo.aspx页面的链接.这样该控件的声明代码看起来和下面的差不多:

<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>


注意在LoggedInTemplate里的lnkUpdateSettings HyperLink控件.有了这个链接,认证用户就可以快速的导航到该页面,查看和修改他们的home town, homepage, 以及signature设置.


第4步: Adding New Guestbook Comments


Guestbook.aspx是这样的一个页面,认证用户可以查看guestbook并留言.我们最开始创建一个界面来添加新的guestbook留言.

打开Guestbook.aspx,为构建用户界面,添加2个TextBox控件,一个用于输入留言的主题,一个用于输入留言内容.将第一个TextBox的ID设为Subject,Columns为40;第二个的ID为Body,TextMode属性为MultiLine,而Width 和 Rows属性分别为“95%” 和 8.最后再添加一个Button控件,ID为PostCommentButton,而Text属性为“Post Your Comment”.

由于每个guestbook留言都要有主题和内容,分别为这2个TextBoxes添加一个RequiredFieldValidator,且设其ValidationGroup属性为“EnterComment”;同理将PostCommentButton按钮的ValidationGroup属性也设为“EnterComment”.对ASP.NET里的validation控件的更多信息,请参阅《Form Validation in ASP.NET》、《Dissecting the Validation Controls in ASP.NET 2.0》以及《Validation Server Controls Tutorial》.

这样,你的页面的最终声明代码应该和下面的差不多:

<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>

完成后,我们接下来要做的是当点击PostCommentButton时向GuestbookComments表添加一条记录.可以有多种方式实现:比如在按钮的Click event handler了写ADO.NET代码;我们也可以在页面上添加一个SqlDataSource控件,配置其InsertCommand,再在Click event handler里调用其Insert方法;或者我们新建一个中间层,专门负责添加一条新的guestbook留言,再从Click event handler里调用该功能.由于我们在第3步里使用的是SqlDataSource,因此我们使用ADO.NET代码.

注意:
用于编程从Microsoft SQL Server数据库里检索数据的ADO.NET classes属于System.Data.SqlClient命名空间.因此你应在代码里导入
using System.Data.SqlClient.


为PostCommentButton的Click事件创建一个事件处理器,如下:
protected void PostCommentButton_Click(object sender, EventArgs e)
{
if (!Page.IsValid)
return;

// Determine the currently logged on user's UserId
MembershipUser currentUser = Membership.GetUser();
Guid currentUserId = (Guid)currentUser.ProviderUserKey;

// Insert a new record into GuestbookComments
string connectionString = ConfigurationManager.ConnectionStrings["SecurityTutorialsConnectionString"].ConnectionString;

string insertSql = "INSERT INTO GuestbookComments(Subject, Body, UserId) VALUES(@Subject, @Body, @UserId)";
using (SqlConnection myConnection = new SqlConnection(connectionString))
{
myConnection.Open();
SqlCommand myCommand = 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();
}

// "Reset" the Subject and Body TextBoxes
Subject.Text = string.Empty;
Body.Text = string.Empty;
}


Click event handler最先检查用户输入的数据是否有效,如果无效就退出.假如数据有效,就获取当前用户的UserId值并存储在局部变量currentUserId里.在向GuestbookComments表里添加新记录时我们要用该值.

接下来,从Web.config里获取链接到SecurityTutorials数据库的链接字符串并指定SQL statement.接下来创建一个SqlConnection对象,并打开.然后创建一个SqlCommand对象,并为INSERT查询要用的参数赋值.然后执行INSERT statement ,并关闭链接.最后,对Subject 和 Body TextBoxes的Text属性清空.


在浏览器里进行测试.因为该页面位于Membership文件夹,对匿名用户不可见.因此,你需要先“登录”.分别在Subject 和 Body TextBoxes里输入值,点PostCommentButton按钮.这样将向GuestbookComments添加一条新记录.页面回传后,你提供的主题和内容信息就被“擦掉了”.


点击PostCommentButton按钮后,没有感官上的提示添加留言了.我们仍然需要对页面进行更新以显示现有的guestbook留言,我们将在第5步里实现.一旦完成后,我们刚刚添加的留言就会显示来留言列表里,以进行感官上的提示.现在查看GuestbookComments表,看我们添加记录成功没.图17显示的是添加2条记录后的情形.

Security Tutorials系列文章第八章:Storing Additional User Information
图17

注意:
如果用户输入的留言包含潜在的危险代码—比如HTML,那么ASP.NET将抛出一个HttpRequestValidationException异常.关于这个问题,比如为什么要抛出异常,如何阻止用户输入潜在的危险代码,请参考《Request Validation Whitepaper》.


Step 5: Listing the Existing Guestbook Comments


除了留言外,用户访问Guestbook.aspx页面时还应该可以看到现有的留言.为此,在页面底部添加一个名为CommentList的ListView控件.

注意:
ListView是ASP.NET 3.5里的新控件。它是用来以灵活的、可定制的形式来显示一系列的items,和GridView类似,也提供了内置的编辑、添加、删除、分页、排序的功能.如果你使用的是ASP.NET 2.0,那么你可以使用DataList 或 Repeater来替换ListView控件.对如何使用ListView的更多信息,请参阅Scott Guthrie的博文《The asp:ListView Control》,以及我的文章《Displaying Data with the ListView Control》.


打开ListView的智能标签,在Choose Data Source下拉列表里选绑定到新的数据源,如我们在第2步里做的那样,这将打开Data Source Configuration Wizard,选Database图标,将SqlDataSource命名为CommentsDataSource,点OK.接下来,数据库连接字符串下拉列表里选SecurityTutorialsConnectionString,点Next.

在第2步里,我们从UserProfiles表里返回所有的字段(如图9).这次,我们要自己定制SQL statement,不仅从GuestbookComments里返回记录,还有将留言人的 home town, homepage, signature以及username一同返回.因此选择“Specify a custom SQL statement or stored procedure”,再点Next.

这将开启“Define Custom Statements or Stored Procedures”界面。点击Query Builder按钮以图形的方式构架查询.Query Builder首先提示我们要从哪些表检索数据.选GuestbookComments, UserProfiles,aspnet_Users这3个表,点OK.这样就把这3个表添加到设计器里了,因为GuestbookComments, UserProfiles, 和aspnet_Users表之间存在外键约束,因此Query Builder自动将这些表JOINs起来.

接下来要指定返回哪些字段.返回GuestbookComments表的Subject, Body,以及CommentDate字段;返回UserProfiles表的HomeTown, HomepageUrl,以及Signature字段;返回aspnet_Users表的UserName字段.再在SELECT查询的结尾添加“ORDER BY CommentDate DESC”,以便使最新发表的留言排在最前面.这样,你的Query Builder界面看起来和图18差不多.

Security Tutorials系列文章第八章:Storing Additional User Information
图18

点击OK关闭Query Builder窗口,返回到“Define Custom Statements or Stored Procedures”屏幕.点Next进入到“Test Query”屏幕,你可以在这里点Test Query按钮测试结果.最后点Finish完成Configure Data Source向导.

在第2步里,当完成Configure Data Source向导后,DetailsView控件会自动的生成相应的BoundField.而此时我们的ListView却没什么变化,需要我们自己定义布局.我们可以手动添加代码来构架布局,也可以从其智能标签里的“Configure ListView”项来构建.我比较喜欢自己动手添加代码来构架,当然使用哪种方法就看你自己的喜好了.


我是这样来定义ListView control的:

<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>

其中,ItemTemplate模板呈现出来的是SqlDataSource返回的每一条记录.而ItemTemplate的最终声明代码(resulting markup)是放置LayoutTemplate的itemPlaceholder控件里的.除了itemPlaceholder控件外,LayoutTemplate还包含了一个DataPager控件,它将ListView控件里每页显示的记录限制为10条(默认值),且显示一个分页接口.

我们的ItemTemplate在一个<h4>元素里显示每一个留言的主题,留言内容放在主题的下方.请注意返回内容信息的Eval("Body")绑定语法,它首先将留言内容转化为字符串,再将里面的空白行用<br />元素替换掉.当你想把提交留言时的空白分隔行显示出来时,这种转换是必要的,因为HTML会忽略掉空白(whitespace).而signature用斜体显示在留言内容下面,紧接着是用户的home town,链接到其homepage的超链接,留言时间、以及用户名等.花点时间在浏览器里测试。你将看到你在第5步里留的言.

Security Tutorials系列文章第八章:Storing Additional User Information
图19

试着添加一条新留言。点击PostCommentButton按钮后页面回传,留言添加到数据库里,但是ListView控件依然没及时更新以显示最新的留言.我们可以用下面的任一种方法来弥补:

.更新PostCommentButton按钮的Click事件处理器,在留言添加到数据库后马上调用ListView控件的DataBind()方法,或者

.将ListView控件的EnableViewState属性设置为false.这样就行了,因为禁用了控件的view state后,每次页面回传时它都必须重新绑定数据.

在下载代码里我们这2种方法都使用了,我们将ListView控件的EnableViewState设为false;而第一种方法要用的Click事件处理器的代码也写出来了,只是我们把它注释掉了而已.

注意:
现在AdditionalUserInfo.aspx页面允许用户查看和修改其home town, homepage,以及signature信息.理想状态是登录用户还可以看到他发表的留言。也就是说,用户除了在AdditionalUserInfo.aspx页面上查看和修改信息外,还可以看到自己以前的留言,我将这作为练习留给有兴趣的读者.

Step 6: Customizing the CreateUserWizard Control to Include an Interface for the Home Town, Homepage, and Signature

Guestbook.aspx页面用到的SELECT查询通过一个“INNER JOIN”将GuestbookComments, UserProfiles,以及aspnet_Users表里的相关记录组合起来.如果某个用户在UserProfiles表里没有相关的记录,而他又留了言。最终的结果是留言将不会显示在ListView里.因为“INNER JOIN”语句只从GuestbookComments表里返回那些与UserProfiles和aspnet_Users表匹配的记录. 正如我们在第3步里看到的那样,如果一个用户在UserProfiles表里没有他对应的记录,那么他就不能在AdditionalUserInfo.aspx页面里查看和修改他的信息.

毫无疑问,我们在设计的时候,有一点很重要,Membership系统里的每一个用户帐户,必须在UserProfiles表里有一条对应的记录.因此当我们用CreateUserWizard控件创建一个Membership用户帐户时,也要同时向UserProfiles表添加一条记录.

正如我们在《Creating User Accounts》里探讨的那样,当在CreateUserWizard控件里创建一个用户帐户时将触发它的CreatedUser event.因此我们可以为该事件创建一个事件处理器,获取刚才创建的帐户的UserId,然后向UserProfiles表添加一条记录,而HomeTown, HomepageUrl,和Signature字段都采用默认值.此外,我们也可以对CreateUserWizard控件进行定制,添加一些TextBoxes,提示用户输入他们的HomeTown, HomepageUrl,Signature信息.

首先,我们考察如何在CreatedUser事件处理器里使用默认值向UserProfiles表添加一条新记录. 接下来,我们再考察如何对CreateUserWizard的界面进行定制,供用户输入他们的home town, homepage,以及signature信息.


Adding a Default Row to UserProfiles

在文章《Creating User Accounts》里,我们向Membership文件夹里的CreatingUserAccounts.aspx页面添加一个CreateUserWizard控件.为了在创建用户帐户时向UserProfiles表添加记录,我们需要改动CreateUserWizard控件的功能.犯不着对CreatingUserAccounts.aspx页面进行改动,我们向EnhancedCreateUserWizard.aspx页面新添加一个新的CreateUserWizard控件,以对本文内容进行演示.

打开EnhancedCreateUserWizard.aspx页面,拖一个CreateUserWizard控件到页面上.设其ID为NewUserWizard.

当用户输入信息并提交表单时,CreateUserWizard控件就会触发其CreatingUser event.如果在处理时发生了错误,那么就触发CreateUserError event;而如果创建用户帐户成功那么就触发CreatedUser event.在文章《Creating User Accounts》里我们为CreatingUser事件创建了事件处理器,确保用户名的前面和后面不能有空白,且username不能包含在password里.

为了为新用户帐户在UserProfiles表里添加一条记录,我们需要为CreatedUser事件创建以事件处理器.当触发CreatedUser event时,也就说明已经成功的创建了一个用户帐户,因次我们就可以检索该用户帐户的UserId值.为NewUserWizard的CreatedUser event创建事件处理器,如下:

protected void NewUserWizard_CreatedUser(object sender, EventArgs e)
{
// Get the UserId of the just-added user
MembershipUser newUser = Membership.GetUser(NewUserWizard.UserName);
Guid newUserId = (Guid)newUser.ProviderUserKey;
// Insert a new record into UserProfiles
string connectionString = ConfigurationManager.ConnectionStrings["SecurityTutorialsConnectionString"].ConnectionString;


string insertSql = "INSERT INTO UserProfiles(UserId, HomeTown, HomepageUrl, Signature) VALUES(@UserId, @HomeTown,

@HomepageUrl, @Signature)";

using (SqlConnection myConnection = new SqlConnection(connectionString))
{
myConnection.Open();
SqlCommand myCommand = 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();
}
}

上述代码已开始使用Membership.GetUser(username)方法来返回用户的信息,再通过ProviderUserKey属性来检索其UserId值.用户键入的用户名可以通过CreateUserWizard控件的UserName属性来获取.

接下来,从Web.config里获取连接字符串,并指定INSERT statement.再创建必要的ADO.NET对象实例,执行command.为@HomeTown, @HomepageUrl,以及@Signature这3个参数分配的是DBNull.

在浏览器里访问EnhancedCreateUserWizard.aspx页面,并创建新用户帐户.完成后,返回Visual Studio,查看aspnet_Users 和 UserProfiles表(就像我们图12做的那样),你应该在aspnet_Users表里看到一个新用户记录,对应的,在UserProfiles表里也有一条记录(其HomeTown, HomepageUrl,以及Signature字段为NULL值).

Security Tutorials系列文章第八章:Storing Additional User Information
图20

当用户输入新帐户信息并点击“Create User”按钮后,创建新帐户,并在UserProfiles表里添加一行记录.接下来CreateUserWizard显示其 CompleteWizardStep,显示一个提示操作成功的消息,和一个Continue按钮.点击Continue按钮,页面回传,但什么都没发生,用户依然停留在EnhancedCreateUserWizard.aspx页面.

我们可以为CreateUserWizard控件的ContinueDestinationPageUrl属性指定一个URL,当用户点击Continue按钮时就将用户导航到该URL.我们将ContinueDestinationPageUrl属性指定为“~/Membership/AdditionalUserInfo.aspx”.这就会将新用户导航到AdditionalUserInfo.aspx页面,编辑和查看他们的信息.


Customizing the CreateUserWizard’s Interface to Prompt for the New User’s Home Town, Homepage, and Signature

CreateUserWizard控件的默认界面只适合比较简单的注册场景,比如要用户输入其username, password, 以及email.但如果我们要提示用户输入其home town, homepage, 以及signature信息呢? 我们可以对CreateUserWizard的界面进行定制.在CreatedUser event事件里这些信息将会添加到数据库里.

CreateUserWizard控件是对ASP.NET Wizard控件的扩展,Wizard控件允许开发人员定义一系列有序的WizardSteps.我们知道Wizard控件只呈现众多步骤里的某个具体的步骤,同时提供一个导航链接,供用户在这些步骤之间进行穿梭.Wizard 控件的设计目的是将一个长的步骤细分为多个小的步骤.关于Wizard控件的更多详情,请参阅文章《Creating a Step-by-Step User Interface with the ASP.NET 2.0 Wizard Control》

CreateUserWizard控件的默认界面只定义了二个WizardSteps: 也即CreateUserWizardStep 和 CompleteWizardStep,如下:

<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,呈现为一个提示用户输入 username, password, email等的界面.当用户输入并点击“Create User”后,接下来就会显示CompleteWizardStep,显示一个成功消息,以及一个Continue按钮.

要对CreateUserWizard的界面进行定制以包含其他的字段,我们可以:

.创建一个或多个新的WizardSteps,以包含额外的用户信息元素.要在CreateUserWizard里添加新的WizardStep,点击智能标签里的“Add/Remove WizardSteps”链接,这将打开WizardStep Collection Editor.在这里,我们可以添加、移除,或对步骤重新排序.本文就是使用的该方法.

.将CreateUserWizardStep转换为一个可编辑的的WizardStep.这会将CreateUserWizardStep转换为一个相匹配的WizardStep.转换后我们可以对控件重新配置,或在这一步添加新的用户界面元素.要将CreateUserWizardStep 或CompleteWizardStep转换为一个可编辑的WizardStep, 请点击智能标签里的“Customize Create User Step” 或 “Customize Complete Step”.


.将上述2种方法结合来使用.

有一点务必要明确:CreateUserWizard执行创建新用户帐户的流程是当用户点击CreateUserWizardStep里的“Create User”按钮时才发生的.因此,跟在CreateUserWizardStep步骤之后有没有其他新添加的步骤一点关系都没有.

当向CreateUserWizard控件添加新的自定义WizardStep以便于用户输入其他信息时,该WizardStep放在CreateUserWizardStep之前和之后都可以。不同之处在于,放在前面的时候,我们就可以在CreatedUser事件处理器里获取用户输入的其它信息;如果是放在后面,那么当用户导航到该WizardStep步骤时,新用户帐户已经创建好了,CreatedUser event事件也已经发生了.


图21揭示的是将我们自定义的步骤放在CreateUserWizardStep之前的流程.因为在CreatedUser event事件触发时,我们已经收集到了这些额外的信息,我们要做的就是对CreatedUser事件处理器进行更新,获取这些额外信息,并对INSERT命令的参数赋值(而不是默认的DBNull.Value).

Security Tutorials系列文章第八章:Storing Additional User Information
图21

如果是放在CreateUserWizardStep之后,那么在用户输入home town, homepage,或signature信息之前新用户帐户就已经创建好了.在这种情况下,我们要把这些信息添加到数据库即可,如图22所示.

Security Tutorials系列文章第八章:Storing Additional User Information
图22

图22所示的流程是当Step 2结束时向UserProfiles表添加一条记录.如果用户在step 1完成后关闭了浏览器,那么最终的结果就是他创建了新用户帐户而没有向UserProfiles表添加对应的记录.有一种解决办法,在CreatedUser事件处理器里(由step 1触发)向UserProfiles表添加一条记录,不过对应相应的字段为NULL 或默认值.而在step 2步骤里对该条记录进行更新.这就保证了即使用户在中途退出时,只要成功的创建了新用户帐户,就会向UserProfiles表里添加一条对应的记录.

本文我们将创建一个新的WizardStep,位于CreateUserWizardStep之后,CompleteWizardStep之前.

在CreateUserWizard控件里打开其智能标签,选“Add/Remove WizardSteps”,这将开启WizardStep Collection Editor对话框.添加一个新的WizardStep,设其ID为UserSettings,设Title为“Your Settings”,StepType属性为Step.再将它排在CreateUserWizardStep(“Sign Up for Your New Account”)之后,CompleteWizardStep(“Complete”)之前, 如图23所示.

Security Tutorials系列文章第八章:Storing Additional User Information
图23

点OK关闭WizardStep Collection Editor对话框。现在代码如下:

<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>元素.我们需要添加界面以供用户输入home town, homepage,signature等信息.你要么手动输入代码要么通过设计器来添加.要通过设计器来操作的话,在智能标签里的下拉列表里选“Your Settings” step,以在设计器里查看该步骤.

注意:
从智能标签的下拉列表里选择了某个步骤的话,将会更新CreateUserWizard控件的ActiveStepIndex属性,该属性指定了开始步骤的索引.因此,如果你在下拉列表里选择了“Your Settings”的话,界面设计完成后,记得将该步骤放在“Sign Up for Your New Account” 步骤后面,这样当用户访问EnhancedCreateUserWizard.aspx页面时,首先显示的是“Sign Up for Your New Account”步骤的界面.

在“Your Settings”这一步里构建用户界面,该界面包含3个TextBox控件,分别为HomeTown, HomepageUrl,以及Signature.构建完成后,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>

在浏览器里访问该页面并创建一个用户帐户,输入hometown,homepage,以及signature信息.CreateUserWizardStep步骤结束后就会在Membership framework里创建一个用户帐户,并运行CreatedUser事件,该事件向UserProfiles表添加一条新记录,只是HomeTown, HomepageUrl,以及Signature字段为NULL值,没有使用输入的home town, homepage,以及signature值.

我们需要在“Your Settings”这一步执行之后将用户指定的home town, honepage,以及signature值对UserProfiles表里的相应记录进行更新. 每次在Wizard控件里从这一步导航到另一步时就会触发ActiveStepChanged event事件.我们可以为该事件创建事件处理器,当“Your Settings”这一步完成以后就对相应的UserProfiles表里的记录进行更新.

我们来为CreateUserWizard的ActiveStepChanged事件创建如下事件处理器:
protected void NewUserWizard_ActiveStepChanged(object sender, EventArgs e)
{
// Have we JUST reached the Complete step?
if (NewUserWizard.ActiveStep.Title == "Complete")
{
WizardStep UserSettings = NewUserWizard.FindControl("UserSettings") as WizardStep;

// Programmatically reference the TextBox controls
TextBox HomeTown = UserSettings.FindControl("HomeTown") as TextBox;
TextBox HomepageUrl = UserSettings.FindControl("HomepageUrl") as TextBox;
TextBox Signature = UserSettings.FindControl("Signature") as TextBox;

// Update the UserProfiles record for this user
// Get the UserId of the just-added user
MembershipUser newUser = Membership.GetUser(NewUserWizard.UserName);
Guid newUserId = (Guid)newUser.ProviderUserKey;
// Insert a new record into UserProfiles
string connectionString =ConfigurationManager.ConnectionStrings["SecurityTutorialsConnectionString"].ConnectionString;

string updateSql = "UPDATE UserProfiles SET HomeTown= @HomeTown, HomepageUrl = @HomepageUrl, Signature = @Signature WHERE

UserId =@UserId";

using (SqlConnection myConnection = new SqlConnection(connectionString))
{
myConnection.Open();
SqlCommand myCommand = 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();
}
}
}

上述代码首先探测,看我们是否到“Complete” 这一步了.因为“Complete” 这一步是紧跟在“Your Settings” 这一步的.当用户到“Complete”这一步时,就说明他刚完成了“Your Settings”这一步.

在这种情况下,我们需要编程引用UserSettings WizardStep步骤里的那些TextBox控件.我们首先通过FindControl方法来引用UserSettings WizardStep;再引用WizardStep里的TextBoxes控件.一点引用了TextBoxes后,我们就可以执行UPDATE statement了.用用户提供的home town, homepage,以及signature值进行更新.

创建好该事件处理器后,在浏览器里登录EnhancedCreateUserWizard.aspx页面并创建一个用户帐户,指定home town, homepage, 以及signature信息.创建新帐户后再导航到AdditionalUserInfo.aspx页面,你就可以看到你刚才输入的 home town, homepage,以及signature信息了.

注意:
我们的站点目前有2个页面可供用户创建新帐户:CreatingUserAccounts.aspx 以及 EnhancedCreateUserWizard.aspx.目前站点的
网站地图和登录页面指向的是CreatingUserAccounts.aspx页面,但该页面没有让用户输入其home town, homepage,signature的信息;也没有向UserProfiles表添加新记录的功能.因此,你要么对CreatingUserAccounts.aspx页面进行更新以添加这些功能;要么对网站地图和登录页面进行更新以引用EnhancedCreateUserWizard.aspx页面而不是CreatingUserAccounts.aspx页面.如果你选择后一种方式,记得在Membership文件夹里的Web.config文件进行配置以允许匿名用户访问EnhancedCreateUserWizard.aspx页面.

结语:

在本文,我们探讨了如何为与Membership framework的用户帐户相关的数据建立模型的技术.此外,我们看到了如何将这些数据显示、添加、更新的操作.示例使用到了SqlDataSource控件,以及其他的ADO.NET代码.

到本文为止,对用户帐户的考察就结束了。下一章,我们将考察roles,在接下来的几章里,我们将考察Roles framework,看如何创建新roles,如何将角色分配给用户,如何判断某个用户属于什么角色,以及如何执行基于角色的授权.

祝编程愉快!

作者简介:

Scott Mitchell,著有七本ASP/ASP.NET方面的书,是4GuysFromRolla.com的创始人,自1998年以来一直应用 微软Web技术。Scott是个独立的技术咨询顾问,培训师,作家,最近完成了将由Sams出版社出版的新作,《24小时内精通ASP.NET 2.0》。他的联系电邮为[email protected],也可以通过他的博客http://ScottOnWriting.NET与他联系。

你可能感兴趣的:(sql,.net,server,Security,asp.net,asp)