Asp.Net 2.0(codename Whidbey)通过Provider模式为用户验证、角色管理等方面提供了非常强大易用的框架模型。Whidbey中提供了一个Asp.Net configuration工具,通过它可以非常容易地配置用户信息数据库,管理角色等等,再与新加入的Security控件配合,几乎不用写什么代码就能够实现用户验证和角色管理功能。关于这些控件和配置工具的具体使用,可以参考这篇文章:使用更精简的代码保证 ASP.NET 应用程序的安全
但是在PDC Preview版本的Whidbey中,这个配置工具的功能还不是很完善。从我使用的情况来看,它目前还只能创建和连接自己的Demo用的Access数据库,不能连接SQL Server数据库进行扩展。因此,为了能够连接SQL Server,我们必须提供我们自己的Providers。这里以连接IBuySpy的Portal数据库为例来说明如何实现一个Membership Provider。
为了搞清楚如何实现我们自己的Membership Provider,有必要先看看Whidbey默认使用的Membership Provider是如何做的。在machine.config配置文件中,Whidbey使用类似下面这样的配置实现:
<membership defaultProvider="AspNetAccessProvider" userIsOnlineTimeWindow="15" >
<providers>
<add name="AspNetSqlProvider"
type="System.Web.Security.SqlMembershipProvider, System.Web, Version=1.2.3400.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="LocalSqlServer"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="false"
applicationName="/"
requiresUniqueEmail="false"
passwordFormat="Hashed"
description="Stores and retrieves membership data from the local Microsoft SQL Server database"
/>
<add name="AspNetAccessProvider"
type="System.Web.Security.AccessMembershipProvider, System.Web, Version=1.2.3400.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="AccessFileName"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="false"
applicationName="/"
requiresUniqueEmail="false"
passwordFormat="Hashed"
description="Stores and retrieves membership data from the local Microsoft Access database file"
/>
</providers>
</membership>
关于这段配置文件的更详细解说,可以参考《A First Look at ASP.NET v. 2.0》。
可以看出,Whidbey默认使用SqlMembershipProvider或者AccessMembershipProvider来进行用户验证和管理。这两个Provider实现了IProvider和IMembershipProvider接口,实际上这两个接口也是每个MembershipProvider所必需的,其中IProvider负责Provider的初始化,而IMembershipProvider则实现MembershipProvider的主要功能。它们的定义如下:
namespace System.Configuration.Provider
{
public interface IProvider
{
public string Name { get; }
public void Initialize(string name,
System.Collections.Specialized.NameValueCollection config);
}
}
namespace System.Web.Security
{
public interface IMembershipProvider
{
public bool ChangePassword(string name, string oldPwd,
string newPwd);
public bool ChangePasswordQuestionAndAnswer(string name,
string password,
string newPwdQuestion, string newPwdAnswer);
public System.Web.Security.MembershipUser CreateUser(
string username,
string password, string email,
out System.Web.Security.MembershipCreateStatus status);
public bool DeleteUser(string name);
public System.Web.Security.MembershipUserCollection GetAllUsers();
public int GetNumberOfUsersOnline();
public string GetPassword(string name, string answer);
public System.Web.Security.MembershipUser GetUser(string name,
bool userIsOnline);
public string GetUserNameByEmail(string email);
public string ResetPassword(string name, string answer);
public void UpdateUser(System.Web.Security.MembershipUser user);
public bool ValidateUser(string name, string password);
public string ApplicationName {get; set;}
public bool EnablePasswordReset { get;}
public bool EnablePasswordRetrieval { get;}
public bool RequiresQuestionAndAnswer { get;}
}
}
现在可以动手来实现我们自己的MembershipProvider了:
public class MyMembershipProvider : IProvider, IMembershipProvider
{
……
}
验证功能是必需的:
public bool ValidateUser (string name, string password)
{
string connectStr = ConfigurationSettings.ConnectionStrings["PortalData"];
SqlConnection myConnection = new SqlConnection (connectStr);
SqlCommand myCommand = new SqlCommand ("UserLogin", myConnection);
myCommand.CommandType = CommandType.StoredProcedure;
// Add Parameters to SPROC
SqlParameter parameterEmail = new SqlParameter ("@Email", SqlDbType.NVarChar, 100);
parameterEmail.Value = name;
myCommand.Parameters.Add (parameterEmail);
SqlParameter parameterPassword = new SqlParameter ("@Password", SqlDbType.NVarChar, 20);
parameterPassword.Value = password;
myCommand.Parameters.Add (parameterPassword);
SqlParameter parameterUserName = new SqlParameter ("@UserName", SqlDbType.NVarChar, 100);
parameterUserName.Direction = ParameterDirection.Output;
myCommand.Parameters.Add (parameterUserName);
// Open the database connection and execute the command
myConnection.Open ();
myCommand.ExecuteNonQuery ();
myConnection.Close ();
if ((parameterUserName.Value != null) && (parameterUserName.Value != System.DBNull.Value))
return true;
return false;
}
现在在web.config中可以这样配置connectionString了:
<connectionStrings>
<add name="BugDepotData" connectionString="Data Source=(local);Trusted_Connection=true;Database=Portal" />
</connectionStrings>
这样,我们自己的一个简单的MembershipProvider就基本上完成了。接下来需要配置web.config,让需要Provider服务的控件能够认识它:
<membership>
<providers>
<add name="MyMembershipProvider"
type="MyMembershipProvider"
appName="/" />
</providers>
</membership>
这段设置是参考machine.config而来的,其中type属性的值是这样的字符串:
type="ProviderType, Assembly, Version, Culture, PublicKeyToken"
由于我们的MyMembershipProvider放在/Code目录下,并不是在单独的Assembly中,因此只需要指出ProviderType就行了。
这样,一个具有验证功能的Provider就完成了,现在可以在页面上放一个新的Security控件,比如Login控件,并指定它的MembershipProperty为MyMembershipProvider(或者也可以设置membership的defaultProvider属性为MyMembershipProvider),打开Forms验证,试试是不是已经能够成功登陆了?J
还是开心的那个问题,我试了一下,问题是存在的,使用彭刚给出的工具,我得到了下面的报告(我的系统是Win2K3 + .NET FX 1.1,程序编译为debug版本):
First start
-------------------------------------------------------------------------------------
Totle Private Shareable Shared
Pages KBytes KBytes KBytes KBytes
Grand Total Working Set 3723 14892 4736 2940 7216
Module Working Set Contributions in pages
Total Private Shareable Shared Module
4 2 2 0 ddddd.exe
77 4 0 73 ntdll.dll
27 3 0 24 mscoree.dll
57 4 0 53 KERNEL32.dll
30 3 0 27 ADVAPI32.dll
40 2 0 38 RPCRT4.dll
34 2 0 32 SHLWAPI.dll
32 2 0 30 GDI32.dll
49 2 0 47 USER32.dll
34 4 0 30 msvcrt.dll
13 2 0 11 IMM32.DLL
8 2 0 6 LPK.DLL
34 4 0 30 USP10.dll
248 21 0 227 mscorwks.dll
47 6 1 40 MSVCR71.dll
39 3 0 36 fusion.dll
31 8 0 23 SHELL32.dll
17 2 0 15 comctl32.dll
251 2 0 249 mscorlib.dll
317 62 255 0 mscorlib.dll
37 3 0 34 diasymreader.dll
25 4 0 21 ole32.dll
14 2 0 12 mscorsn.dll
54 3 0 51 MSCTF.dll
6 2 0 4 Cjktl32.dll
277 1 141 135 system.windows.forms.dll
273 40 233 0 system.windows.forms.dll
171 1 0 170 system.dll
451 385 66 0 system.dll
63 2 0 61 MSCORJIT.DLL
76 1 0 75 system.drawing.dll
200 169 31 0 system.drawing.dll
9 1 0 8 apphelp.dll
41 36 0 5 msctfime.ime
26 3 1 22 gdiplus.dll
11 2 0 9 Msimtf.dll
4 1 0 3 VERSION.dll
----------------------------------------------------------------------------------------
Minimized
----------------------------------------------------------------------------------------
Totle Private Shareable Shared
Pages KBytes KBytes KBytes KBytes
Grand Total Working Set 169 676 416 64 196
Module Working Set Contributions in pages
Total Private Shareable Shared Module
6 1 0 5 ntdll.dll
3 1 0 2 KERNEL32.dll
11 2 0 9 USER32.dll
5 2 0 3 IMM32.DLL
18 4 0 14 mscorwks.dll
11 6 5 0 mscorlib.dll
7 2 0 5 MSCTF.dll
22 12 10 0 system.windows.forms.dll
8 8 0 0 msctfime.ime
----------------------------------------------------------------------------------------
Restored
----------------------------------------------------------------------------------------
Totle Private Shareable Shared
Pages KBytes KBytes KBytes KBytes
Grand Total Working Set 540 2160 968 300 892
Module Working Set Contributions in pages
Total Private Shareable Shared Module
25 2 0 23 ntdll.dll
11 1 0 10 KERNEL32.dll
10 3 0 7 ADVAPI32.dll
13 2 0 11 GDI32.dll
31 2 0 29 USER32.dll
1 0 0 1 msvcrt.dll
8 2 0 6 IMM32.DLL
5 2 0 3 LPK.DLL
7 0 0 7 USP10.dll
60 7 0 53 mscorwks.dll
1 0 0 1 MSVCR71.dll
49 23 26 0 mscorlib.dll
44 3 0 41 MSCTF.dll
3 1 0 2 Cjktl32.dll
77 29 48 0 system.windows.forms.dll
8 8 0 0 system.dll
14 14 0 0 system.drawing.dll
11 11 0 0 msctfime.ime
2 2 0 0 gdiplus.dll
2 1 0 1 Msimtf.dll
-----------------------------------------------------------------------------------------
可以看出,程序启动时加载了许多系统和应用程序模块(比如Cjktl32.dll就是金山词霸的dll),这可能是为了JIT的需要(MSCORJIT.DLL),在最小化时这些模块被卸载了,还原之后,就只剩下了程序运行所需的模块了。这看起来是内存变化的主要原因。
如果程序不被最小化,那些JIT用到的模块什么时候被卸载呢?