`

Security Tutorials系列文章第五章:Creating User Accounts

阅读更多


Security Tutorials系列文章第五章:Creating User Accounts

本文英文原版及代码下载:
http://www.asp.net/learn/security/tutorial-05-cs.aspx

导言:
在第四章,我们在一个数据库里安装了application services schema,它添加了对SqlMembershipProvider 和 SqlRoleProvider来说必需的表、视图、存储过程.这为我们的后续文章打下了良好的基础.在本文,我们将使用Membership framework(通过SqlMembershipProvider)来创建新的用户账户.我们将看到如何通过编程的方式以及利用ASP.NET内置的CreateUserWizard控件来创建新用户帐户.

除了学习如何创建新用户帐户外,我们将对在前面文章里创建的演示站点进行改动.使站点包含一个login page,以使users’ credentials生效,而不是采用硬编码的username/password的形式来处理.此外,在Global.asax里包含了为通过认证的用户创建自定义IPrincipal 和 IIdentity对象的代码.我们将对login page进行更新,利用Membership framework来使users’ credentials生效,同时移除那些自定义的principal 和 identity逻辑.


Forms Authentication 和 Membership Checklist

在使用Membership framework之前,让我们花点时间来回顾一下这一路走来所执行的重要的步骤.在一个基于窗体的认证的使用SqlMembershipProvider的Membership framework里,在执行Membership功能之前,下面的步骤是必须执行的:

1.激活基于窗体的认证——正如我们在《An Overview of Forms Authentication》里探讨的那样,我们可以在Web.config文件里将<authentication>元素的mode属性设置为Forms来激活forms authentication.当激活后,每次后续请求抵达后,都要检查forms authentication ticket票据,如果有票据,请求就通过了认证.

2.将application services schema添加到恰当的数据库——当使用SqlMembershipProvider时,我们需要将application services schema安装到一个数据库里.通常就是应用程序用于存储数据的那个数据库.文章《Creating the Membership Schema in SQL Server》探讨了用aspnet_regsql.exe来实现.

3.自定义Web Application的设置以引用第2步里的数据库——文章《Creating the Membership Schema in SQL Server》探讨了2种配置web application以使SqlMembershipProvider调用第2步选择的数据库的方法:要么LocalSqlServer的连接字符串名称;要么注册一个新的provider,对该provider进行定制,以调用第2步里选择的数据库.

当开始构架使用SqlMembershipProvider和基于窗体认证的web应用程序时,在使用Membership class 或ASP.NET的Login Web control之前我们都必须执行这3步.由于我们在前面的文章里已经执行了这3个步骤,所以我们现在就要使用Membership framework了!


第一步:添加新的ASP.NET页面

在本文以及后面的3篇文章,我们将考察各种与Membership相关的函数和功能.我们需要一系列的页面来贯彻这些技术点.让我们创建这些页面和一个网站地图文件(Web.sitemap)吧.

我们首先在项目里添加一个名为Membership的文件夹,在里面添加5个ASP.NET页面,将每个页面与Site.master页面对应起来:

.CreatingUserAccounts.aspx
.UserBasedAuthorization.aspx
.EnhancedCreateUserWizard.aspx
.AdditionalUserInfo.aspx
.Guestbook.aspx
此时你的Solution Explorer看起来和下面的截屏差不多:


图1

每个页面应有2个Content controls,其中一个是对应母版页的ContentPlaceHolders:
如下:
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" Runat="Server"> </asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="LoginContent" Runat="Server"> </asp:Content>

记得LoginContent ContentPlaceHolder的默认代码将显示一个到登录或注销的链接,这取决于用户是否通过了认证.不过我们在Content2 Content控件里重写了母版页的默认代码.就像在《An Overview of Forms Authentication tutorial》里探讨的那样,如果我们不希望在页面左边部分显示与登录相关的选项的话,这样做是很有用的.

不过对这5个页面而言,删除Content2 Content的声明代码,这样一来,这5个页面就只包含1个Content控件了.


第二步:创建网站地图

很多网站都有便于用户导航的用户界面.该界面可能仅仅包含到站点各个部分的链接;或以菜单或树形结构的形式提供链接.作为页面开发人员,创建这样的界面仅仅是完成了一半的任务,我们还要将用于导航的逻辑结构便于维护和更新.比如,当添加一个新的页面或将某个页面删除时,我们希望对站点地图进行更新——并且这些改动可以通过导航用户界面反映出来.

要完成定义站点地图和执行基于站点地图的导航界面这2个任务是很容易的,当然这要归功于ASP.NET 2.0里的Site Map framework和Navigation Web控件. Site Map framework允许开发人员定义一个站点地图,并通过API(具体说就是SiteMap class)来进行访问.而内置的Navigation Web控件包括Menu control,TreeView control,以及SiteMapPath控件.

与Membership和Roles frameworks一样,Site Map framework也是采用的provider模式.Site Map provider class的作用就是在内存里生成一个供SiteMap class使用的构造器(in-memory structure),而SiteMap class从一个持久的数据存储——比如XML文件或一个数据库表.

在.NET Framework里以及包含了一个默认的Site Map provider(也就是XmlSiteMapProvider),它从一个XML文件里读取站点地图数据,本文使用的就是XmlSiteMapProvider.对Site Map provider的更多资料请参阅本文结尾处的外延阅读.

该默认的Site Map provider需要用到根目录下一个叫Web.sitemap的XML文件,因此我们要添加它.在项目的Solution Explorer上右击鼠标,选“Add New”项,在对话框里选择添加一个名为Web.sitemap的Site Map类型的文件.


图2:


该XML站点地图文件以层次结构的形式定义了站点的结构.这些层次结构关系是在该XML文件的<siteMapNode>元素里定义的.该Web.sitemap文件必须以一个<siteMap>节点开始,其下面刚好有一个<siteMapNode>子节点.最顶层的<siteMapNode>元素代表的是站点体系结构的根节点,其下可有任意数量的派生节点.每个<siteMapNode>元素必须包含一个title属性,以及可选的url 和 description属性,以及其它的节点.每个非空的url属性必须是全局唯一的.

在Web.sitemap文件里输入如下的XML代码:
<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="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:


第三步:在母版页里添加一个可导航的用户界面

ASP.NET里与导航相关的控件有Menu, TreeView,以及SiteMapPath.其中Menu和TreeView
控件分别以菜单和树形结构的形式呈现站点的层次结构.SiteMapPath控件则有所不同,它按照层次结构将用户当前访问的节点,及其父节点,以此类推地将各层显示出来.我们可以用SiteMapDataSource将站点地图数据绑定到其它的Web控件上,也可以通过SiteMap class,以编程的方式访问站点地图数据.

对Site Map framework 和 Navigation controls的深入探讨以及超出了本系列文章的范围,为了简便,我们使用《Working with Data in ASP.NET 2.0》这个系列文章所用过的导航用户界面.它通过一个Repeater控件来展示导航链接,如图4所示.


Adding a Two-Level List of Links in the Left Column


为了创建这样的导航界面,在Site.master母版页的左边,也就是文本“TODO: Menu will go here...”所在的地方添加如下的声明代码:

<ul>
<li>
<asp:HyperLink runat="server" ID="lnkHome" NavigateUrl="~/Default.aspx">Home</asp:HyperLink>
</li>
<asp:Repeater runat="server" ID="menu" DataSourceID="SiteMapDataSource1"> <ItemTemplate>
<li>
<asp:HyperLink ID="lnkMenuItem" runat="server" NavigateUrl='<%# Eval("Url") %>'><%# Eval("Title") %></asp:HyperLink>

<asp:Repeater ID="submenu" runat="server" DataSource="<%# ((SiteMapNode)Container.DataItem).ChildNodes %>">

<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<asp:HyperLink ID="lnkMenuItem" runat="server" NavigateUrl='<%# Eval("Url") %>'><%# Eval("Title") %></asp:HyperLink>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</li>
</ItemTemplate>
</asp:Repeater>
</ul>
<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" ShowStartingNode="false" />

上述代码将一个名为menu的Repeater控件绑定到一个SiteMapDataSource控件,该控件返回的是Web.sitemap里定义的站点地图的层次结构.

由于SiteMapDataSource控件的ShowStartingNode属性设置为False,因此它返回的层次结构是从“Home”节点的派生节点开始的.该Repeater控件将每个派生节点(目前就只有“Membership”)在一个<li>元素里显示出来.此外,在Repeater内部,还将当前节点的子节点也显示在一个镶套的列表里.

图4显示的是将我们在第二步里定义的站点地图的层次结构显示出来的效果.而在Styles.css文件里定义的层叠式样式表(CSS)负责显示效果.

对上述代码的工作原理的深入探讨请参阅《Master Pages and Site Navigation》系列文章.

图4


Adding Breadcrumb Navigation

除了在左边列出一系列的链接外,我们也要在每页上显示一个breadcrumb.所谓的breadcrumb就是一个导航用户界面元件,它可以显示用户当前所在位置位于站点结构的对应层次.SiteMapPath控件利用Site Map framework来确定当前页面在站点地图的位置,然后根据该信息显示一个breadcrumb.


具体来说,在母版页头部的<div>元素里添加一个<span>元素,将该<span>元素的class属性设置为“breadcrumb”(在Styles.css里有对“breadcrumb”的相应规则).接下来,在该<span>元素里添加一个新的SiteMapPath:

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

</asp:SiteMapPath>
</span>
</div>

图5显示的是当访问~/Membership/CreatingUserAccounts.aspx页面时候的SiteMapPath控件的效果

图5


第四步:移除自定义的Principal 和 Identity逻辑

在文章《Forms Authentication Configuration and Advanced Topics》里,我们看到了如何将通过认证的用户和我们自定义的principal 和 identity对象联系起来。

虽然在某些情况下自定义的principal 和 identity对象很有用,但在绝大多数情况下,仅用GenericPrincipal 和 FormsIdentity对象就完全够应付了.因此,我觉得使用默认的处理方式就行了.为此,我们要么将PostAuthenticateRequest事件处理器删除或注释掉,要么直接将Global.asax文件删除.


第五步:编程创建新用户

要通过Membership framework创建一个新用户帐户,要用到Membership class的CreateUser方法.该方法接受username, password,以及其他和用户相关的输入参数.当调用该方法时,它将新用户帐户的创建委托给配置好的Membership provider,再返回一个反映刚刚创建的用户帐户的MembershipUser对象.


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

.CreateUser(username, password)

.CreateUser(username, password, email)

.CreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, MembershipCreateStatus)

.CreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, MembershipCreateStatus)


这四个重载的不同在于输入参数的数目不同而已.为什么有这些重载方法呢?因为创建一个新用户帐户所需要的信息取决于Membership provider的配置情况.在文章《Creating the Membership Schema in SQL Server》里我们考察了在Web.config文件里设置Membership provider的配置选项.

需要注意的是requiresQuestionAndAnswer属性的设置.如果将它设置为true (默认值),当创建一个新帐户时,必须指定具体的安全提示问题和答案,当以后用户需要重新设置密码的时候需要这些信息.当其设置为true时,调用头2个重载函数将抛出一个异常,因为没有安全提示问题和答案信息.因此我们必须使用最后2个重载函数之一.


为了演示CreateUser方法的使用,让我们创建一个用户界面,供用户提供name, password, email,安全提示问题和答案等信息.我们在Membership文件夹里打开CreatingUserAccounts.aspx页面,添加如下的Content控件:

.一个id为Username的TextBox控件
.一个id为Password的TextBox控件,其TextMode属性为Password
.一个id为Email的TextBox控件
.一个id为SecurityQuestion的Label控件,清除其Text属性
.一个id为SecurityAnswer的TextBox控件
.一个id为CreateAccountButton的Button控件,其TextMode属性为“Create the User Account”
. 一个id为CreateAccountResults的Label控件,清除其Text属性


这样一来,你的界面看起来应该和下面的差不多:

图6


我们注意到这些安全提示问题和密码都是依照user-by-user的原则,这样才可能允许每个用户定义其自己的安全提示问题.就本例而言,我们决定采用一个很常见的安全提示问题:“你最喜欢的是什么颜色?”

为了贯彻该安全提示问题,我们在页面的后台代码类里添加一个名为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;
}


接下来,为CreateAccountButton的Click事件创建一个事件处理器:

protected void CreateAccountButton_Click(object sender, EventArgs e)
{ MembershipCreateStatus createStatus;
MembershipUser newUser = Membership.CreateUser(Username.Text, Password.Text, Email.Text,
passwordQuestion, SecurityAnswer.Text, true, out createStatus);

switch (createStatus)
{
case MembershipCreateStatus.Success:
CreateAccountResults.Text = "The user account was successfully created!";
break;

case MembershipCreateStatus.DuplicateUserName:
CreateAccountResults.Text = "There already exists a user with this username.";
break;

case MembershipCreateStatus.DuplicateEmail:
CreateAccountResults.Text = "There already exists a user with this email address.";
break;

case MembershipCreateStatus.InvalidEmail:
CreateAccountResults.Text = "There email address you provided in invalid.";
break;

case MembershipCreateStatus.InvalidAnswer:
CreateAccountResults.Text = "There security answer was invalid.";
break;

case MembershipCreateStatus.InvalidPassword:
CreateAccountResults.Text = "The password you provided is invalid. It must be seven characters long and have at least one

non-alphanumeric character.";
break;

default:
CreateAccountResults.Text = "There was an unknown error; the user account was NOT created."; break;
}
}


在该事件处理器里我们开始创建了一个类型为MembershipCreateStatus的名为createStatus的变量.MembershipCreateStatus用于指明CreateUser操作的情况.比如,如果创建用户帐户成功,那么MembershipCreateStatus的实例的值就会被赋值为Success;而如果系统已经存在该用户名,那么值就被赋值为DuplicateUserName;在上面我们使用的重载函数里,我们必须传递一个MembershipCreateStatus实例作为out参数,在CreateUser方法内部,将对该输出参数赋一个恰当的值.我们通过考察该参数的值来判断用户帐户是否创建成功.

当调用CreateUser方法,传入createStatus输出参数后,我们用一个switch声明来输出一个相应的消息,当然这取决于createStatus的值.

图7显示的是当成功创建一个用户帐户时的输出界面;而图8和图9显示的是创建失败是界面.其中,图8是因为当用户输入的是5位的密码,低于系统要求的最短长度,而图9是因为要创建的用户已经在系统里存在了(我们在图7里就创建了该用户).

图7


图8

图9
注意:
你可能想知道,如果使用头2种重载方法来创建用户帐户的话,如何来判断是否创建成功呢?比较2个这重载没有类型为MembershipCreateStatus的输出参数.实际上,如果创建失败的话,这2种重载方法将抛出一个MembershipCreateUserException异常,该异常就包含一个类型为MembershipCreateStatus的StatusCode属性.

创建一些用户帐户,然后在SecurityTutorials.mdf数据库里的aspnet_Users 和 aspnet_Membership表里检验这些帐户信息,如图10所示,我在CreatingUserAccounts.aspx页面里创建了2个用户:Tito 和 Bruce.


图10

虽然Membership用户存储现在包含了Bruce 和 Tito的帐户信息,但我们还没有实现允许Bruce 或 Tito登录站点的功能.目前,Login.aspx页面是对硬编码的username/password验证用户信息——而不是通过Membership framework来进行验证.在下一章《Validating User Credentials Against the Membership User Store》里,我们会对该页进行改动以对Membership用户存储的用户帐户信息进行验证.
注意:
如果你没有在SecurityTutorials.mdf数据库里看到任何用户帐户信息的话,很可能是
因为你的web应用程序使用的是默认的Membership provider——AspNetSqlMembershipProvider,它使用的是ASPNETDB.mdf数据库作为它的用户存储.要解决这个问题,在Solution Explorer里点“刷新”.如果在App_Data文件夹里添加了一个ASPNETDB.mdf数据库,那就说明就是这个原因.你可以返回《Creating the Membership Schema in SQL Server》文章的第四步,看如何恰当地配置Membership provider.

创建用户帐户的绝大多数情形是访问者在某个界面里输入他们的username, password, email,以及其他的必要信息,然后基于这些信息来创建用户帐户.在这一步,我们看如何手动添加这样的界面,以及如何以编程的方式用Membership.CreateUser方法来创建用户帐户.我们的代码

仅仅创建新帐户,而没有进行其它的处理,比如让刚刚创建的帐户处于登录状态,或向用户发送一个确认电邮等.这些额外的功能需要在按钮的Click事件处理器里添加相应的代码.

ASP.NET里有一个CreateUserWizard控件,它就是设计来处理创建用户帐户的.包括创建用户帐户的用户界面,在Membership framework里创建帐户,以及其他相关的功能,比如发送一个确认电邮、使刚刚创建的帐户登录站点等.使用该控件很简单,在绝大多数情况下不写一行代码就可以满足我们的需要,在第六步里我们将详细的探讨这个极棒的控件.

虽然使用CreateUserWizard控件我们不写一行代码就可以满足我们的需要,但是在某些情况下我们还是要用到CreateUser方法,比如你想对创建帐户的用户体验进行高度的定制,或你不想用CreateUserWizard控件的那种用户界面来创建用户帐户.比如,你可能有这样 的一个页面,允许用户上载一个包含用户信息的XML文件,该页面将对XML文件的内容进行解析,再调用CreateUser方法来创建一个新的用户帐户.


第六步:Creating a New User with the CreateUserWizard Control

ASP.NET里有一些Login Web控件,这些控件在常见的与用户帐户或登录相关的场合下都是很有适用的.CreateUserWizard控件就是其中一个.


和其他与登录相关的控件一样,我们不写一行代码就可以使用CreateUserWizard控件.它根据Membership provider的配置呈现一个相应的注册界面,当用户输入必要的帐户信息并点击“Create User”按钮后,它就在内部调用Membership类的CreateUser方法创建帐户.我们可以对CreateUserWizard控件进行高度的定制.在创建用户帐户的不同阶段可以触发很多的事件,如果有必要的话,我们可以创建一个事件处理器,执行我们自己的处理逻辑.
此外,CreateUserWizard控件的外观也是很灵活的.有很多的属性来控制其默认界面,如果有必要的话,我们也可以将它转化为一个模板,或添加我们自己的处理步骤.


Examining the CreateUserWizard’s Default Interface and Behavior

返回Membership文件夹里的CreatingUserAccounts.aspx页面,切换到设计或分割模式,在页面顶端添加一个CreateUserWizard.设其ID为RegisterUser,如图11所示,在控件默认的界面包含用于输入username, password,电邮地址,安全提示问题和答案的文本框.


图11


将CreateUserWizard控件生成的默认界面与我们在第五步创建的界面进行比较.最开始,CreateUserWizard控件允许注册者指定安全提示问题和答案,而在我们手动添加的界面里使用的是预先定义好了的安全提示问题.该控件的界面还包含了验证控件,而我们手动添加的界面却没有对表单域进行验证.另外,该控件的界面里还有一个“Confirm Password” 文本框(同时还有一个CompareValidator控件,确保用户在“Password” 和 “Compare Password”文本框里输入的内容是一样的).

有趣的是CreateUserWizard控件将根据Membership provider的配置情况来呈现相应的用户界面.比如,只有requiresQuestionAndAnswer设置为True时,才会显示question 和 answer文本框.同样的,CreateUserWizard控件还会自动添加一个RegularExpressionValidator控件以确保密码长度合乎要求.还会根据minRequiredPasswordLength, minRequiredNonalphanumericCharacters, 和passwordStrengthRegularExpression这3个配置选项的情况来设置ErrorMessage 和ValidationExpression属性.

正如其名字暗示的那样,CreateUserWizard控件源于Wizard控件. 而Wizard控件提供了一个界面以处理多步骤的任务.Wizard控件可以有任意数量的WizardSteps,每个WizardSteps都是一个模板,可以在里面定义该步要使用到的HTML 和 Web控件. Wizard控件最开始显示的是第一个WizardStep,还有便于用户向前一步或向后一步的导航控件.


就像图11里的声明代码显示的那样,CreateUserWizard控件默认的界面包含了2个WizardSteps:

.CreateUserWizardStep——呈现一个界面供用户输入注册信息,这也是图11显示的那个界面.

.CompleteWizardStep——显示一条消息,指出用户帐户已经成功创建了.

我们可以对CreateUserWizard的界面和行为进行定制,方法就是将这些步骤转化成模板,或添加你自己的WizardSteps.我们将在后面的文章《Storing Additional User Information》里进行探讨.

让我们看看一个实际的CreateUserWizard控件.通过浏览器访问CreatingUserAccounts.aspx页面.在其界面里输入一些无效的数值.比如,密码长度不足,或将“User Name” 文本框置空.这样,CreateUserWizard控件会显示一个相应的错误信息.图12显示的是输入的不当密码时的显示的界面.


图12


接下来,在CreateUserWizard控件里输入恰当的值,点“Create User”按钮.假定所有的输入都合乎要求,CreateUserWizard控件将通过Membership framework创建一个新的用户帐户,然后显示CompleteWizardStep界面,如图13所示.在该过程中,CreateUserWizard在内部调用Membership.CreateUser方法,和我们在第五步那样做的一样.


图13


注意:
如你在图13看见的那样,CompleteWizardStep界面包含一个Continue按钮.现在,当你点击它时,仅仅执行一个页面回传而已.在下面的“Customizing the CreateUserWizard’s Appearance and Behavior Through Its Properties”部分,我们将看到,当你点击它时,如何导航到Default.aspx页面(或其它什么页面).


创建一个新用户帐户后,查看aspnet_Users 和 aspnet_Membership表,就像我们在图10做的那样,验证帐户是否成功创建.


Customizing the CreateUserWizard’s Behavior and Appearance Through Its Properties


我们可以通过多种途径对CreateUserWizard进行定制,比如:属性、WizardSteps,以及event handler.在本节,我们考察如何通过其属性来对控件的外观进行控制.在下一节,我们看如何通过event handler来扩展控件的行为.

实际上,CreateUserWizard控件默认界面的所有text都可以通过众多的属性进行定制.比如,“User Name”, “Password”, “Confirm Password”, “E-mail”, “Security Question”, 和 “Security Answer”这些label都可以分别通过UserNameLabelText, PasswordLabelText, ConfirmPasswordLabelText, EmailLabelText, QuestionLabelText, 以及AnswerLabelText属性进行定制.同理,我们还可以在CreateUserWizardStep 和CompleteWizardStep里指定“Create User” 和“Continue”按钮的文本,还可以指定这些按钮是Buttons,

LinkButtons,还是ImageButtons.

对colors, borders, fonts以及其它的视觉元素,我们可以通过一系列的style属性来进行设置.CreateUserWizard控件还有一些通用的Web控件属性——BackColor, BorderStyle, CssClass, Font等等.也有一些属性用于定义特定部分的外观,拿TextBoxStyle属性来说,它定义了CreateUserWizardStep里的textboxe的样式;而TitleTextStyle属性定义了标题(“Sign Up for Your New Account”)的样式.

除了这些与外观相关的属性外,还有一系列的属性可以影响CreateUserWizard控件的行为.比如,如果将DisplayCancelButton属性设为True(默认值为False),那么就会在“Create User”按钮旁边显示一个Cancel按钮,同时还要记得设置CancelDestinationPageUrl属性,它用于指定当用户点击Cancel按钮后将用户导航到哪个页面.另外,就像我们在上一节提到的那样,点击CompleteWizardStep里的Continue按钮,仅仅产生一个页面回传,如果点击该按钮后,想将用户导航到其它某个页面的话,只需要为ContinueDestinationPageUrl属性指定一个URL即可.

我们来对RegisterUser CreateUserWizard控件进行改动,以包含一个Cancel按钮,并且当用户点击Cancel或Continue按钮时将用户导航到Default.aspx页面. 为此,设置 DisplayCancelButton属性为True,并将CancelDestinationPageUrl 和 ContinueDestinationPageUrl属性设为“~/Default.aspx”. 如图14所示的是改动后的CreateUserWizard控件.


图14


当访问者输入username, password, email address,以及安全提示问题和答案,点“Create User”后,将创建一个新帐户,并以刚创建的

那个帐户登录站点.不过还有一种情况,比如,你可能想让Administrator来创建一个新帐户,但创建成功后依然以 Administrator的身份登录

站点,而不是以刚创建的那个新帐户登录站点,这就需要修改LoginCreatedUser属性的布尔值了.

Membership framework里的User account包含一个approved(审核)标记;凡是未通过审核的的用户是无法登录站点的.默认下,新创建的用

户帐户都标记为approved,允许这些帐户马上登录站点.不过有可能我们需要把新帐户标记为unapproved(未审核).比如,我们希望

Administrator手动进行审核,或你希望检查用户提供的电子邮件地址真实有效后才允许他们登录站点.无论是哪种情况,如果你希望将新创建

的用户标记为unapproved的话,只需要将DisableCreatedUser属性设置为True(默认为False)即可.


其它我们还应该注意的与行为相关的属性包括AutoGeneratePassword 和 MailDefinition.如果AutoGeneratePassword属性设置为True,那

么CreateUserWizardStep就不会显示“Password”和“Confirm Password”文本框,而是调用Membership类的 GeneratePassword方法自动地为

用户创建密码.

如果在创建帐户的过程中,你想向用户提供的电邮地址发送邮件的话,MailDefinition属性就可以派上用场了.该属性包含一系列的模板

(subproperties),来确定电邮发送的内容.这些模板包括诸如Subject, Priority, IsBodyHtml, From, CC,和BodyFileName的选项.其中

BodyFileName属性指明了邮件内容主体包含的text或HTML文件.该内容主体包含2个预定义的占位符:<%UserName%>和<%Password%>.如果这些占

位符包含在BodyFileName文件里,那么将被刚创建的用户帐户的name 和 password替换掉.
注意:
CreateUserWizard控件的MailDefinition属性仅仅指定了邮件消息的细节,而并没有指定邮件实际发送的细节(也就是说,是使用的SMTP server还是mail drop directory等等细节).这些底层细节需要在Web.config文件的<system.net>节点定义.关于如何设置这些配置,以及在ASP.NET 2.0里发送邮件的大体情况的更多详情,请参阅站点FAQs at SystemNetMail.com,以及我的文章《Sending Email in ASP.NET 2.0》.


Extending the CreateUserWizard’s Behavior Using Event Handlers

CreateUserWizard控件在处理过程中会触发一系列的事件.比如,当用户输入信息后,点击“Create User”按钮时,将触发CreatingUser事件;如果,在创建过程中发生任何问题将触发CreateUserError事件;如果创建成功,就会触发CreatedUser事件.还有其它的事件,不过这3个是最常见的事件.

在某些情况下,我们可能需要介入CreateUserWizard的处理流程,为某个特定的事件创建一个事件处理器.我们来做个演示,使RegisterUser CreateUserWizard控件对username 和 password执行自定义验证.具体来说,username前后不能有空格,且username和password不能连在一起.简单的说,用户名不能为"Scott ", 或username/password不能连在一起,如“Scott.1234”.

为此,我们为CreatingUser事件创建一个事件处理器以执行额外的验证检查.如果输入的数据有问题就取消创建流程.我们也需要在页面上添加一个Label控件,用于显示一条消息,指出username或password有问题.首先,在CreateUserWizard控件下面添加一个Label控件,设置其ID为InvalidUserNameOrPasswordMessage,而ForeColor属性设为Red.清除其Text属性,再将EnableViewState 和 Visible属性设为False,如下:

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

</asp:Label>

接下来,为CreateUserWizard控件的CreatingUser事件创建一个事件处理器.要创建一个事件处理器,切换到设计模式,选中该控件,打开其属性窗口.在属性窗口里点击闪电图标,在相应的事件上双击以创建对应的事件处理器.在CreatingUser事件处理器里添加如下的代码:

protected void RegisterUser_CreatingUser(object sender, LoginCancelEventArgs e)
{
string trimmedUserName = RegisterUser.UserName.Trim();
if (RegisterUser.UserName.Length != trimmedUserName.Length)
{
// Show the error message
InvalidUserNameOrPasswordMessage.Text = "The username cannot contain leading or trailing spaces.";

InvalidUserNameOrPasswordMessage.Visible = true;

// Cancel the create user workflow
e.Cancel = true;
}

else

{
// Username is valid, make sure that the password does not contain the username
if (RegisterUser.Password.IndexOf(RegisterUser.UserName, StringComparison.OrdinalIgnoreCase)>= 0)
{
// Show the error message
InvalidUserNameOrPasswordMessage.Text = "The username may not appear anywhere in the password.";

InvalidUserNameOrPasswordMessage.Visible = true;

// Cancel the create user workflow
e.Cancel = true;
}
}
}

我们注意到,在CreateUserWizard控件里输入的username 和 password,可以通过该控件的UserName 和 Password属性获取, 在上述代码里我们检查输入的username是否有前后空格,以及password里是否还包含了username;如果出现了上述情况之一,在InvalidUserNameOrPasswordMessage Label控件里就会出现一个出错信息,并将事件处理器的e.Cancel属性设置为true. 如果e.Cancel为true,那么CreateUserWizard就取消用户帐户创建流程.

图15:


注意:
在后面的《Storing Additional User Information》文章里,我们将看到使用CreateUserWizard控件的CreatedUser事件的示例.


结语:

Membership类的CreateUser方法在Membership framework里创建一个新的用户帐户.它是通过将调用委托给我们配置好的Membership provider,在本文里,也就是SqlMembershipProvider.该CreateUser方法将向aspnet_Users 和 aspnet_Membership数据库表里添加一条记录.

如果以编程的方式创建用户帐户的话(就像我们在第五步里做的那样),最快最方便的方式是使用CreateUserWizard控件.它以多步骤的界面方式收集用户信息,并以此在Membership framework里创建用户帐户.在内部,该控件使用的是Membership.CreateUser方法,和我们在第五步里那样做的一样。只不过不用手写一行代码,它就可以提供用户界面,验证控件,并且可以反映出创建过程中出现的错误.

此时,我们完成了创建用户帐户的功能。然而,在登录页面,我们是对第二篇文章里硬编码的用户信息进行的验证.在下一篇文章里,我们将对Login.aspx页面进行更新,对Membership framework提供的用户信息进行验证.

祝编程快乐!


作者简介:

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

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics