海纳百川 有容乃大(http://www.brtech.com.cn)
海纳百川,有容乃大(http://www.brtech.com.cn)
::
首页
:: ::
联系
::
订阅
::
管理
::
Community Server专题八:MemberRole之Membership深入篇
专题八的上篇大致讨论了MemberRole中的Membership实现,对于运用Membership进行web开发足够,但是对于想更深入了解Membership实现机理的朋友那是远远不够的,这个专题我们更深入一下了解Membership。
其实MemberRole是一个非常好的资源包,借住Reflector这个优秀的工具,你可以对其进行代码分析。它无论是在组建的构架、代码的设计、数据库表的建立、存储过程的使用等都是非常优秀的,你是程序员也好构架师也罢,其中可以学习的真的很多很多,我在整个分析的过程中也深深受益。
由于MemberRole中的Membership只实现了对SQL Server的操Provider类,即SqlMembershipProvider类。因此我们从SqlMembershipProvider开始分析。Provider模型在上篇已经做过介绍,SqlMembershipProvider类继承了MembershipProvider,并实现其所有的抽象方法。在分析之前先看两个类:MembershipUser与MembershipUserCollection。
MembershipUser,先看看代码:(代码中省略的具体实现,只有方法与属性名称)
public
class
MembershipUser
{
//
Methods
protected
MembershipUser();
public
MembershipUser(MembershipProvider provider,
string
name,
object
providerUserKey,
string
email,
string
passwordQuestion,
string
comment,
bool
isApproved,
bool
isLockedOut, DateTime creationDate, DateTime lastLoginDate, DateTime lastActivityDate, DateTime lastPasswordChangedDate, DateTime lastLockoutDate);
public
virtual
bool
ChangePassword(
string
oldPassword,
string
newPassword);
public
virtual
bool
ChangePasswordQuestionAndAnswer(
string
password,
string
newPasswordQuestion,
string
newPasswordAnswer);
public
virtual
string
GetPassword();
public
virtual
string
GetPassword(
string
passwordAnswer);
public
virtual
string
ResetPassword();
public
virtual
string
ResetPassword(
string
passwordAnswer);
public
override
string
ToString();
public
virtual
bool
UnlockUser();
internal
virtual
void
Update();
private
void
UpdateSelf();
//
Properties
public
virtual
string
Comment
{
get
;
set
; }
public
virtual
DateTime CreationDate
{
get
; }
public
virtual
string
Email
{
get
;
set
; }
public
virtual
bool
IsApproved
{
get
;
set
; }
public
virtual
bool
IsLockedOut
{
get
; }
public
bool
IsOnline
{
get
; }
public
virtual
DateTime LastActivityDate
{
get
;
set
; }
public
virtual
DateTime LastLockoutDate
{
get
; }
public
virtual
DateTime LastLoginDate
{
get
;
set
; }
public
virtual
DateTime LastPasswordChangedDate
{
get
; }
public
virtual
string
PasswordQuestion
{
get
; }
public
virtual
MembershipProvider Provider
{
get
; }
public
virtual
object
ProviderUserKey
{
get
; }
public
virtual
string
UserName
{
get
; }
//
Fields
private
string
_Comment;
private
DateTime _CreationDate;
private
string
_Email;
private
bool
_IsApproved;
private
bool
_IsLockedOut;
private
DateTime _LastActivityDate;
private
DateTime _LastLockoutDate;
private
DateTime _LastLoginDate;
private
DateTime _LastPasswordChangedDate;
private
string
_PasswordQuestion;
private
MembershipProvider _Provider;
private
object
_ProviderUserKey;
private
string
_UserName;
}
这是一个实体类,表示一个由Membership创建的User,该类中有这个User的一些基本状态,如该User的UserName、Email等,还有一些方法,如ChangePassword()、ResetPassword()等(如果你是初学者,还在为建立一个对象需要什么属性,包含什么方法发愁,那这就是你应该好好学的,这也是OOP最基本的要求)。
MembershipUserCollection,这是一个MembershipUser类的容器,用来存放MembershipUser列表,记得上次广州.net俱乐部聚会时,我的演讲中有朋友在提出CS是否使用自定义类来存储用户列表,其实在这里可以看到CS中使用的就是自定义的类而不是DataSet(我想在asp.net
2
.0正式发布后这也不会改变),这样做主要是因为考虑到性能与灵活性。
好了,回到SqlMembershipProvider类上来,我们具体分析一个有代表性质的方法:
public
override
MembershipUser CreateUser(
string
username,
string
password,
string
email,
string
passwordQuestion,
string
passwordAnswer,
bool
isApproved,
object
providerUserKey,
out
MembershipCreateStatus status)
{
string
text3;
MembershipUser user1;
if
(
!
SecUtility.ValidateParameter(
ref
password,
true
,
true
,
false
,
0x80
))
{
status
=
MembershipCreateStatus.InvalidPassword;
return
null
;
}
string
text1
=
base
.GenerateSalt();
string
text2
=
base
.EncodePassword(password, (
int
)
this
._PasswordFormat, text1);
if
(text2.Length
>
0x80
)
{
status
=
MembershipCreateStatus.InvalidPassword;
return
null
;
}
if
(passwordAnswer
!=
null
)
{
passwordAnswer
=
passwordAnswer.Trim();
}
if
((passwordAnswer
!=
null
)
&&
(passwordAnswer.Length
>
0
))
{
if
(passwordAnswer.Length
>
0x80
)
{
status
=
MembershipCreateStatus.InvalidAnswer;
return
null
;
}
text3
=
base
.EncodePassword(passwordAnswer.ToLower(CultureInfo.InvariantCulture), (
int
)
this
._PasswordFormat, text1);
}
else
{
text3
=
passwordAnswer;
}
if
(
!
SecUtility.ValidateParameter(
ref
text3,
this
.RequiresQuestionAndAnswer,
this
.RequiresQuestionAndAnswer,
false
,
0x80
))
{
status
=
MembershipCreateStatus.InvalidAnswer;
return
null
;
}
if
(
!
SecUtility.ValidateParameter(
ref
username,
true
,
true
,
true
,
0x100
))
{
status
=
MembershipCreateStatus.InvalidUserName;
return
null
;
}
if
(
!
SecUtility.ValidateParameter(
ref
email,
this
.RequiresUniqueEmail,
this
.RequiresUniqueEmail,
false
,
0x100
))
{
status
=
MembershipCreateStatus.InvalidEmail;
return
null
;
}
if
(
!
SecUtility.ValidateParameter(
ref
passwordQuestion,
this
.RequiresQuestionAndAnswer,
this
.RequiresQuestionAndAnswer,
false
,
0x100
))
{
status
=
MembershipCreateStatus.InvalidQuestion;
return
null
;
}
if
((providerUserKey
!=
null
)
&&
!
(providerUserKey
is
Guid))
{
status
=
MembershipCreateStatus.InvalidProviderUserKey;
return
null
;
}
if
(password.Length
<
this
.MinRequiredPasswordLength)
{
status
=
MembershipCreateStatus.InvalidPassword;
return
null
;
}
int
num1
=
0
;
for
(
int
num2
=
0
; num2
<
password.Length; num2
++
)
{
if
(
!
char
.IsLetterOrDigit(password, num2))
{
num1
++
;
}
}
if
(num1
<
this
.MinRequiredNonAlphanumericCharacters)
{
status
=
MembershipCreateStatus.InvalidPassword;
return
null
;
}
if
((
this
.PasswordStrengthRegularExpression.Length
>
0
)
&&
!
Regex.IsMatch(password,
this
.PasswordStrengthRegularExpression))
{
status
=
MembershipCreateStatus.InvalidPassword;
return
null
;
}
ValidatePasswordEventArgs args1
=
new
ValidatePasswordEventArgs(username, password,
true
);
this
.OnValidatingPassword(args1);
if
(args1.Cancel)
{
status
=
MembershipCreateStatus.InvalidPassword;
return
null
;
}
try
{
SqlConnectionHolder holder1
=
null
;
try
{
holder1
=
SqlConnectionHelper.GetConnection(
this
._sqlConnectionString,
true
);
this
.CheckSchemaVersion(holder1.Connection);
SqlCommand command1
=
new
SqlCommand(
"
dbo.aspnet_Membership_CreateUser
"
, holder1.Connection);
command1.CommandTimeout
=
this
.CommandTimeout;
command1.CommandType
=
CommandType.StoredProcedure;
command1.Parameters.Add(
this
.CreateInputParam(
"
@ApplicationName
"
, SqlDbType.NVarChar,
this
.ApplicationName));
command1.Parameters.Add(
this
.CreateInputParam(
"
@UserName
"
, SqlDbType.NVarChar, username));
command1.Parameters.Add(
this
.CreateInputParam(
"
@Password
"
, SqlDbType.NVarChar, text2));
command1.Parameters.Add(
this
.CreateInputParam(
"
@PasswordSalt
"
, SqlDbType.NVarChar, text1));
command1.Parameters.Add(
this
.CreateInputParam(
"
@Email
"
, SqlDbType.NVarChar, email));
command1.Parameters.Add(
this
.CreateInputParam(
"
@PasswordQuestion
"
, SqlDbType.NVarChar, passwordQuestion));
command1.Parameters.Add(
this
.CreateInputParam(
"
@PasswordAnswer
"
, SqlDbType.NVarChar, text3));
command1.Parameters.Add(
this
.CreateInputParam(
"
@IsApproved
"
, SqlDbType.Bit, isApproved));
command1.Parameters.Add(
this
.CreateInputParam(
"
@UniqueEmail
"
, SqlDbType.Int,
this
.RequiresUniqueEmail
?
1
:
0
));
command1.Parameters.Add(
this
.CreateInputParam(
"
@PasswordFormat
"
, SqlDbType.Int, (
int
)
this
.PasswordFormat));
command1.Parameters.Add(
this
.GetTimeZoneAdjustmentParam());
SqlParameter parameter1
=
this
.CreateInputParam(
"
@UserId
"
, SqlDbType.UniqueIdentifier, providerUserKey);
parameter1.Direction
=
ParameterDirection.InputOutput;
command1.Parameters.Add(parameter1);
parameter1
=
new
SqlParameter(
"
@ReturnValue
"
, SqlDbType.Int);
parameter1.Direction
=
ParameterDirection.ReturnValue;
command1.Parameters.Add(parameter1);
object
obj1
=
command1.ExecuteScalar();
DateTime time1
=
this
.RoundToSeconds(DateTime.Now);
if
((obj1
!=
null
)
&&
(obj1
is
DateTime))
{
time1
=
(DateTime) obj1;
}
int
num3
=
(parameter1.Value
!=
null
)
?
((
int
) parameter1.Value) :
-
1
;
if
((num3
<
0
)
||
(num3
>
11
))
{
num3
=
11
;
}
status
=
(MembershipCreateStatus) num3;
if
(num3
!=
0
)
{
return
null
;
}
providerUserKey
=
new
Guid(command1.Parameters[
"
@UserId
"
].Value.ToString());
return
new
MembershipUser(
this
, username, providerUserKey, email, passwordQuestion,
null
, isApproved,
false
, time1, time1, time1, time1,
new
DateTime(
0x6da
,
1
,
1
));
}
finally
{
if
(holder1
!=
null
)
{
holder1.Close();
holder1
=
null
;
}
}
}
catch
{
throw
;
}
return
user1;
}
该方法实现建立一个用户的过程,建立后返回一个被建立的MembershipUser对象,如果建立失败MembershipUser对象为null(其实我早期做过一些项目的时候喜欢在建立对象成功后返回一个ID)。可以看到在这个方法中有很多的if语句,它们是为了检验数据是否合法,这是必须的吗?其实不是,但对于构建一个强壮的底层代码这是必须的,不然一点点的错误都有可能导致系统的瘫痪。其实做项目与做开发有的时候不太一样,企业的有些项目开发很多时候只要能实现功能就可以了,而且开发过程也集中在一些现有的代码或者组建的基础上,个人的错误不会影响全局的运行,PM也不做过多要求。但如果是做产品,这个情况可能会有所改变,很多时候要求很严格,至少我是这样。在做完对输入参数的验证后,CreateUser建立与数据库的连接,这里是调用SqlConnectionHelper类下的GetConnection方法进行的,为了照顾初学者阅读,我这里讲一下为什么需要把对数据库连接与操作写在SqlConnectionHelper类下,而不是直接采用SqlConnection提供的方法,其实这是一个设计模式的问题,Membership的实现需要很多的方法与数据库进行交换数据库,如果每次方法都调用一次SqlConnection的方法建立数据库连接,一来会造成大量的代码冗余,而且一旦数据库连接语句一旦改变,你就要去修改很多个方法,如果你把这个过程都包装在一个类下面,连接数据库就有统一的入口,一来容易维护,二来不会有太多的代码冗余,再者如果需要查找错误也非常容易。这里Membership采用的是存储过程,我们可以看到使用的是dbo.aspnet_Membership_CreateUser存储过程,好了,打开你的数据库,找到这个存储过程:
CREATE PROCEDURE dbo.aspnet_Membership_CreateUser
@ApplicationName NVARCHAR(
256
),
@UserName NVARCHAR(
256
),
@Password NVARCHAR(
128
),
@PasswordSalt NVARCHAR(
128
),
@Email NVARCHAR(
256
),
@PasswordQuestion NVARCHAR(
256
),
@PasswordAnswer NVARCHAR(
128
),
@IsApproved BIT,
@TimeZoneAdjustment INT,
@CreateDate DATETIME
=
NULL,
@UniqueEmail INT
=
0
,
@PasswordFormat INT
=
0
,
@UserId UNIQUEIDENTIFIER OUTPUT
AS
BEGIN
DECLARE @ApplicationId UNIQUEIDENTIFIER
SELECT @ApplicationId
=
NULL
DECLARE @NewUserId UNIQUEIDENTIFIER
SELECT @NewUserId
=
NULL
DECLARE @IsLockedOut BIT
SET @IsLockedOut
=
0
DECLARE @LastLockoutDate DATETIME
SET @LastLockoutDate
=
CONVERT( DATETIME,
'
17540101
'
,
112
)
DECLARE @FailedPasswordAttemptCount INT
SET @FailedPasswordAttemptCount
=
0
DECLARE @FailedPasswordAttemptWindowStart DATETIME
SET @FailedPasswordAttemptWindowStart
=
CONVERT( DATETIME,
'
17540101
'
,
112
)
DECLARE @FailedPasswordAnswerAttemptCount INT
SET @FailedPasswordAnswerAttemptCount
=
0
DECLARE @FailedPasswordAnswerAttemptWindowStart DATETIME
SET @FailedPasswordAnswerAttemptWindowStart
=
CONVERT( DATETIME,
'
17540101
'
,
112
)
DECLARE @NewUserCreated BIT
DECLARE @ReturnValue INT
SET @ReturnValue
=
0
DECLARE @ErrorCode INT
SET @ErrorCode
=
0
DECLARE @TranStarted BIT
SET @TranStarted
=
0
IF( @@TRANCOUNT
=
0
)
BEGIN
BEGIN TRANSACTION
SET @TranStarted
=
1
END
ELSE
SET @TranStarted
=
0
EXEC dbo.aspnet_Applications_CreateApplication @ApplicationName, @ApplicationId OUTPUT
IF( @@ERROR
<>
0
)
BEGIN
SET @ErrorCode
=
-
1
GOTO Cleanup
END
IF (@CreateDate IS NULL)
EXEC dbo.aspnet_GetUtcDate @TimeZoneAdjustment, @CreateDate OUTPUT
ELSE
SELECT @CreateDate
=
DATEADD(n,
-
@TimeZoneAdjustment, @CreateDate)
--
switch
TO UTC time
SELECT @NewUserId
=
UserId FROM dbo.aspnet_Users WHERE LOWER(@UserName)
=
LoweredUserName AND @ApplicationId
=
ApplicationId
IF ( @NewUserId IS NULL )
BEGIN
SET @NewUserId
=
@UserId
EXEC @ReturnValue
=
dbo.aspnet_Users_CreateUser @ApplicationId, @UserName,
0
, @CreateDate, @NewUserId OUTPUT
SET @NewUserCreated
=
1
END
ELSE
BEGIN
SET @NewUserCreated
=
0
IF( @NewUserId
<>
@UserId AND @UserId IS NOT NULL )
BEGIN
SET @ErrorCode
=
6
GOTO Cleanup
END
END
IF( @@ERROR
<>
0
)
BEGIN
SET @ErrorCode
=
-
1
GOTO Cleanup
END
IF( @ReturnValue
=
-
1
)
BEGIN
SET @ErrorCode
=
10
GOTO Cleanup
END
IF ( EXISTS ( SELECT UserId
FROM dbo.aspnet_Membership
WHERE @NewUserId
=
UserId ) )
BEGIN
SET @ErrorCode
=
6
GOTO Cleanup
END
SET @UserId
=
@NewUserId
IF (@UniqueEmail
=
1
)
BEGIN
IF (EXISTS (SELECT
*
FROM dbo.aspnet_Membership m WITH ( UPDLOCK, HOLDLOCK )
WHERE ApplicationId
=
@ApplicationId AND LoweredEmail
=
LOWER(@Email)))
BEGIN
SET @ErrorCode
=
7
GOTO Cleanup
END
END
INSERT INTO dbo.aspnet_Membership
( ApplicationId,
UserId,
Password,
PasswordSalt,
Email,
LoweredEmail,
PasswordQuestion,
PasswordAnswer,
PasswordFormat,
IsApproved,
IsLockedOut,
CreateDate,
LastLoginDate,
LastPasswordChangedDate,
LastLockoutDate,
FailedPasswordAttemptCount,
FailedPasswordAttemptWindowStart,
FailedPasswordAnswerAttemptCount,
FailedPasswordAnswerAttemptWindowStart )
VALUES ( @ApplicationId,
@UserId,
@Password,
@PasswordSalt,
@Email,
LOWER(@Email),
@PasswordQuestion,
@PasswordAnswer,
@PasswordFormat,
@IsApproved,
@IsLockedOut,
@CreateDate,
@CreateDate,
@CreateDate,
@LastLockoutDate,
@FailedPasswordAttemptCount,
@FailedPasswordAttemptWindowStart,
@FailedPasswordAnswerAttemptCount,
@FailedPasswordAnswerAttemptWindowStart )
IF( @@ERROR
<>
0
)
BEGIN
SET @ErrorCode
=
-
1
GOTO Cleanup
END
IF (@NewUserCreated
=
0
)
BEGIN
UPDATE dbo.aspnet_Users
SET LastActivityDate
=
@CreateDate
WHERE @UserId
=
UserId
IF( @@ERROR
<>
0
)
BEGIN
SET @ErrorCode
=
-
1
GOTO Cleanup
END
END
SELECT @CreateDate
=
DATEADD( n, @TimeZoneAdjustment, @CreateDate )
IF( @TranStarted
=
1
)
BEGIN
SET @TranStarted
=
0
COMMIT TRANSACTION
END
RETURN
0
Cleanup:
IF( @TranStarted
=
1
)
BEGIN
SET @TranStarted
=
0
ROLLBACK TRANSACTION
END
RETURN @ErrorCode
END
GO
够长的,不过没有关系,分几个部分看,首先是定义一些要发挥得参数,然后初始化,接着EXEC dbo.aspnet_Applications_CreateApplication,调用aspnet_Applications_CreateApplication存储过程,建立一个名字为@ApplicationName 的Application,如果该Application不存在的话。并且返回该Application的ID,这里的ApplicationName在web.config membership节点中设置过,即:dev。如果执行以上过程有错误,通过SQL的GOTO语句跳至Cleanup部分,执行ROLLBACK TRANSACTION,回滚这次操作。如果没有错误存储过程就接着向下执行,EXEC dbo.aspnet_GetUtcDate @TimeZoneAdjustment, @CreateDate OUTPUT,这是获得当前Utc时间。再下来就判断aspnet_Users表中用户的UserId是否在数据库中有该UserId(UserId是一个Guid),如果没有就在表aspnet_Users中建立。这时在进行一次失分发生错误的判断,执行的方法与前一次一样。再下来判断aspnet_Membership表中是否有该UserId存在,如果没有就根据@UniqueEmail参数判断是否允许Email在数据库中重复。最后才是把User的信息插入aspnet_Membership表,再下来还有一些对错误的处理
其实这个存储过程并不复杂,但是非常繁琐的,也可以看出设计者对数据库检验的严格性的要求非常高。有了对存储过程一定的了解后,我们接下来那些传递的参数也就明白有何用处了,最后关闭数据库的连接,把返回的这些参数通过实例化一个MembershipUser类传递过去,然后返回这个实例化的MembershipUser,这样该方法就完成了一次操作。
最后我们看看数据库,Membership直接关联的有3个表
表很简单,关系也很明了,我就不多说了,总要给我留点时间吧,也给你自己留一些分析的空间,我要是全都说完了那你做什么?呵呵。
如果你了解CS系统,你肯定会提出这样一个疑问:用户信息不只表Membership中这一点呀,保存用户个性化设置的如选用什么语言、什么皮肤等等信息的数据都在哪里?期待吧,那是后面的Profile专题需要叙述的问题
绿色通道:
好文要顶
关注我
收藏该文
与我联系
posted on 2006-06-02 15:29
阿昆
阅读(221)
评论(0)
编辑
收藏
注册用户登录后才能发表评论,请
登录
或
注册
,
返回博客园首页
。
首页
博问
闪存
新闻
园子
招聘
知识库
最新IT新闻
:
·
伊朗封杀Gmail和Facebook等互联网服务
·
分析称专利之争让谷歌苹果两败俱伤
·
Android平台发现新型手机病毒Rootsmart
·
HTC首款Android4.0手机大曝光
·
这是不是你期待的 iPad 3?
»
更多新闻...
最新知识库文章
:
·
高级编程语言的发展历程
·
如何学习一门新的编程语言?
·
学习不同编程语言的重要性
·
为什么我喜欢富于表达性的编程语言
·
计算机专业的女生为什么要学编程
»
更多知识库文章...
China-pub 2011秋季教材巡展
China-Pub 计算机绝版图书按需印刷服务
公告
百容科技是中国内容管理(ECM)领域的著名品牌,是.NET平台下专业的内容管理解决方案及服务提供商。
昵称:
阿昆
园龄:
6年8个月
粉丝:
0
关注:
0
搜索
常用链接
我的随笔
我的评论
我的参与
最新评论
我的标签
随笔分类
(86)
ajax(9)
(rss)
ASP.NET(9)
(rss)
C#(2)
(rss)
Community Server(24)
(rss)
创业
(rss)
存储过程(1)
(rss)
管理(3)
(rss)
技术(8)
(rss)
生活(2)
(rss)
搜索引擎(4)
(rss)
信息采集(20)
(rss)
中文分词(4)
(rss)
随笔档案
(87)
2006年6月 (29)
2006年4月 (1)
2006年3月 (50)
2006年2月 (7)
文章分类
(1)
English(1)
(rss)
推荐排行榜