第21章 成员资格(Membership)

(本文译自 Pro ASP.NET 2.0 IN C# 2005 ,译得不到之处,欢迎批评指正)

21 成员资格(Membership)

一方面,窗体认证为实现安全,自定义登录,提供了关键性的基础。另一方面,实现登录窗体的任务以与底层的信任连接对于每个Web 应用来讲,总是相同的,而这些工作是单调乏味的。你应该记住的一点是,窗体认证仅仅提供了用户认证的基础。如果你在使用自定义的信任存储,你必须编写管理应用来管理用户。

这就是ASP.NET小组从开发者社区获得的反馈。因此,这个小组添加了新的成员资格(MembershipAPIASP.NET20中。成员资格API是一个基于既存的窗体认证(form authentication)基础之上的框架。使用成员资格API时,你不需要实现登录页面或者信任存储。本章中,你将对Membership API有详细了解。

ASP.NET成员资格API介绍(Introducing the ASP.NET Membership API

Membership API框架提供了箱外(out of the box)的用户管理函数的完整集:

l         能够通过程序或者ASP.NET Web配置工具来创建和删除用户。

l         能够通过发送邮件给用户来重置密码(用户应当提供了邮件地址)。

l         能够自动为后台创建的用户产生密码。当然,密码能够通过邮件发送给用户。

l         能够在底层的数据存储中找到用户,并且能够获取用户列表和用户详细信息。

l         预编译了控件集来创建登录页面、注册页面,显示登录状态,为认证和非认证用户提供不同视图。

l         通过所谓的Membership提供者类,提供一个抽象层,以使应用独立于底层的数据存储。目前列出的任何函数因此与底层数据存储完全独立,并且数据存储能够被其它的数据存储类型所替换,而这并不需要对应用有丁点修改。

21-1显示了Membership API的基础架构,它包含了提供者、API和创建用户接口的控件。

21-1 Membership API的架构

设计Membership API的目的就是要使其与底层的数据存储完全独立。作为应用开发者,使用ASP.NET提供的控件与使用Membership类是一样的。Membership提供了一系列方法,通过程序访问存储的用户和角色。这些方法与Membership提供者一道工作。这个提供者实现了对底层数据存储的访问。表21-1描述了Membership的组件。

Component  

 Description  

 Membership  

 The Membership class is the primary point of interaction with the Membership API. It provides a couple of methods for managing users, validating users, and resetting user passwords.  

MembershipCreateUserException

An exception is thrown if an error occurs when you try to create a user through the Membership class.  

 MembershipUser  

Represents a single user stored in a Membership API credential store. This object contains all information about this user and is returned through several methods of the Membership class such as GetUser.

 MembershipUserCollection  

A collection of Membership users. For example, the GetUsers method of the Membership class returns an instance of this collection.  

 MembershipProvider  

 This is the base class that you derive from if you want to create a custom Membership provider that authenticates users against your custom credential store.

 MembershipProviderCollection

 A collection of available Membership providers on the machine and for this web application.  

SqlMembershipProvider

An implementation of the MembershipProvider class that works with SQL Server databases.  

ActiveDirectoryMembershipProvider  

 An implementation of the MembershipProvider class that works with Active Directory.  

ActiveDirectoryMembershipUser  

 This class inherits all the functionality from MembershipUser and adds some Active Directoryspecific properties.  

21-1 Membership API 组件

ASP.NETSQL Server提供者和活动目录(Active Directory,允许你创建自定义的登录页面来将用户存储于活动目录中)移植而来。但提供者的思想是它们使你能够完全扩展基础结构。因此,你可以编写自己的Membership提供者,这个提供者是一个基础的类,从System.Web.Security.MembershipProvider提供者继承而来。Membership提供者主要通过Web.config配置文件来进行配置的。Web.config文件中包含一个新的<membership/>节。第26章中将对自定义Membership提供者有更深入的了解。

注意,尽管Membership API作为一个提供者支持活动目录,但使用Windows认证和使用Membership API来在Web窗体中进行认证还是有很大的不同。Membership API是基于窗体认证的。信任关系是以明文方式通过网络传递的(除非使用SSL),并且窗体认证证书用于认证,这在前面的章节中已进行介绍。另一方面,当配置Windows认证时,用户通过NTML或者通过Kerberos进行认证。这两个方法都非常安全。因此认证并不会通过网络传递。

Membership API用于管理和认证用户。它并不实现任何认证功能,也不提供管理用户角色的功能。因此,你必须使用Roles API。认证和角色管理功能的更多信息见第23章。

使用Membership API(Using the Membership API)

在使用ASP.NET Membership API和安全控件前,必须完成下列步骤:

1、      web.config文件中正常配置为forms认证,配置拒绝匿名用户访问。

2、      安装Membership数据存储。例如,如果你使用的是SQL Server,则必须在SQL Server数据库中创建一些表和存储过程。

3、      配置数据库连接字符串和使用的Membership提供者。

4、      使用ASP.NET web配置工具或者自定义管理页面,在Membership存储中用户。

5、      使用预编译的Login控件创建登录页面,或者使用Membership类创建一个登录页面,由Membership类对输入进行信任检验和对用户进行认证。

可以使用ASP.NET WAT执行提供者配置之外的配置步骤。ASP.NET WAT提供了一个安全向导,如图21-2所示:

21-2 WAT中进行安全配置

如果使用ASP.NET的计算机上同时安装了SQL Server 2005,你不需要设置数据存储和配置Membership提供者。只需要在WAT中使用安全向导就可以了,如图21-2中所示那样。可以直接开始将用户添加到Membership存储中。所需要的底层数据存储将会在创建第一个用户时自动创建。即便是通过程序来访问Membership存储,它仍会自动创建,因为这个功能是由SqlMembershipProvider提供的。

但是如果你想使用自己的数据库来作为数据存储,则在登录安全向导前,必须为提供者配置Membership提供者和连接信息。在后面的章节中将会对配置步骤和Membership API的工作机理有更多了解。

配置窗体认证(Configuring Forms Authentication

Membership API是基于窗体认证之上,提供了一个箱外的(out-of-the-box)结构来管理和认证用户。因此,第一步,通常必须将应用配置为窗体认证。但此时的解决方法会稍有不同。一般地,Web应用的根目录允许匿名用户访问,而限制访问的资源存储在子目录中。这些子目录有它们自己的web.config配置文件来拒绝匿名用户访问。如果有人想要访问这些安全目录中的资源,ASP.NET运行时会自动将用户重定向到登录页面。通常情况下,对于用户可访问的根目录,包含了一些功能页面,如登录和注册页面。图21-3中可以看到web应用的结构,它显示了Visual Studio项目中的解决方法窗口中的结构:

21-3 具有安全区域的web应用的目录和文件结构

因此,在应用的根目录里,只需要配置窗体认证就可以了 ,如下所示:

<system.web>

<authentication mode="Forms" />

</system.web>

你已经看到,这个配置明确了使用窗体认证,允许匿名用户访问网页。在有安全要求的子目录中,你需要在web.config文件中添加如下的内容:

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

<system.web>

<authorization>

<deny users="?" />

</authorization>

</system.web>

</configuration>

这个配置拒绝任何匿名用户访问网站的安全子目录。如果有未经认证的客户试图访问这个目录中的资源,ASP.NET会自动将用户重定向到登录页面。当然,你必须创建自己的登录页面,但使用Membership API会更方便和快捷。在“使用安全控件”一节中你将了解到Login控件。

创建数据存储(Creating the Data Store

使用Membership API时,必须使用Membership提供者来设置要使用的数据存储。使用SQL Server时,这意味着会创建一些数据库表到既存的或者新建的数据库中。幸运的是,.NET框附带了一个工具:aspnet_resql.exe,用于自动创建表。使用自定义提供者时,则必须根据自定义提供者规范来准备和配置数据存储。

可以通过两种方式来使用aspnet_regsql.exe:向导接口或者命令行。如果使用工具时没有带参数,就会启动向导接口来引导你完成创建数据库和进程,如图21-4所示:

21-4 aspnet_regsql.exe向导接口

向导提供了一些选项来方便你创建必要的数据库或者从既存的数据库中移除表。如果选择<default>选项,它就会在指定的服务器上查找名为aspnetdb的数据库。如果该数据库不存在,则创建这个数据库,并在其中创建表。如果在目标数据库中表已存在,则、向导则对其不做处理。

前面已经提到,你也可以通过命令行使用aspnet_regsql.exe工具。事实上,这是自动进行应用设置的好方法,它仅仅从命令行调用这个工具,并且自动设置你的应用所需要的ASP.NET数据库表。例如,要设置Membdership API数据库表,你可以执行如下命令:

Aspnet_regsql –S (local) –E all –d MyDatabase

21-5显示了执行命令后的结果。

21-5 执行aspnet_regsql.exe来安装数据库

21-2描述了asqnet_regsql.exe工具用于Membership API和相关的ASP.NET应用服务的命令行开关。

 

 

Switch  

 Description  

 -S servername  

 Specifies the SQL Server and instance for which you want to install the ASP.NET database tables.  

 -U username  

 The SQL Server database user with which you want to connect to SQL Server.  

 -P password  

 If the -U switch is specified, you need to specify the password switch as well.  

 -E  

 If you dont specify -U and -P, you automatically connect through Windows authentication to the SQL Server instance specified in -S. With -E, you can explicitly specify to connect through Windows authentication to the SQL Server.  

 -C  

 Allows you to specify a full-fledged ODBC or OLEDB connection string for connecting to the database.  

 -sqlexportonly  

 Creates the SQL scripts for the specified option without installing them on a dedicated SQL Server instance.  

 -A  

 Installs application services. The valid options for this switch are all, m, r, p, c, and w. The command in the previous example used the option all for installing all application services; m is dedicated to Membership. r means role services, p means ASP.NET profiles for supporting user profiles, and c stands for personalization of web part pages.  

 -R  

 Uninstalls application services. This switch supports the same option as -A and uninstalls the corresponding database tables for the application services.  

 -d  

 Lets you optionally specify the name of the database into which you want to install the application services. If you dont specify this parameter, a database named aspnetdb is created automatically (as is the case with the <default> option for the database in the wizard interface).  

21-2 aspnet_regsql.exe的命令行开关

Aspnet_regsql.exe工具包含了一系列额外的开关用于安装基于SQL Server数据库的会话状态(session state)和配置SQL缓存依赖(cache dekpendendy)。对于会话状态,请参阅第6章。第11章将对缓存(caching)和缓存依赖作更多介绍。

用于ASP.NET服务的数据库脚本

Aspnet_regsql.exe工具执行一系列脚本来创建(或移除)成员资格相关的数据库和数据库表。这些脚本随.NET框架发布。可以在.NET框架的目录中找到它们,如图21-6所示:

21-6 安装和卸载SQL数据库的脚本

有两个基本的脚本类型:InstallXXX和对应的UnistallXXX脚本。当InstallXXX脚本安装一系列数据库表,如Membership API所需的设置。相应的UnistallXXX脚本移除相同的表和数据库。表21-3 描述了.NET install脚本。

Script  

 Description  

 InstallCommon.sql  

 Installs some common tables and stored procedures necessary for both the Membership and Roles APIs. This includes tables for identifying ASP.NET applications that use other ASP.NET features such as the Membership API, role service, or personalization.  

 InstallMembership.sql  

 Installs the database tables, stored procedures, and triggers used by the Membership API. This includes tables for users, additional user properties, and stored procedures for accessing this information.  

 InstallRoles.sql  

 Installs all database tables and stored procedures required for associating users with application roles. These roles will be used for authorization, as you will learn in Chapter 23.  

 InstallPersonalization.sql  

 Contains DDL for creating any table and stored procedure required for creating personalized portal applications with web parts.  

 InstallProfile.sql  

 Creates all the necessary tables and stored procedures for supporting ASP.NET 2.0 user profiles.  

 InstallSqlState.sql  

 Installs tables for persistent session state in the TEMP database of SQL Server. That means every time the SQL Server service is shut down, the session state gets lost.  

 InstallPersistSqlState.sql  

 Installs tables for persistent session state in a separate ASPState database. That means the state stays alive even if the SQL Server service gets restarted.  

21-3 Membership API Installation脚本

可以使用osql.exe或者sqlcmd.exe.来执行这些脚本。Osql.exeSQL Server2000版本发布,sqlcmd.exe存在于SQL Server 2005版本中,用于通过命令行执行脚本。例如,要在SQL Server Express版本中安装普通的数据库表,可以执行下面的命令:

sqlcmd -S (local)/SQLExpress -E -i InstallCommon.sql

-S开关指明了SQL Server服务器和实例名。通常情况下不需要实例名(位于 / 之后),但SQL Server Express版本会作为一个命名的实例来安装,以便以于你在同一台计算机上可以安装更多的SQL Server版本和实例。因此,对于SQL Server Express Edition,你必须指明实例名(默认为SQLExpree)。

-E开关指明通过Windows认证来访问SQL Server

- i 开关指明用来执行的SQL脚本。图21-7显示了执行前面的命令后的结果。

21-7 SQL Server Express上安装ASP.NET数据库表

 

基于文件的SQL Server存储(File-Based SQL Server Staore

SQL Server 2005支持file-only数据库模式,允许你通过MDF文件来直接访问SQL Server数据库,而不必在SQL Server实例中创建和附加它们。使用这个功能,随着应用文件拷贝数据库文件到目标服务器上运行应用就成为可能。SQL Server提供者于是使用连接字符串来直接访问数据库文件。SQL Server自动附加数据库(临时的),并且允许你通过文件直接访问它,而这并不需要额外的配置步骤。当然,前提是在目标计算机上安装了SQL Server 2005

这些数据库文件位于应用的特定的App_Data子目录中。当使用默认配置应用ASP.NET时,这个文件会被自动创建。但到底是什么导致了创建这个文件呢?答案非常简单:当一个功能第一次请求特定的函数类型时,提供者自动创建数据库文件和其中必要的内容。因此,当你第一次运行安全向导时,会在第一次创建用户时自动创建数据库。这个功能是由SqlMembershipProvider类提供的。(真正的实现包含在unitlity类中,由Sql Provider类,如SqlRoleProvider使用)。

配置连接字符串和成员资格提供者(Configuring Connection String and Membership Provider

使用默认的配置和SQL Server 2005(Express Edition或者完全版本),你不需要准备数据存储和配置成员资格提供者,因此ASP.NET运行时使用基于文件的SQL Server 2005提供者,并且为你自动创建数据库文件。

但是如果你想使用自己的SQL Server数据库,甚至使用你自定义的成员资格提供者和存储,你则必须正确配置提供者和连接字符串到成员资格存储数据库。为此,你必须直接编辑web.config文件,或者通过IIS控制台来进行配置(如果你的应用在IIS中运行的话)。

在使用SQL Server存储(或者其它基于数据库的存储)的情况下,你必须首先配置连接字符串。这在web.config文件中的<connectionStrings/>中进行。例如,假设你想使用名为MyDataBase的本地数据库,数据库中通过aspnet_regsql.exe工具安装了数据库表,你应当这样配置连接字符串(记住,<connectingStrings>节位于<configuration/>元素之内):

<configuration>

<connectionStrings>

<add name="MyMembershipConnString"

connectionString="data source=(local);Integrated Security=SSPI;

initial catalog=MyDatabase" />

</connectionStrings>

</configuration>

为配置好连接字符串之后,你必须为应用配置成员资格提供者。为此,你必须添加<membership/>节到web.config文件中(位于<system.web/>节内):

<system.web>

<authentication mode="Forms" />

<authorization>

<deny users="?"/>

</authorization>

<membership defaultProvider="MyMembershipProvider">

<providers>

<add name="MyMembershipProvider"

connectionStringName="MyMembershipConnString"

applicationName="MyMembership"

enablePasswordRetrieval="false"

enablePasswordReset="true"

requiresQuestionAndAnswer="true"

requiresUniqueEmail="true"

passwordFormat="Hashed"

type="System.Web.Security.SqlMembershipProvider" />

</providers>

</membership>

</system.web>

<membership/>节内,你可以添加多个提供者作为<providers/>节的子元素。在前面的代码中,你可以看到针对SqlMembershipProvider的有效配置。别忘记defaultProvider属性,这一点很重要。这个属性指明将会使用Membdership提供者,而不会覆盖代码中已经使用的提供者。当选择为每个特征选择不同的提供者项后,配置后的提供者显示在ASP.NET Web配置之中,如图21-8所示:

21-8 WAT中选择配置的提供者

21-4描述了可以为SqlMembershipProvider配置的属性。

Property  

 Description  

 Name  

Specifies a name for the Membership provider. You can choose any name you want. This name can be used later for referencing the provider when programmatically accessing the list of configured Membership providers. Furthermore, this name will be used by the WAT to display the provider.  

 ApplicationName  

Specifies the name of the application for which the Membership provider manages users and their settings.  

 Description  

An optional description for the Membership provider.  

 PasswordFormat  

Gets or sets the format in which passwords will be stored in the underlying credential store. Valid options are Clear for clear-text password storage, Encrypted for encrypting passwords in the data store (uses the locally configured machine key for encryption), and Hashed for hashing passwords stored in the underlying Membership store.  

 MinRequiredNonAlphanumericCharacters  

Specifies the number of nonalphanumeric characters the password needs to have. This is an important part for the validation of the password and enables you to specify strength requirements for the passwords used by your users.  

 MinRequiredPasswordLength  

 Allows you to specify the minimum length of passwords for users of your application. This is also an important property for specifying password strength properties.  

 PasswordStrengthRegularExpression  

 If the previously mentioned properties are not sufficient for specifying password strength conditions, then you can use a regular expression for specifying the format of valid passwords. With this option you are completely flexible in terms of specifying password format criteria.  

 EnablePasswordReset  

 The Membership API contains functionality for resetting a users password and optionally sending an e-mail if an SMTP server is configured for the application.  

 EnablePasswordRetrieval  

 When set to true, you can retrieve the password of a MembershipUser object by calling its GetPassword method. Of course, this works  only if the password is not hashed.  

 MaxInvalidPasswordAttempts  

 Specifies the number of invalid validation attempts before the user gets locked.  

 PasswordAttemptWindow  

 Here you can set the number of minutes in which a maximum number of invalid password or password question-answer attempts are allowed before the user is completely locked out from the application. In that case, the user gets locked out, so the administrator must activate the account again.

 RequiresQuestionAndAnswer  

 Specifies whether the password question with an answer is required for this application. This question can be used if the user has forgotten his password. With the answer he gets the possibility of retrieving an automatically generated, new password via e-mail.  

 RequiresUniqueEmail  

 Specifies whether e-mail addresses must be unique for every user in the underlying Membership store.  

21-SqlMembershipProvider的属性

现在,在设置数据存储和配置成员资格提供者之后,你可以通过WAT创建用户来测试配置。工具包含了一个连接来测试配置,测试时使用配置的成员资格提供者来连接到数据库,如图21-9所示。

21-9 测试成员提供者配置

创建和认证用户(Creating and Authenticating Users

要在先前创建的成员资格提供者存储中创建新的用户,通过Visual Studio 2005菜单:网站>>ASP.NET网站配置菜单登录WAT,切换到安全页面,选择创建用户,如图21-10所示。

21-10 使用WAT创建用户

创建了一系列用户后,可以连接到数据库,查看aspnet_Useraspnet_Membership表,如图21-11所示。

21-11 成员资格数据库中的aspnet_User

如果在<memebership>配置节中为提供者选择了passwordFormat = “Hashed”选项,则密码和密码问题答案都以哈希方式存储在数据库中。添加用户到Membership存储中后,可以使用成员资格 API来对用户进行认证。因此,必须创建一个登录页面来要求用户名和密码,并且对其进行校验:

protected void LoginAction_Click(object sender, EventArgs e)

{

if (Membership.ValidateUser(UsernameText.Text, PasswordText.Text))

{

FormsAuthentication.RedirectFromLoginPage(UsernameText.Text, false);

}

else

{

LegendStatus.Text = "Invalid user name or password!";//LegendStatus可能是一label.

}

}

你不必知道应用到底使用了哪个提供者。如果你想使用不同的成员资格提供者,只需要改变配置,以便成员资格 API使用不同的提供者。应用也不知道底层提供者的细节。在下一节,你将了解到新的安全控件。你会明白根本不再需要手工创建登录页面。

使用安全控件(Using the Security Controls

到目前,你仍必须自己一次又一次地创建登录页面。但是ASP.NET20发布了几个新的控件来简化创建登录页面的过程,也提供了其它相关功能。在本节中,你会进一步学习ASP.NET中新的安全控件。这些安全控件依赖于底层的窗体认证和成员资格 API结构。表21-5描述了随ASP.NET一同发布的安全控件。

Control  

 Primary Purpose  

 Login  

The Login control is a composite control that solves the most common task for forms authenticationbased applicationsdisplaying a user name and password textbox with a login button. Furthermore, if events are caught through custom event procedures, it automatically validates the user against the default Membership provider.  

 LoginStatus  

The login status is a simple control that validates the authentication state of the current session. If the user is not authenticated, it offers a login button that redirects to the configured login page. Otherwise, it displays a sign-out button for the possibility of logging off.  

 LoginView  

This is really a powerful control that allows you to display different sets of controls for authenticated and unauthenticated users. Furthermore, it allows you to display different controls for users who are in different roles, as you will see in Chapter 23.

 PasswordRecovery  

This allows the user to retrieve the password if the user has provided an e-mail address during registration. It requests the user name from the user and then automatically displays a user interface that displays the password question and requests the appropriate answer from the user. If the answer is correct, it uses the Membership API to send the password to the user.  

 ChangePassword  

This control is a composite control that requests the old password from the user and lets the user enter a new password including the password confirmation.  

CreateUserWizard  

Includes a complete wizard that guides the user (or an administrator) through the creation process of a user.  

21-5 新的ASP.NET安全控件

你可以同其它任何控件一些使用这些控件。例如,你可以在主页面或者单独页面中使用登录控件。每个控件都以相同的方式工作:如果你不捕获任何自定义事件,所有这些控件默认与成员资格API一起工作。只要你捕获控件提供的事件,就必须负责完成事件处理任务。例如,Login控件支持认证事件。如果不捕获这个事件,它自动使用成员资格 API。如果捕获事件,你必须自己负责校验用户信任。

登录控件(The Login Control

登录控件提供了一个准备好的用户接口来要求用户名和密码,并且提供登录按钮来使用户登录。图21-12显示了登录控件的一个示例。

21-12 使用中的登录控件

在登录控件背后,它其实不过就是一个ASP.NET组合控件。它是完全可扩展的,允许你重载任何设计风格和属性,同时通过重载默认行为捕获控件抛出的事件。如果不捕获任何登录控件的事件,它就自动使用配置的成员资格提供者。页面上最简单的登录控件是这样的:

<form id="form1" runat="server">

<div style="text-align: center">

<asp:Login ID="Login1" runat="server">

</asp:Login>

</div>

</form>

你可以使用几个属性来改变控件的外观。你可以使用登录控件提供的不同的风格设置,如下所示:

<form id="form1" runat="server">

<div style="text-align: center">

<asp:Login ID="Login1" runat="server"

BackColor="aliceblue" BorderColor="Black" BorderStyle="double">

<LoginButtonStyle BackColor="darkblue" ForeColor="White" />

<TextBoxStyle BackColor="LightCyan" ForeColor="Black" Font-Bold="true" />

<TitleTextStyle Font-Italic="true" Font-Bold="true" Font-Names="Verdana" />

</asp:Login>

</div>

</form>

你可以使用CSS类来自定义登录控件的外观。每个由登录控件支持的风格属性包含CssClass属性。与其它ASP.NET控件一样,这个属性允许你为添加到网站的登录控件设置CSS类名。假设你添加了下面的CSS样式表MyStyles.css到项目中:

.MyLoginTextBoxStyle

{

cursor: crosshair;

background-color: yellow;

text-align: center;

border-left-color: black;

border-bottom-color: black;

border-top-style: dotted;

border-top-color: black;

border-right-style: dotted;

border-left-style: dotted;

border-right-color: black;

border-bottom-style: dotted;

font-family: Verdana;

vertical-align: middle;

}

CSS文件的内容定义了用于登录控件中的文本框的风格.MyLoginTextBoxStyle。你可以在你的登录网页中包含这个文件,以便于可以将风格用于登录控件,如下所示:

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">

<title>Untitled Page</title>

<link href="MyStyles.css" rel="stylesheet" type="text/css" />

</head>

<body>

<form id="form1" runat="server">

<div style="text-align: center">

BackColor="aliceblue"

BorderColor="Black" BorderStyle="double">

<LoginButtonStyle BackColor="darkblue" ForeColor="White" />

<TextBoxStyle CssClass="MyLoginTextBoxStyle" />

<TitleTextStyle Font-Italic="true" Font-Bold="true"

Font-Names="Verdana" />

</asp:Login>

</div>

</form>

</body>

</html>

如果CSS文件放置到匿名用户不能访问的目录,则运行网页时不会应用风格,因为CSS文件被ASP.NET运行时所保护。如果将CSS文件放入根目录,但拒绝匿名用户访问根目录,效果也是一样的。因此,如果想在登录控件上应用CSS文件,要么将CSS文件放到允许匿名用户访问的目录,要么添加下面的配置内容到web.config文件中:

<location path="MyStyles.css">

<system.web>

<authorization>

<allow users="*" />

</authorization>

</system.web>

</location>

我们倾向于将可公开的资源放入单独的文件夹中,而限制对web应用的其它位置的访问。第23章将对认证和配置步骤进行更深入的介绍。

21-6 列出了登录控件支持的风格。每个风格都以相同的方式工作。你可以直接设置颜色或者字体属性,也可以使用CssClass属性来分配CSS类。

Style

Description

CheckBoxStyle

Defines the style properties for the Remember Me check box.

FailureStyle

Defines the style for the text displayed if the login was not successful.

HyperLinkStyle

The Login control allows you to define several types of hyperlinks, for example, to a registration page. This style defines the appearance of these hyperlinks.

InstructionTextStyle

The Login control allows you to specify help text that is displayed directly in the Login control. This style defines the appearance of this text.

LabelStyle

Defines the style for the User Name and Password labels.

LoginButtonStyle

Defines the style for the login button.

TextBoxStyle

Defines the style for the User Name and Password text boxes.

TitleTextStyle

Defines a style for the title text of the Login control.

ValidatorTextStyle

Defines styles for validation controls that are used for validating the user name and password.

21-6 登录控件支持的风格

这些可自定义的风格并不是仅仅用于登录控件。任何在控件中显示的内容通过几个属性都是可以自定义的。例如,可以为登录按钮选择文本显示,也可以将登录按钮显示为登录链接。不仅如此,你还可以添加几个链接到登录控件中,如到帮助页面或到注册页面的超级链接。当然,这些页面对于匿名用户都应当是可访问的,因为帮助应该向匿名用户开放(记住,如果某人看到了登录控件,他可能就是一个匿名用户)。如果想在登录控件中包含其它链接,修改前面显示的控件:

<asp:Login ID="Login1" runat="server"

BackColor="aliceblue"

BorderColor="Black" BorderStyle="double"

CreateUserText="Register"

CreateUserUrl="Register.aspx" HelpPageText="Additional Help"

HelpPageUrl="HelpMe.htm"

InstructionText="Please enter your user name and password for <br>

logging into the system.">

<LoginButtonStyle BackColor="DarkBlue" ForeColor="White" />

<TextBoxStyle CssClass="MyLoginTextBoxStyle" />

<TitleTextStyle Font-Italic="True" Font-Bold="True" Font-Names="Verdana" />

</asp:Login>

这段代码显示了两个附加的链接,一个链到帮助页面,另一个到注册页面,并且在登录控件的头部下面添加了一些简短的指导性文字。前面讨论的网格应用到这些属性中。表21-7描述了用于自定义登录控件的相关属性。

Property  

 Description  

 TitleText  

 The text displayed as the heading of the control.  

 InstructionText  

 You have already used this property in the previous code snippet, which contains text that is displayed below the heading of the control.  

 FailureText  

 This is the text displayed by the Login control if the login attempt was not successful.  

 UserNameLabelText  

 The text displayed as a label in front of the user name text box.  

 PasswordLabelText  

 The text displayed as a label in front of the password text box.  

 UserName  

Initial value filled into the user name text box.  

 UsernameRequiredErrorMessage  

 Error message displayed if the user has not entered a  user name.

 PasswordRequiredErrorMessage  

 Error message displayed if the user has not entered a password.  

 LoginButtonText  

 The text displayed for the login button.  

 LoginButtonType  

 The login button can be displayed as a link, button, or image. For this purpose, you have to set this property appropriately. Supported values are Link, Button, and Image.  

 LoginButtonImageUrl  

 If you display the login button as an image, you have to provide a URL to an image that is displayed for the button.  

 DestinationPageUrl  

 If the login attempt was successful, the Login control redirects the user to this page. This property is empty by default. If empty, it uses the forms authentication infrastructure for redirecting either to the originally requested page or to the defautlUrl configured in web.config for forms authentication.  

 DisplayRememberMe  

 Enables you to show and hide the Remember Me check box. By default this property is set to true.  

 FailureAction  

 Defines the action the control performs after a login attempt failed. The two valid options are Refresh and RedirectToLoginPage. The first one refreshes just the current page, and the second one redirects to the config-ured login page. Of course, the second one is useful if you use the control anywhere else instead of the login page.  

 RememberMeSet  

 Defines the default value for the Remember Me check box.By default this option is set to false, which means the check box is not checked by default.  

 VisibleWhenLoggedIn  

 If set to false, the control automatically hides itself if the user is already logged in. If set to true (default), the Login control is displayed even if the user is already logged in.  

 CreateUserUrl  

 Defines a hyperlink to a page in the website that allows you to create (register!) a user. Therefore, this is typically used for enabling the user to access a registration page. Typically this page displays the CreateUserWizard control.  

 CreateUserText  

 Defines the text displayed for the CreateUserUrl hyperlink.  

 CreateUserIconUrl  

 Defines a URL to an image displayed together with the text for the CreateUserUrl hyperlink.  

 HelpPageUrl  

 URL for redirecting the user to a help page.  

 HelpPageText  

 Text displayed for the hyperlink configured in the HelpPageUrl property.  

 HelpPageIconUrl  

 URL to an icon displayed together with the text for the HelpPageUrl hyperlink.  

 PasswordRecoveryUrl  

 URL for redirecting the user to a password recovery page. This page is used if the user has forgotten the password. Typically this page displays the PasswordRecovery control.  

 PasswordRecoveryText  

 The text displayed for the hyperlink configured in PasswordRecoveryUrl.  

 PasswordRecoveryIconUrl  

 Icon displayed together with the text for the PasswordRecoveryUrl.  

21-7 登录控件的相关自定义属性

模板和登录控件(Templates and the Login Control

正如你所见到那样,控件通过这些属性进行了几乎完全的自定义。但你同时也可能看到,你不能为验证输入定义任何验证表达式。当然,你可以在服务器端进行验证,通过登录控件提供的事件处理程序来完成。但是,通常情况下,如果你想添加任何控件到登录控件中,你却不能通过前面介绍的属性来做到这一点。例如,在一些控制页面上,你有额外的文本框来进行第二个密码或者用户访问钥匙来验证,那该怎么办?

幸运的是,登录控件如其它控件,如GricView控件一样,支持模板。使用模板,你能够自定义登录控件的内容,而这并不会有任何限制。你能够添加任何控件到登录控件中。例如,可以通过LayoutTemplate标签来为登录控件自定义模板:

<asp:Login ID="LoginCtrl" runat="server"

BackColor="aliceblue"

BorderColor="Black"

BorderStyle="double">

<LayoutTemplate>

<h4>Log-In to the System</h4>

<table>

<tr>

<td>

User Name:

</td>

<td>

<asp:TextBox ID="UserName" runat="server" />

<asp:RequiredFieldValidator ID="UserNameRequired"

runat="server"

ControlToValidate="UserName"

ErrorMessage="*" />

<asp:RegularExpressionValidator ID="UsernameValidator"

runat="server"

ControlToValidate="UserName"

ValidationExpression="[/w| ]*"

ErrorMessage="Invalid User Name" />

</td>

</tr>

<tr>

<td>

Password:

</td>

<td>

<asp:TextBox ID="Password" runat="server" TextMode="Password" />

<asp:RequiredFieldValidator ID="PasswordRequired"

runat="server"

ControlToValidate="Password"

ErrorMessage="*" />

<asp:RegularExpressionValidator ID="RegularExpressionValidator1"

runat="server"

ControlToValidate="Password"

ValidationExpression='[/w| !"§$%&amp;/()=/-?/*]*'

ErrorMessage="Invalid Password" />

</td>

</tr>

</table>

<asp:CheckBox ID="RememberMe" runat="server" Text="Remember Me" />

<asp:Literal ID="FailureText" runat="server" /><br />

<asp:Button ID="Login" CommandName="Login"

runat="server" Text="Login" />

</LayoutTemplate>

</asp:Login>

使用正确的控件和ID值,你不需要写一行代码来处理事件。代码通常情况下会正常运行,除了你定义了一系列控件和这些控件的布局。事实上,登录控件要求至少两个文本框,其ID分别为UserNamePassword。如果没有这两个文本框(或者没有这些ID值),控件就会抛出一个异常。所有其它控件都是可选的。但如果你指定了相应的ID值(如为登录按钮指定了Login),则登录控件会自动处理事件和行为,正如在使用预定义的控件布局时一样。表21-8列出了特殊的ID值,它们要求控件类型、是否被请求或者为可选。

Control ID

Control Type

Required?

UserName

System.Web.UI.WebControls.Textbox

Yes

Password

System.Web.UI.WebControls.Textbox

Yes

RememberMe

System.Web.UI.WebControls.CheckBox

No

FailureText

System.Web.UI.WebControls.Literal

No

Login

Any control that supports event bubbling and a CommandName

No

21-8 用于登录模板的特殊控件

具有ID Login的控件可以是支持事件发生和CommandName属性的任何控件。对Login设置CommandName属性是非常重要的,因为如果不这样,它在事件处理程序就不会被登录控件所识别。你可以添加与登录控件没有关系的其它控件,并赋予其它ID。前面的代码包含了用于正确验证用户名和密码的RequiredFieldValidatorRegularExpressionValidator控件。如果不使用CommandName集的控件到Login中,你则必须处理控件的事件,并且编写合适的代码来验证用户名和密码,并且将网页重定向到正常请求的页面。

使用LayoutTemplate时,通常由控件提供的许多属性就不再能够获得。使用模板时,只有下列属性可用:

. DestinationPageUrl

. VisibleWhenLoggedIn

. FailureAction

. MembershipProvider

. Password

. Username

. RememberMeSet

所有风格属性和几个用于配置默认控件的文本内容的属性也不再可用,因为你可以将它们作为分离控件或者静态文本添加登录控件模板。

登录控件程序设计(Programming the Login Control

登录控件支持一系列事件和属性,你可以用这些事件和属性来自定义控件的行为。这使你能够完全控制对登录控件的自定义(可以与其它自定义,如模板和自定义风格属性一起进行)。登录控件支持的事件列于表21-9中。

Event  

 Description  

 LoggingIn  

 Raised before the user gets authenticated by the control.  

 LoggedIn  

 Raised after the user has been authenticated by the control.  

 LoginError  

 Raised when the login of the user failed for some reason (such as a wrong password or user name).  

 Authenticate  

 Raised to authenticate the user. If you catch this event, you have to authenticate the user on your own, and the Login control completely relies on your authentication code.  

21-9 登录控件的事件

在用户认证过程中,如果发生了错误,你可以捕获前面三个事件,并且执行一些操作。例如,你可以在规定次数的尝试失败后,使用LoginError事件来自动重定向用户到密码恢复页面,如下所示:

protected void Page_Load(object sender, EventArgs e)

{

if (!this.IsPostBack)

ViewState["LoginErrors"] = 0;

}

protected void LoginCtrl_LoginError(object sender, EventArgs e)

{

// Increase the number of invalid logins

int ErrorCount = (int)ViewState["LoginErrors"] + 1;

ViewState["LoginErrors"] = ErrorCount;

// Now validate the number of errors

if ((ErrorCount > 3) && (LoginCtrl.PasswordRecoveryUrl != string.Empty))

Response.Redirect(LoginCtrl.PasswordRecoveryUrl);

}

登录控件按下列顺序释放事件:

21-13 登录控件事件的顺序

如前面提到的那样,如果你捕获了事件,你必须添加自己的代码来验证用户名和密码。Authenticate属性支持AuthenticateEventArgs的实例到参数列表中。这个事件参数类支持属性Authenticated。如果设置这个属性为true,登录控件就假定认证成功,并且唤起LoggedIn事件。如果设置为false,就显示FailureText,并且唤起LoginError事件。

protected void LoginCtrl_Authenticate(object sender, AuthenticateEventArgs e)

{

if (YourValidationFunction(LoginCtrl.UserName, LoginCtrl.Password))

{

e.Authenticated = true;

}

else

{

e.Authenticated = false;

}

}

你应当已经看到,你可以通过UserNamePassword属性直接访问到输入的值。这两个属性包含了输入到相应文本框中的文本。如果使用模板控件,并且要求除了UserNamePassword ID对应的控件之外的其它控件的值,你可以使用控件的FindControl方法来获取控件。这个方法要求控件ID,返回一个System.Web.UI.Control的实例。于是你将控件转换到正确的类型,并且读取要求的值,提供给自定义验证的方法。下面的登录控件使用了一个带有额外控件的模板,在后面的Authenticate事件代码中将用到该控件:

<asp:Login ID="OtherLoginCtrl" runat="server"

BackColor="aliceblue"

BorderColor="Black"

BorderStyle="double"

PasswordRecoveryUrl="~/pwdrecover.aspx"

OnAuthenticate="OtherLoginCtrl_Authenticate">

<LayoutTemplate>

<font face="Courier New">

Userskey: <asp:Textbox ID="AccessKey" runat="server" /><br />

User Name: <asp:TextBox ID="UserName" runat="server" /><br />

Password: <asp:TextBox ID="Password" runat="server"

TextMode="password" Width="149px" /><br />

<asp:Button runat="server" ID="Login"

CommandName="Login" Text="Login" />

</font>

</LayoutTemplate>

</asp:Login>

在前面的代码示例中,用户的key是一个额外的值,必须由用户提供才能成功登录。为了将这个值包含到认证验证程序中,你必须修改Authenticate事件的内容,如下:

protected void OtherLoginCtrl_Authenticate(object sender, AuthenticateEventArgs e)

{

TextBox AccessKeyText = (TextBox)OtherLoginCtrl.FindControl("AccessKey");

if (YourValidation(AccessKeyText.Text, AuthenticateEventArgs e))

{

e.Authenticated = true;

}

else

{

e.Authenticated = false;

}

}

当然,在这种情况下,你不能使用任何默认的成员资格提供者。你必须实现自己的验证函数来接收这些额外参数。登录控件会强制你根本不能使用成员资格。验证函数可以是你想要的任何函数类型。你只需要正确设置e.Authenticated属性就可以。因此,你可以将登录控件用任何想要的登录机制。

登录状态控件(The LoginStatus Control

登录状态控件是一个简单的控件,用于显示登录链接(如果用户未通过认证),或者显示退出链接(如果用户已通过认证)。登录链接自动重定向到配置的登录页面,而退出链接自动调用方法FormsAuthentication.SignOut来使用户退出。这个控件相当简单,对其进行自定义也相当简单:

<asp:LoginStatus ID="LoginStatus1" runat="server"

LoginText="Sign In"

LogoutText="Sign Out"

LogoutPageUrl="~/Default.aspx"

LogoutAction="Redirect" />

LoginStatus控件提供了一系列属性来自定义链接文本的显示和当用户点击链接时URL的重定向。表21-10中可以找到最重要的属性。

Property  

 Description  

 LoginText  

 The text displayed if the user is not signed in.  

 LoginImageUrl  

 A URL for an image displayed as an icon for the login link.  

 LogoutText  

 The text displayed if the user is authenticated.  

 LogoutImageUrl  

 A URL for an image displayed as icon for the logout link.  

 LogoutAction  

 Configures the action the control performs if the user clicks the logout link that is displayed when the user is authenticated. Valid options are Refresh, Redirect, and RedirectToLoginPage. The first option just refreshes the current page, the second option redirects to the page configured in the LogoutPageUrl, and the last option redirects to the login page.  

 LogoutPageUrl  

 A page to redirect to if the user clicks the logout link and the LogoutAction is set to Redirect.  

21-10 自定义LoginStatus控件的属性

登录视图控件(The LoginView Control

这个控件相当简单,但功能相当强大。它允许你为匿名用户和认证用户显示不同的控件集。而且,它允许你基于当前登录的用户的角色显示不同的内容。关于角色和角色与LoginView控件的连接将在第23章详述。现在,你将学习如何为匿名用户和认证用户显示不同的内容。

LoginView控件是一个有不同类型模板的模板控件。一个模板用于匿名用户,一个用于认证的用户,还有一个用于支持基于角色的模板。在这些模板里,你只需要为相应的情形添加要显示的控件就可以了(基于角色的模板封装到了RoldGroup控件中,详见第23章),如下所示:

<asp:LoginView ID="LoginViewCtrl" runat="server">

<AnonymousTemplate>

<h2>You are anonymous</h2>

</AnonymousTemplate>

<LoggedInTemplate>

<h2>You are logged in</h2>

Submit your comment: <asp:TextBox runat="server" ID="CommentText" />

<br />

<asp:Button runat="server" ID="SubmitCommentAction" Text="Submit" />

</LoggedInTemplate>

</asp:LoginView>

前面的控件为匿名用户显示一些简单的文本,而在文本框中的文本和按钮是为登录后的用户显示的。如图21-14所示。不仅如此,控件支持两种事件,你可以在显示之前,捕获它们来正确初始化不同模板的内容控件。

l         ViewChanging,在控件显示由另一个模板里定义的内容之前唤起。

l         ViewChanged,在控件改变内容显示到另一个模板之后唤起。

21-14 分别对于匿名用户和认证用户不同的LoginView控件

密码恢复控件(The PasswordRecovery Control

密码恢复控件对于用户忘记密码后非常有用。它要求提供用户名字,然后自动显示该用户的密码问题。如果用户输入了正确的答案,密码会自动通过邮件传送给用户(邮件地址先前必须正确配置)。图21-15显示了使用中的密码恢复控件。

21-15 密码恢复控件的使用

控件包含三个自定义的视图模式。首先,用户必须输入用户名。当用户点击提交按钮的时候,控件通过Membership API从底层的信任存储中查询密码问题。第二,显示问题,并且要求用户输入正确的答案。当用户输入正确的答案后,自动产生密码或者将存储的密码通过邮件发送给用户。这个邮件地址是注册用户时设置的。如果发送成功,控件显示确认视图。任何邮件的配置都通过控件属性进行,如下所示。当然,密码仅仅在不是哈希存储时才能发送给用户。因此,必须配置成员资格提供者为对密码加密存储或以明文存储。如果成员资格提供者存储的密码是哈希方式,它自动产生一个新的、随机的密码,然后在邮件中将新密码发送给用户。

<asp:PasswordRecovery ID=" PasswordRecoveryCtrl" runat="server"

BackColor="Azure"

BorderColor="Black" BorderStyle="solid">

<MailDefinition From="proaspnet2@apress.com"

Subject="Forgotten Password"

Priority="high" />

<TitleTextStyle Font-Bold="true" Font-Italic="true" BorderStyle="dotted" />

<TextBoxStyle BackColor="Yellow" BorderStyle="double" />

<FailureTextStyle Font-Bold="true" ForeColor="Red" />

</asp:PasswordRecovery>

控件要求一个e-mail SMTP服务器来发送邮件。因此,你必须在web.config文件中配置SMTP邮件服务器,如下:

<system.web>

<smtpMail serverName="MyServer"

serverPort="15"

from="proaspnet2@apress.com">

<fields>

<add name="smtpauthenticate" value="2">

</fields>

</smtpMail>

</system.web>

(译注:smtpMail类位于System.Web.Mail名字空间中,已经不再使用,改用命名空间System.Net.Mail中的类,如SmtpClient,相关内容请查阅MSDN

MailDefinition允许你设置基本的属性。同时,通过BodyFileName属性,你可以指定包含了e-mail文本的文件名字。这个文件必须与控件所在的网页文件位于相同的目录。如果控件放置在其它的用户控件中,则文件必须位于包含了该用户控件的网页所在的目录。密码恢复控件支持不同类型的属性来指定控件不同部分的格式和布局选项(正如登录控件一样)。支持的属性的完整列表,请参阅MSDN文档;这些属性与登录控件的属性相似。在密码恢复过程中,控件唤起几个不同的事件。你可以捕获这些事件来自定义控件要完成的动作。图21-11表出了这些事件。

Event

Description

 VerifyingUser  

Raised before the control starts validating the user name entered. Validating the user name means looking for the user in the Membership store and retrieving the password question information.  

 UserLookupError  

If the user name entered in the user name text box doesnt exist in the Membership store, this event is raised before the failure text is displayed.  

 VerifyingAnswer  

When the user clicks the submit button in the second step, the answer for the question is compared to the one stored in the Membership store. This event is raised before this action takes place.  

 AnswerLookupError  

If the answer provided by the user is not correct, this event is raised by the control.  

SendingEmail  

This event is raised by the control after the answer submitted by the user has been identified as the correct answer and before the e-mail is sent through the mail server.

SendMailError  

If the e-mail cannot be sent for some reason (for example, the mail server is not available), this event is raised by the control.  

21-11 密码恢复控件的事件

你可以使用这些事件来准备信息以便由控件处理。例如,如果你想将用户名全部转化为小写字母,以便与存储在成员资格存储中的内容进行比较。你可以在VerifyingUser事件中完成这一步骤。相似地,你也可以使用VerifyingAnswer来预处理信息,然后再由控件进行处理。这两个事件获取LoginCancelEventArgs类型的参数。LoginCancelEventArgs包含了Cancel属性。如果设置这个属性为false,就可以取消整个密码恢复过程。

捕获SendingEmail事件时,你可以修改e-mail信息的内容,然后再将e-mail发送给用户。传递的MailMessageEventArgs包含了一个Message属性,代表真正的e-mail信息。通过修改Message的属性,如Attachments集,你可以添加附件,配置接收人地址,或完成其它与e-mail信息相关的事情。

密码恢复模板(PasswordRecovery Templates

登录控件一样,密码恢复控件能够进行完全自定义。控件支持模板的每一个视图:

l         UsernameTemplate包含了密码恢复过程中第一步的所有控件,它要求用户输入用户名。

l         第二步(密码问题)的控件,被放置在QuestionTemplate中。

l         最后,控件控件支持SuccessTemplateSuccessTemplate包含用于显示密码发送成功后的确认信息的控件。

每个模板都包含几个必要的控件。例如,UsernameTemplate要求一个文本框来输入用户名。QuestionTemplage要求一个文本框来输入问题,而SuccessTemplate要求一个Literal控件来显示最后的确认信息。一个模板PasswordRecovery控件可能这样:

<asp:PasswordRecovery ID=" PasswordTemplateCtrl" runat="server">

<MailDefinition From="pwd@apress.com"

Priority="high"

Subject="Important information" />

<UserNameTemplate>

<span style="text-align: center">

<font face="Courier New">

<h2>Forgotten your Password?</h2>

Please enter your user name:<br />

<asp:TextBox ID="UserName" runat="server" />

<br />

<asp:Button ID="SubmitButton" CommandName="Submit"

runat="server" Text="Next" />

<br />

<span style="color: Red">

<asp:Literal ID="FailureText" runat="server" />

</span>

</font>

</span>

</UserNameTemplate>

<QuestionTemplate>

<span style="text-align: center">

<font face="Courier New">

<h2>Forgotten your Password?</h2>

Hello <asp:Literal ID="UserName" runat="server" />! <br />

Please answer your password-question:<br />

<asp:Literal ID="Question" runat="server" /><br />

<asp:TextBox ID="Answer" runat="server" /><br />

<asp:Button ID="SubmitButton" CommandName="Submit"

runat="Server" Text="Send Answer" /><br />

<asp:Literal ID="FailureText" runat="server" />

</span>

</font>

</span>

</QuestionTemplate>

<SuccessTemplate>

Your password has been sent to your email address

<asp:Label ID="EmailLabel" runat="server" />!

</SuccessTemplate>

</asp:PasswordRecovery>

如果你使用的控件具有正确的ID值,并且为按钮指定了正确的CommandName值,则你不必写任何代码,控件就可以工作。正如在前面的示例中你不需要使用模板一样。在前面的代码中,这个特殊的控件是粗体显示的。其中一些控件是模板必需的,而其它的则是可选的。表21-12列出了PasswordRecovery模板的控件。

Template  

 ID  

 Control Type  

必须?

 Comments  

UserNameTemplate  

UserName  

System.Web.UI.Web-Controls.TextBox  

Yes  

 

UserNameTemplate  

SubmitButton  

All controls that support event bubbling  

No  

CommandName must be set to Submit.

UserNameTemplate  

FailureText  

System.Web.UI.Web-Controls.Literal  

No  

 

QuestionTemplate  

UserName  

System.Web.UI.Web-Controls.Literal  

No  

 

QuestionTemplate  

Question  

System.Web.UI.Web-Controls.Literal  

No  

 

QuestionTemplate  

Answer  

System.Web.UI.Web-Controls.TextBox  

Yes  

 

QuestionTemplate  

SubmitButton  

All controls that support event bubbling  

No  

CommandName must be set to Submit

QuestionTemplate  

FailureText  

System.Web.UI.Web-Controls.Literal  

No  

 

21-12 PasswordRecover模板所需控件

提交按钮可以是任何支持事件,并且具有CommandName属性的控件。通常情况下,你可以使用Button, ImageButton或者LinkButton来替代提交按钮。CommandName属性必须设置为Submit。否则,控件就无法识别命令。SuccessTemplate不要求任何具有特殊ID的控件。因此,你可以添加任何控件到想要的位置,它仅仅用来显示确认信息。在前面的示例中,包含了一个Literal控件来显示密码发送到的e-mail地址。可以通过SendingEmail事件过程来设置Literal控件。你还可以使用FindControl方法在正确的模板中查找控件(密码控件的子控件)。如下所示:

protected void PasswordTemplateCtrl_SendingMail(object sender,

MailMessageEventArgs e)

{

(Label)PasswordTemplateCtrl.SuccessTemplateContainer.FindControl(

"EmailLabel");

lbl.Text = e.Message.To[0].Address;

}

由于控件包含了不止一个模板,你不能直接在PasswordRecovery控件实例上调用FindControl方法。你必须选择正确的模板容器(UserNameTemplateContainer, QuestionTemplateContainer或者SuccessTemplateContainer),然后你就可以正常使用控件。在前面的示例中,你只需要设置标签(label)的文本为第一个e-mail接收者就可以了。当然,对于密码恢复,只需要一个邮件接收者。

修改密码控件(The ChangePassword Control

可以将这个控件作为标准控件,以便用户改变他们的密码。这个控件只需要用户提供用户名和旧密码。然后要求用户输入新密码和确认新密码。如果用户已经登录,则控件自动隐藏用户名文本域,并且使用已认证的用户名。可以在安全页面上使用控件,如下所示:

<asp:ChangePassword ID="ChangePwdCtrl" runat="server"

BorderStyle="groove" BackColor="aliceblue">

<MailDefinition From="pwd@apress.com"

Subject="Changes in your profile"

Priority="high" />

<TitleTextStyle Font-Bold="true" Font-Underline="true"

Font-Names="Verdana" ForeColor="blue" />

</asp:ChangePassword>

这个控件包含了与PasswordRecover控件相同的MailDefinition属性。这是因为当密码修改成功后,控件自动发送一封e-mail给用户。和其它控件一样,这个控件可以通过属性、风格和基于模板的方法进行自定义。但此次自定义控件时,需要两个模板:

l         ChangePasswordTemplate提供显示输入旧用户名、密码和新密码及新密码确认域。

l         SuccessTemplate显示修改成功的信息。

ChangePasswordTemplate要求你添加一些特殊的控件。这些控件具有特殊的IDCommandName属性值。你可以在下面的代码中找到粗体显示的这些控件IDCommandName值:

<asp:ChangePassword ID="ChangePwdCtrl" runat="server">

<ChangePasswordTemplate>

Old Password:&nbsp;

<asp:TextBox ID="CurrentPassword" runat="server"

TextMode="Password" /><br />

New Password:&nbsp;

<asp:TextBox ID="NewPassword" runat="server"

TextMode="Password" /><br />

Confirmation:&nbsp;

<asp:TextBox ID="ConfirmNewPassword" runat="server"

TextMode="Password" /><br />

<asp:Button ID="ChangePasswordPushButton" CommandName="ChangePassword"

runat="server" Text="Change Password" />

<asp:Button ID="CancelPushButton" CommandName="Cancel"

runat="server" Text="Cancel" /><br />

<asp:Literal ID="FailureText" runat="server"

EnableViewState="False" />

</ChangePasswordTemplate>

<SuccessTemplate>

Your password has been changed!</td>

<asp:Button ID="ContinuePushButton" CommandName="Continue"

runat="server" Text="Continue" />

</SuccessTemplate>

</asp:ChangePassword>

ChangePasswordTemplate最基本的要求是文本框控件。而其它的控件是可选的。如果你为按钮设置了正确的IDCommandName属性,则你不需要编写一行代码就可以工作。

创建用户向导控件(The CreateUserWizard Control

创建用户向导控件是登录控件中最强大的控件。它使你在几分钟之内创建一个注册页面。这个向导控件默认有两个步骤:其中一步是询问用户一般信息,另一步是显示确认信息。当然,由于创建用户向导控件是继承于基础的Wizard控件的,你可以添加任何步骤到向导中。但当你仅仅添加CreateUserWizard控件到网页中时(如下),其结果就非常明趣,图21-16显示了效果。

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

BorderStyle="ridge" BackColor="aquamarine">

<TitleTextStyle Font-Bold="true" Font-Names="Verdana" />

<WizardSteps>

<asp:CreateUserWizardStep runat="server">

</asp:CreateUserWizardStep>

<asp:CompleteWizardStep runat="server">

</asp:CompleteWizardStep>

</WizardSteps>

</asp:CreateUserWizard>

21-16 创建用户向导

控件的默认外观是通过属性和风格进行自定义的。控件提供了许多风格,但其最基本的风格与前面所述的其它控件一样。事实上,这个控件包含了风格的大多数完整列表,如前面的控件中展示的大多数域一样。使用CreateUserWizard控件时,你不需要进行任何特别的配置。它自动使用配置好的成员资格提供者来创建用户。有两个步骤:默认的CreateUserWizardStep创建控件来搜集必要的信息,而CompleteWizardStep显示确认信息。这两步都可以通过风格、属性或者模板来进行自定义。尽管你可以自定义这两个步骤,但你不能够移除它们。如果使用模板,你还必须创建一些必要的控件,如下所示:

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

BorderStyle="ridge" BackColor="aquamarine">

<TitleTextStyle Font-Bold="True" Font-Names="Verdana" />

<WizardSteps>

<asp:CreateUserWizardStep runat="server">

<ContentTemplate>

<div align="right">

<font face="Courier New">

User Name:

<asp:TextBox ID="UserName" runat="server" /><br />

Password:

<asp:TextBox ID="Password" runat="server"

TextMode="Password" /><br />

Conform Password:

<asp:TextBox ID="ConfirmPassword" runat="server"

TextMode="Password" /><br />

Email:

<asp:TextBox ID="Email" runat="server" /><br />

Security Question:

<asp:TextBox ID="Question" runat="server" /><br />

Security Answer:

<asp:TextBox ID="Answer" runat="server" /><br />

<asp:Literal ID="ErrorMessage" runat="server"

EnableViewState="False" />

</font>

</div>

</ContentTemplate>

</asp:CreateUserWizardStep>

<asp:CompleteWizardStep runat="server">

<ContentTemplate>

Your account has been successfully created.</td>

<asp:Button ID="ContinueButton" CommandName="Continue"

runat="server" Text="Continue" />

</ContentTemplate>

</asp:CompleteWizardStep>

</WizardSteps>

</asp:CreateUserWizard>

由于控件是一个向导控件,第一步不需要任何按钮,因为向导控件自动提供了一个下一步(Next)按钮。根据成员资格提供者的配置,其中一些控件是必须的,而另一些不是,如表21-13中所列。

ID  

 Type  

必须?  

 Comments  

UserName  

 System.Web.UI.WebControls.TextBox

 Yes 

Always required

Password  

 System.Web.UI.WebControls.TextBox

 Yes  

Always required  

 ConfirmPassword  

 System.Web.UI.WebControls.TextBox

 Yes  

Always required  

 Email  

 System.Web.UI.WebControls.TextBox

No  

Required only if the RequireEmail property of the control is set to true  

 Question  

 System.Web.UI.WebControls.TextBox

No

Required only if the underlying Membership provider requires a password question  

 Answer  

 System.Web.UI.WebControls.TextBox

No  

Required only if the underlying Membership provider requires a password question  

 ContinueButton  

Any control that supports bubbling

No  

Not required at all, but if present you need to set the CommandName to Continue  

21-13 必要控件和可选控件

只要创建额外的向导步骤,你就需要捕获事件并对其进行处理。例如,你想添加向导步骤来收集额外的信息,你就必须在某处存储这些信息,然后执行SQL语句来应用到数据库。表21-14列出了CreateUserWizard控件特定的事件。它同时继承了所有来自于Wizard控件的事件。

Event  

 Description  

 ContinueButtonClick  

Raised when the user clicks the Continue button in the last wizard step.  

CreatingUser

Raised by the wizard before it creates the new user through the Membership API.  

CreatedUser

After the control has been created successfully, the control raises this event.

 CreateUserError

If the creation of the user was not successful, this event is raised.  

SendingEmail

The control can send an e-mail to the created user if a mail server is configured. This event is raised by the control before the e-mail is sent so that you can modify the contents of the mail message.  

SendMailError

If the control was unable to send the messagefor example, because the mail server was unavailableit raises this event.  

21-14 CreateUserWizard事件

现在,你可以添加一个向导步骤来询问额外的用户信息,如姓和名,并且自动将这些信息存储到自定义的数据库表中。一个正确的观点是将其存储在profile中。但通过向导运行时,用户并没有获得认证,因此,你不能存储信息到profile中,因为这只对经过认证的用户有效。因此,你要么将其存入一个自定义数据库表中,要么使用户在完成注册过程之后能够编辑profile

不仅如此,在CreateUserWizardStep顺利完成之后立即唤起CreatedUser事件。因此,如果你想在这个事件中保存附加的数据,则必须在这一步骤之前收集这些信息。为此,应当放置其它向导步骤在<asp:CreateUserWizardStep>标签之前。在其它情况下,你必须在其它事件中保存信息(例如,FinishButtonClick事件)。但是由于你不能肯定用户确实运行了整个向导并且点击了完成按钮,在CreateUserWizardStep之前收集所有需要的信息就显得尤为必要,然后在CreateUser事件中保存这些附加信息。

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

BorderStyle="ridge" BackColor="aquamarine"

OnCreatedUser="RegisterUser_CreatedUser"

<TitleTextStyle Font-Bold="True" Font-Names="Verdana" />

<WizardSteps>

<asp:WizardStep ID="NameStep" AllowReturn="true">

Firstname:

<asp:TextBox ID="FirstnameText" runat="server" /><br />

Lastname:

<asp:TextBox ID="LastnameText" runat="server" /><br />

Age:

<asp:TextBox ID="AgeText" runat="server" />

</asp:WizardStep>

<asp:CreateUserWizardStep runat="server">

...

</asp:CreateUserWizardStep>

<asp:CompleteWizardStep runat="server">

...

</asp:CompleteWizardStep>

</WizardSteps>

</asp:CreateUserWizard>

在前面的向导步骤中,你可以在控件唤起CreatedUser事件时存储额外的信息到数据存储中,如下所示:

private short _Age;

private string _Firstname, _Lastname;

protected void Page_Load(object sender, EventArgs e)

{

if (!this.IsPostBack)

{

_Age = -1;

_Firstname = _Lastname = string.Empty;

}

}

protected void RegisterUser_CreatedUser(object sender, EventArgs e)

{

// Find the correct wizard step

WizardStepBase step = null;

for (int i = 0; i < RegisterUser.WizardSteps.Count; i++)

{

if (RegisterUser.WizardSteps[i].ID == "NameStep")

{

step = RegisterUser.WizardSteps[i];

break;

}

}

if (step != null)

{

_Firstname = ((TextBox)step.FindControl("FirstnameText")).Text;

_Lastname = ((TextBox)step.FindControl("LastnameText")).Text;

_Age = short.Parse(((TextBox)step.FindControl("AgeTExt")).Text);

// Store the information

Debug.WriteLine(string.Format("{0} {1} {2}", _Firstname, _Lastname, _Age));

}

}

在创建CreatedUser事件中,程序根据在NameStep上设置的ID来查找向导步骤。于是它多次使用FindControl方法来获得控件的内容。只要你获得了控件,你就可以访问它们的属性,并且执行任何动作。

总的来说,CreateUserWizard控件是功能强大的控件,它基于成员资格 API,并且可自定义,就ASP.NET2.0所带的其它登录控件一样。使用模板控件,你使其变得灵活,并且能够控制登录控件的外面,控件还要执行许多工作,特别是与Membership交互。对于你来说,如果你想自己执行动作,可以捕获控件的几个事件。

使用成员资格类(Using the Membership Class

在本章接下来的节中,你将学习如何使用底层的Membership编程接口,这些编程接口由所有控件和整个Membership API基础结构所使用。你将会看到编程接口很简单。它包含Membership类和MembershipUser类。类MembershipUser为单个用户封装了属性。Membership类的方法执行基础的操作:

l         创建新用户

l         删除用户

l         更新用户

l         获取用户列表

l         获取单个用户的详情

Membership类的一些方法接收MembershipUser的实例为参数,然后返回一个MembershipUser实例,或者返回MembershipUser实例集。例如,通过Membership.GetUser方法来获取一个用户,并设置在这个实例上的属性,然后将其传递给Membership类的UpdateUser方法,你就能够简单地更新用户属性。Membership类和MembershipUser类都在真实的提供者和应用之间提供了一个必要的抽象层。使用Membership类所做的一切都依赖于提供者。这意味着如果Membership提供者的实现是完整的,并且支持所有从MembershipProvider基类衍生的所有特征,则你与底层的Membership提供者交互,并不影响你的应用。

所有用于Membership API的类都在System.Web.Security名字空间中定义。Membership类具有许多静态的方法和属性。你现在可以简略地使用Membership类和相关类(如MembershipUser)来执行不同类型的任务。

从存储中获取用户(Retriving Users from the Store

你的第一个任务是通过Membership类从Membership的存储中获取单个用户和用户列表。为此,你创建一个简单的页面,并将用户绑定到GridView控件,如下所示:

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">

<title>Untitled Page</title>

</head>

<body>

<form id="form1" runat="server">

<div>

<asp:GridView ID="UsersGridView" runat="server"

DataKeyNames="UserName"

AutoGenerateColumns="False">

<Columns>

<asp:BoundField DataField="UserName" HeaderText="Username" />

<asp:BoundField DataField="Email" HeaderText="Email" />

<asp:BoundField DataField="CreationDate"

HeaderText="Creation Date" />

</Columns>

</asp:GridView>

</div>

</form>

</body>

</html>

你已经看到,GridViewDataKeyName定义为UserName域。这使你能够直接通过gridSelectValue属性访问当前选择的用户的UserName值。大多数方法需要用户名来获取更多细节,这显然是有用的。在这个页面中,你可以添加下面的代码到Page_Load事件处理程序中来加载来自于Membership存储的用户,并且将它们绑定到grid

public partial class _Default : System.Web.UI.Page

{

MembershipUserCollection _MyUsers;

protected void Page_Load(object sender, EventArgs e)

{

_MyUsers = Membership.GetAllUsers();

UsersGridView.DataSource = _MyUsers;

if (!this.IsPostBack)

{

UsersGridView.DataBind();

}

}

}

21-17显示了应用的结果页面

21-17 自定的用户管理应用执行页面

你看到,Membership类包含了GetAllUsers方法,返回MembershipUserCollection类的实例。你可以其它集(collection)一样使用这个集。每一个实体都包含了单个用户的所有属性。因此,如果你想显示选择的用户的细节,你只需要在前面创建的页面中添加一些控件来显示选择用户的内容,如下:

Selected User:<br />

<table border="1" bordercolor="blue">

<tr>

<td>User Name:</td>

<td><asp:Label ID="UsernameLabel" runat="server" /></td>

</tr>

<tr>

<td>Email:</td>

<td><asp:TextBox ID="EmailText" runat="server" /></td>

</tr>

<tr>

<td>Password Question:</td>

<td><asp:Label ID="PwdQuestionLabel" runat="server" /></td>

</tr>

<tr>

<td>Last Login Date:</td>

<td><asp:Label ID="LastLoginLabel" runat="server" /></td>

</tr>

<tr>

<td>Comment:</td>

<td><asp:TextBox ID="CommentTextBox" runat="server"

TextMode="multiline" /></td>

</tr>

<tr>

<td>

<asp:CheckBox ID="IsApprovedCheck" runat="server" Text="Approved" />

</td>

<td>

<asp:CheckBox ID="IsLockedOutCheck" runat="Server" Text="Locked Out" />

</td>

</tr>

</table>

你可以捕获前面添加的GridView控件的SelectedIndexChanged事件,使用正确的值来填充这些字段,如下:

protected void UsersGridView_SelectedIndexChanged(object sender, EventArgs e)

{

if (UsersGridView.SelectedIndex >= 0)

{

MembershipUser Current = _MyUsers[(string)UsersGridView.SelectedValue];

UsernameLabel.Text = Current.UserName;

PwdQuestionLabel.Text = Current.PasswordQuestion;

LastLoginLabel.Text = Current.LastLoginDate.ToShortDateString();

EmailText.Text = Current.Email;

CommentTextBox.Text = Current.Comment;

IsApprovedCheck.Checked = Current.IsApproved;

IsLockedOutCheck.Checked = Current.IsLockedOut;

}

}

可以看到,MembershipCollection对象要求用户名来直接访问用户。Membership类的方法,如GetUser也要求用户名。因此,在前面的GridView中,你应该使用UserName域作为DataKeyNames的属性。使用MembershipUser实例,你可以正常地访问用户的属性。

更新存储的用户(Updating Users in the Store

更新在Membership存储里的用户与获取用户一样简单。只要你具有MembershipUser实例,你就可以轻易地更新诸如e-mail、评论之类的属性。你只需要调用Membership类的UpdateUser方法就可以了。你可以通过添加按钮到网页中和插入下列代码到按钮的Click事件处理器中来扩展程序:

protected void ActionUpdateUser_Click(object sender, EventArgs e)

{

if (UsersGridView.SelectedIndex >= 0)

{

MembershipUser Current = _MyUsers[(string)UsersGridView.SelectedValue];

Current.Email = EmailText.Text;

Current.Comment = CommentText.Text;

Current.IsApproved = IsApprovedCheck.Checked;

Membership.UpdateUser(Current);

// Refresh the grids view

UsersGridView.DataBind();

}

}

UpdateUser方法接收了修改后的MembershipUser。在这个方法调用前,你必须更新实例上的属性。这里有一个异常,即IsLockedOut属生不能设置。这个属性是在用户尝登录的失败次数过多后自动进行设置。如果你想解锁一个用户,你需要独立地调用MembershipUserUnlockUser方法。这对于密码也是相似的。你不能通过设置MembershipUser上的某些属性来直接改变密码。为此,它支持GetPassword方法和ChangePassword方法来要求用户提供旧的和新的密码。通过GetPassword方法来获取密码是可能的,但这要求存储的密码不是哈希方式才有效。因此,GetPassword仅仅在配置Membership提供者来存储明文的密码或加密的密码到底层的Membership存储中才有效。

创建和删除用户(Creating and Deleting Users

创建用户正如使用其它的Membership API一样简单。你通过调用Membership类的CreateUser方法就能创建用户。因此,如果你想创建一些添加用户的功能到网站中,你可以添加新的网页,包含必要的文本框来输入信息,然后添加按钮,最后使用下面的代码捕获这个按钮的Click事件:

protected void ActionAddUser_Click(object sender, EventArgs e)

{

try

{

MembershipCreateStatus Status;

Membership.CreateUser(UserNameText.Text,

PasswordText.Text,

UserEmailText.Text,

PwdQuestionText.Text,

PwdAnswerText.Text, true,

out Status);

StatusLabel.Text = "User created successfully!";

}

catch(Exception ex)

{

Debug.WriteLine("Exception: " + ex.Message);

StatusLabel.Text = "Unable to create user!";

}

}

CreateUser具有几个负载。最容易的负载是仅仅接收用户名和密码,而更复杂的版本是同时要求密码问题和答案。MembershipCreateStatus对象返回额外的用户的创建状态信息,然后作为一个输出参数,因为方法已经返回了MembershipUser的新实例。依据配置的提供者,你调用CreateUser的更简单版本可能成功,也可能失败。例如,默认的Membership提供者要求你包含密码问题和答案,因此,如果你没有提供这部分内容,CreateUser就会抛出一个异常。

删除用户与创建用户一样简单。Membership类提供了Delete方法,要求你传递用户名作为参数。它删除用户的同时,如果有必要,也删除底层Membership存储的其相关信息。

验证用户(Validating Users

最后,Membership类提供了验证Membership用户的方法。如果用户在输入了用户名和密码,你可以使用这个方法来编程验证用户输入的信息,如下所示:

if (Membership.ValidateUser(UserNameText.Text, PasswordText.Text))

{

FormsAuthentication.RedirectFromLoginPage(UserNameText.Text, false);

}

else

{

// Invalid username or password

}

总结

在本章中,你了解了Membership API,它是ASP.NET2.0里的新内容。Membership提供了功能完备的基础结构来管理应用中的用户。你可以使用WAT(一个新的安全控件),或Membership API来访问这些基础服务。Membership本身也是基于提供者的。换句话说,你可以通过改变底层的提供者来交换底层的存储内容,而不需要涉及应用。本章中,仅仅使用了SQL Server作为提供者。在第26章,你将学习创建和配置自定义Membership提供者的必要细节。在下一章,你可以看到验证用户的另一个方法:Windows认证。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值