自定义MembershipProvider配合Asp.net 2.0 Login控件

转向Asp.net 2.0,如果单单看Asp.net 2.0的例子和SDK,相信你一定对系统自带的Login控件有比较深刻的印象。Asp.Net 2.0的Login控件不用你写一行检测用户输入是否合法的程序代码及相关查询数据库的SQL脚本,只需把相应的控件拖到Web表单中,即可完成用户登陆,创建用户,用户角色管理,修改密码用户详细情况,取回密码等功能模块。

  Login控件看上去近乎完美,而我们现在手头正好来了一个项目要求采用Asp.net 2.0开发,而该项目也要求有登陆,用户管理,权限管理,修改密码等功能,相信绝大多数人都会考虑使用Login控件来快速搞定这些要求。于是乎,大家拿出以前的教学例子,试着分析较深一层的代码,看看该在哪里对Login控件修改一番,让它满足手头项目已设计好的数据库表结构。结果发现,除了aspx文件里面可以对Login控件的外观,提示文字可以自定义外,cs文件中愣是找不到一行代码,然后继续翻MSDN和Google,终于知道,要在自己的项目中直接使用系统自带的Login控件,需要做2项修改工作:

  1、根据你选用的数据库,修改Web.config中相应的connectionStrings。系统默认的数据库是SQL Server 2005 Express,如果我们的数据库是Access/SQL2000/2005/Oracle,当然要大改一番了。

  2、改完Web.config还不够,我们还得执行C:\WINNT\Microsoft.NET\Framework\v2.0.507\aspnet_sqlreg.exe注册你的sql server,该程序的作用是在你的数据库中建立Login控件需要的所有资源(大约有上十个表,三十多条存储过程,上十个视图等等),如果你使用的是access/orcale,或者是其他格式的数据库,那你自己去Google相应的SQL脚本吧。

  OK,想到Login控件帮你节省的工作量,相信不少人都会咬着牙完成上面的2项工作。完成上面2项工作后,大家接着读项目需求,发现有用户组管理和权限管理,幸好开发资料上提到Login控件集成的Role角色管理模块正好与之对应,不过以后我们创建一个用户后,还要再进入一个页面给用户选择所属用户组,当然,采用Role的话,我们可以设定一个用户同时属于多个用户组,貌似功能很强大哟。继续读项目需求,发现这些项目的用户对象还有不少Login控件中没有的属性要保存,回头再去翻MSDN,发现Profile可以帮我们解决这个问题。

  嗯,除开使用Login控件,运行aspnet_sqlreg.exe帮我们建立的上十个表,三十多条存储过程,上十个视图等,我们再不用建表保存用户的任何信息了,以后我们只用在Web.config文件和相应的cs代码中加上Role和Profile的处理代码,即可完成该项目的登陆,用户管理,密码修改功能。算算投入查MSDN,Google及修改Web.config文件和相应的cs代码的时间,相信原来自己写过自定义Login控件的朋友已经准备发誓再也不碰Asp.net 2.0自带的Login控件了。

  其实,我们完全有更简洁通用的办法来重用Asp.net 2.0自带的Login控件,即只用它最基本的登陆及修改密码功能,这2个基本功能照旧从工具箱拖个控件出来往Web表单上一扔即可,一行代码都不多加。其他的用户/用户组管理,权限管理不用扯上Login控件,数据库想用什么产品就用什么产品,mysql/db2/infomax来者不拒;表结构想怎么设计就怎么设计,E-R图,UML图直接照搬就成;用户/用户组管理和权限管理模块想怎么规定就怎么规定,自关联,无限分级都行。总之一句话:让Login控件附带的上十个表,三十多条存储过程,上十个视图见鬼去吧。

  下面细说实现方法,Asp.net 2.0的Login控件用到了3个类来从数据库中获取相应的数据,分别是MemberShipprovider,RoleProvider及ProfileProvider,系统自带的这3个类的方法的代码被隐藏起来了,尽管没公开,但实际上就是使用我上面一直念叨的上十个表,三十多条存储过程,上十个视图。不管你用什么数据库,只要想使用Login控件的所有功能,必须保证该数据库中有与之对应的十来个表,三十多条存储过程,十来个视图。

  当然,MS的架构设计师也不是某些人想象中的那么无能,上面的这三个类其实都是抽象类,系统的Login控件实际调用的是从这3个类派生出来的针对SQL Server2000/2005的数据操作类,灵活的架构设计正是在这里体现出来。既然MemberShipProvider,RoleProvider及ProfileProvider三大头是抽象类,那么我们完全可以自定义一个只针对用户表的username及password2个列操作的MemberShipprovider派生类出来,重写登陆验证,修改密码以及其调用的方法,然后在Web.config中把membership的提供者指定为我们自己写的MemberShipprovider派生类,这样我们就可以和原来一样,把Login控件的登陆和修改密码2个子控件往Web表单上一拖了事。

  下面开始贴代码,懒的深究的朋友们可以直接把我给出的cs代码贴回去,建个cs文件放到App_Code目录下,然后按照后面的Web.config修改相应的connectionStrings和membership即可,以后任何项目要利用Asp.net 2.0的Login控件的登陆和修改密码都是这样照葫芦画瓢,够傻瓜吧。

