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看起來和下面的截屏差不多:
每個頁面應有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類型的文件.
該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
圖9
注意:
你可能想知道,如果使用頭2種重載方法來創建用戶帳戶的話,如何來判斷是否創建成功呢?比較2個這重載沒有類型為MembershipCreateStatus的輸出參數.實際上,如果創建失敗的話,這2種重載方法將拋出一個MembershipCreateUserException異常,該異常就包含一個類型為MembershipCreateStatus的StatusCode屬性.
創建一些用戶帳戶,然后在SecurityTutorials.mdf數據庫里的aspnet_Users 和 aspnet_Membership表里檢驗這些帳戶信息,如圖10所示,我在CreatingUserAccounts.aspx頁面里創建了2個用戶:Tito 和 Bruce.
雖然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,電郵地址,安全提示問題和答案的文本框.
將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顯示的是輸入的不當密碼時的顯示的界面.
接下來,在CreateUserWizard控件里輸入恰當的值,點“Create User”按鈕.假定所有的輸入都合乎要求,CreateUserWizard控件將通過Membership framework創建一個新的用戶帳戶,然后顯示CompleteWizardStep界面,如圖13所示.在該過程中,CreateUserWizard在內部調用Membership.CreateUser方法,和我們在第五步那樣做的一樣.
注意:
如你在圖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 與他聯系。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