using System;

using System.Data;

using System.Configuration;

using System.Data.SqlClient;

using System.Collections.Generic;

using System.Text.RegularExpressions;

using System.Data.SqlTypes;

using System.Web;

using System.Web.Security;

/**//// <summary>

/// MyMemberShip 的摘要说明

/// </summary>

public class MyMemberShip : MembershipProvider

...{

private bool _requiresQuestionAndAnswer;

private int _minRequiredPasswordLength;

public MyMemberShip()

...{

//

// TODO: 在此处添加构造函数逻辑

//

}

public override string ApplicationName

...{

get

...{

throw new Exception("The method or operation is not implemented.");

}

set

...{

throw new Exception("The method or operation is not implemented.");

}

}

public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)

...{

if (config["requiresQuestionAndAnswer"].ToLower() == "true")

_requiresQuestionAndAnswer = true;

else

_requiresQuestionAndAnswer = false;

int.TryParse(config["minPasswordLength"], out _minRequiredPasswordLength);

base.Initialize(name, config);

}

public override bool ChangePassword(string username, string oldPassword, string newPassword)

...{

using (SqlConnection connection = new SqlConnection(DBBase.DBConnectionString))

...{

SqlCommand command = new SqlCommand();

command.CommandText = "update [User] set user_pwd=@newpwd where user_name=@name and user_pwd=@oldpwd";

command.Parameters.AddWithValue("@name", username);

command.Parameters.AddWithValue("@oldpwd", CryptUtil.GetStringHashValue1(StringUtil.SqlEscape(oldPassword)));

command.Parameters.AddWithValue("@newpwd", CryptUtil.GetStringHashValue1(StringUtil.SqlEscape(newPassword)));

command.Connection = connection;

connection.Open();

return (int)command.ExecuteNonQuery() > 0 ? true : false;

}

//throw new Exception("The method or operation is not implemented.");

}

public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)

...{

throw new Exception("The method or operation is not implemented.");

}

public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)

...{

throw new Exception("The method or operation is not implemented.");

}

public override bool DeleteUser(string username, bool deleteAllRelatedData)

...{

throw new Exception("The method or operation is not implemented.");

}

public override bool EnablePasswordReset

...{

get ...{ throw new Exception("The method or operation is not implemented."); }

}

public override bool EnablePasswordRetrieval

...{

get ...{ throw new Exception("The method or operation is not implemented."); }

}

public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)

...{

throw new Exception("The method or operation is not implemented.");

}

public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)

...{

throw new Exception("The method or operation is not implemented.");

}

public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)

...{

throw new Exception("The method or operation is not implemented.");

}

public override int GetNumberOfUsersOnline()

...{

throw new Exception("The method or operation is not implemented.");

}

public override string GetPassword(string username, string answer)

...{

throw new Exception("The method or operation is not implemented.");

}

public override MembershipUser GetUser(string username, bool userIsOnline)

...{

DateTime myDate = DateTime.Today;

MembershipUser user = new MembershipUser(

Name, // Provider name

username, // Username

null, // providerUserKey

bobcy@21cn.com, // Email

String.Empty, // passwordQuestion

String.Empty, // Comment

true, // isApproved

false, // isLockedOut

DateTime.Now, // creationDate

DateTime.Now, // lastLoginDate

DateTime.Now, // lastActivityDate

DateTime.Now, // lastPasswordChangedDate

new DateTime(1980, 1, 1) // lastLockoutDate

);

return user;

}

public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)

...{

throw new Exception("The method or operation is not implemented.");

}

public override string GetUserNameByEmail(string email)

...{

throw new Exception("The method or operation is not implemented.");

}

public override int MaxInvalidPasswordAttempts

...{

get ...{ throw new Exception("The method or operation is not implemented."); }

}

public override int MinRequiredNonAlphanumericCharacters

...{

get ...{ return 0; }

}

public override int MinRequiredPasswordLength

...{

get ...{ return _minRequiredPasswordLength; }

}

public override int PasswordAttemptWindow

...{

get ...{ throw new Exception("The method or operation is not implemented."); }

}

public override MembershipPasswordFormat PasswordFormat

...{

get ...{ throw new Exception("The method or operation is not implemented."); }

}

public override string PasswordStrengthRegularExpression

...{

get ...{ throw new Exception("The method or operation is not implemented."); }

}

public override bool RequiresQuestionAndAnswer

...{

get ...{ return _requiresQuestionAndAnswer; }

}

public override bool RequiresUniqueEmail

...{

get ...{ throw new Exception("The method or operation is not implemented."); }

}

public override string ResetPassword(string username, string answer)

...{

throw new Exception("The method or operation is not implemented.");

}

public override bool UnlockUser(string userName)

...{

throw new Exception("The method or operation is not implemented.");

}

public override void UpdateUser(MembershipUser user)

...{

throw new Exception("The method or operation is not implemented.");

}

public override bool ValidateUser(string username, string password)

...{

using (SqlConnection connection = new SqlConnection(DBBase.DBConnectionString))

...{

SqlCommand command = new SqlCommand();

command.CommandText = "select count(0) from [User] where user_name=@name and user_pwd=@pwd";

command.Parameters.AddWithValue("@name", username);

command.Parameters.AddWithValue("@pwd", password);

command.Connection = connection;

connection.Open();

return ((int)command.ExecuteScalar()) > 0 ? true : false;

}

}

}

Web.Config的membership节这样写,connectionStrings和数据库有关,不同的数据库差别很大,大家自己Google,我就不列出来了。

<membership defaultProvider="MyMemberShip">

<providers>

<clear/>

<add name="MyMemberShip" type="MyMemberShip" requiresQuestionAndAnswer="false" connectionString="AdminSqlServer" minRequiredNonalphanumericCharacters="0" />

</providers>

</membership>

  如果我们想在用户验证登陆成功后做一些额外的处理,可以给登陆控件的登陆按钮添加一个事件,,相应的代码如下:

protected void Login1_Authenticate(object sender, AuthenticateEventArgs e)

...{

if (Membership.ValidateUser(Login1.UserName, Login1.Password))

...{

//这里添加你的额外处理代码,如Session,login日至等等

e.Authenticated = true;

}

}

  如果还想重用Login控件的其他功能,但不想按照系统附带的数据库设计的话,还可以按照你的项目的数据库设计重写RoleProvider和ProfileProvider2个抽象类,对这方面资料感兴趣的朋友可以看看下面这篇文章(重写了几个RoleProvider的方法),更多资料请自行查阅MSDN及Google:

http://www.lemongtree.com/Archives/2006/11/28/0000572.aspx

  不过我个人的经验来看,每个项目的用户管理和权限管理都不尽相同,有的甚至差别很大,重写RoleProvider和ProfileProvider的重用价值不大。