Community Server专题八:MemberRole之Membership深入篇

专题八的上篇大致讨论了MemberRole中的Membership实现,对于运用Membership进行web开发足够,但是对于想更深入了解Membership实现机理的朋友那是远远不够的,这个专题我们更深入一下了解Membership

其实MemberRole是一个非常好的资源包,借住Reflector这个优秀的工具,你可以对其进行代码分析。它无论是在组建的构架、代码的设计、数据库表的建立、存储过程的使用等都是非常优秀的,你是程序员也好构架师也罢,其中可以学习的真的很多很多,我在整个分析的过程中也深深受益。

由于MemberRole中的Membership只实现了对SQL Server的操Provider类,即SqlMembershipProvider类。因此我们从SqlMembershipProvider开始分析。Provider模型在上篇已经做过介绍,SqlMembershipProvider类继承了MembershipProvider,并实现其所有的抽象方法。在分析之前先看两个类:MembershipUserMembershipUserCollection

MembershipUser,先看看代码:(代码中省略的具体实现,只有方法与属性名称)

Membership创建的User,该类中有这个User的一些基本状态,如该UserUserNameEmail等,还有一些方法,如ChangePassword()ResetPassword()等(如果你是初学者,还在为建立一个对象需要什么属性,包含什么方法发愁,那这就是你应该好好学的,这也是OOP最基本的要求)。

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 getset; }
      
public virtual DateTime CreationDate get; }
      
public virtual string Email getset; }
      
public virtual bool IsApproved getset; }
      
public virtual bool IsLockedOut get; }
      
public bool IsOnline get; }
      
public virtual DateTime LastActivityDate getset; }
      
public virtual DateTime LastLockoutDate get; }
      
public virtual DateTime LastLoginDate getset; }
      
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;
}

这是一个实体类,表示一个由

MembershipUserCollection,这是一个MembershipUser类的容器,用来存放MembershipUser列表,记得上次广州.net俱乐部聚会时,我的演讲中有朋友在提出CS是否使用自定义类来存储用户列表,其实在这里可以看到CS中使用的就是自定义的类而不是DataSet(我想在asp.net 2.0正式发布后这也不会改变),这样做主要是因为考虑到性能与灵活性。

好了,回到SqlMembershipProvider类上来,我们具体分析一个有代表性质的方法:

MembershipUser对象,如果建立失败MembershipUser对象为null(其实我早期做过一些项目的时候喜欢在建立对象成功后返回一个ID)。可以看到在这个方法中有很多的if语句,它们是为了检验数据是否合法,这是必须的吗?其实不是,但对于构建一个强壮的底层代码这是必须的,不然一点点的错误都有可能导致系统的瘫痪。其实做项目与做开发有的时候不太一样,企业的有些项目开发很多时候只要能实现功能就可以了,而且开发过程也集中在一些现有的代码或者组建的基础上,个人的错误不会影响全局的运行,PM也不做过多要求。但如果是做产品,这个情况可能会有所改变,很多时候要求很严格,至少我是这样。在做完对输入参数的验证后,CreateUser建立与数据库的连接,这里是调用SqlConnectionHelper类下的GetConnection方法进行的,为了照顾初学者阅读,我这里这一下为什么需要把对数据库连接与操作写在SqlConnectionHelper类下,而不是直接采用SqlConnection提供的方法,其实这是一个设计模式的问题,Membership的实现需要很多的方法与数据库进行交换数据库,如果每次方法都调用一次SqlConnection的方法建立数据库连接,一来会造成大量的代码冗余,而且一旦数据库连接语句一旦改变,你就要去修改很多个方法,如果你把这个过程都包装在一个类下面,连接数据库就有统一的入口,一来容易维护,二来不会有太多的代码冗余,再者如果需要查找错误也非常容易。这里Membership采用的是存储过程,我们可以看到使用的是dbo.aspnet_Membership_CreateUser存储过程,好了,打开你的数据库,找到这个存储过程:

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, truetruefalse0x80))

      
{

            status 
= MembershipCreateStatus.InvalidPassword;

            
return null;

      }


      
string text1 = base.GenerateSalt();

      
string text2 = base.EncodePassword(password, (intthis._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), (intthis._PasswordFormat, text1);

      }


      
else

      
{

            text3 
= passwordAnswer;

      }


      
if (!SecUtility.ValidateParameter(ref text3, this.RequiresQuestionAndAnswer, this.RequiresQuestionAndAnswer, false0x80))

      
{

            status 
= MembershipCreateStatus.InvalidAnswer;

            
return null;

      }


      
if (!SecUtility.ValidateParameter(ref username, truetruetrue0x100))

      
{

            status 
= MembershipCreateStatus.InvalidUserName;

            
return null;

      }


      
if (!SecUtility.ValidateParameter(ref email, this.RequiresUniqueEmail, this.RequiresUniqueEmail, false0x100))

      
{

            status 
= MembershipCreateStatus.InvalidEmail;

            
return null;

      }


      
if (!SecUtility.ValidateParameter(ref passwordQuestion, this.RequiresQuestionAndAnswer, this.RequiresQuestionAndAnswer, false0x100))

      
{

            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, (intthis.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(0x6da11));

            }


            
finally

            
{

                  
if (holder1 != null)

                  
{

                        holder1.Close();

                        holder1 
= null;

                  }


            }


      }


      
catch

      
{

            
throw;

      }


      
return user1;

}


该方法实现建立一个用户的过程,建立后返回一个被建立的

EXEC dbo.aspnet_Applications_CreateApplication,调用aspnet_Applications_CreateApplication存储过程,建立一个名字为@ApplicationName Application,如果该Application不存在的话.并且返回该ApplicationID,这里的ApplicationNameweb.config membership节点中设置过,即:dev。如果执行以上过程有错误,通过SQLGOTO语句跳至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 2005-09-19 09:45 彭斌 阅读(3917) 评论(16)  编辑  收藏 所属分类: CommunityServer

#2楼 [楼主]   回复  引用  查看    

membership是不对用户角色做管理的,但是MemberRole下面还有另外一个机制实现,那就是RoleManager,通过给User赋予不同的角色年可以实现你说的个人用户和企业用户,然后可以建立自己的权限机制,不同的角色拥有不同的权限,这样就可以实现你说的不同的用户在网站有不同的功能。
这很容易办到。
2005-09-19 15:04 | uGoer

#3楼    回复  引用  查看    

不错,偶正在学习.net编程ing.

#4楼    回复  引用  查看    

不单是权限问题,个人和企业用户,各自的注册信息的条目都是不一样的,比如企业用户有一项"法定代表人",而个人是没有这一项的.
2005-09-19 15:46 | himan [未注册用户]

#5楼 [楼主]   回复  引用  查看    

@himan
就我对权限的理解,权限是可以控制页面任意布局与页面任意信息的,举例说明一下:
当页面被请求时,你可以先判断是什么角色的用户,如果是企业用户,在构建页面的时候就显示"法定代表人",当是个人用户的角色时,就不显示该信息。
不知道能否能理解?
2005-09-19 16:39 | uGoer

#6楼    回复  引用  查看    

感谢您的答复,
你的意思我理解,我主要的疙瘩是在数据库表的设计上,是不是我要把个人用户和企业用户各自的注册信息字段都放在一个表里?还是分开来放两个表里,哪种合适.能给我这种数据库设计的指导吗?
2005-09-19 16:52 | himan [未注册用户]

#7楼 [楼主]   回复  引用  查看    

你可以使用MemberRole的 Profile机制来实现个性化信息存储,这个是一种解决方法。或者新建一个表 ,专门存放企业用户的专有信息,该表与UserId关联。

其实很多时候处理这里复杂信息的时候需要做一些折中,太追求完美往往达不到效果。
2005-09-19 17:00 | uGoer

#8楼    回复  引用  查看    

TO:uGoer,我引用了该文,实现了实例,如下
http://mqingqing123.cnblogs.com/archive/2005/08/08/210291.html
2005-09-20 11:27 | 天天

#9楼    回复  引用  查看    

我今天在用宝玉的CS1.04改论坛时,在我的项目中用到了
member = Membership.CreateUser(username,password,email);//username,password,email都有正确的值
走到这步就给我来个错误:
Membership InvalidPassword 是为何???????????????????????????????

我想知道它的实现原理。请高手赐教!
2005-09-28 11:54 | zf [未注册用户]

#10楼 [楼主]   回复  引用  查看    

@zf
你检查一下秘密长度或者秘密的规则
2005-09-28 12:41 | uGoer

#11楼    回复  引用  查看    

@uGoer
你检查一下秘密长度或者秘密的规则

在哪里有长度和规则的限制我没找到
我用REF看了一下源码不解..........


member = Membership.CreateUser(username,password,email);

------------------------
public static MembershipUser CreateUser(string username, string password, string email)
{
MembershipCreateStatus status1;
MembershipUser user1 = Membership.CreateUser(username, password, email, null, null, true, out status1);
if (user1 == null)
{
throw new MembershipCreateUserException(status1);
}
return user1;
}

----------------------------
public static MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, out MembershipCreateStatus status)
{
return Membership.CreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, null, out status);
}

---------------------------
public static MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
if (!SecUtility.ValidateParameter(ref username, true, true, true, 0))
{
status = MembershipCreateStatus.InvalidUserName;
return null;
}
if (!SecUtility.ValidateParameter(ref password, 0))
{
status = MembershipCreateStatus.InvalidPassword;
return null;
}
if (!SecUtility.ValidateParameter(ref email, false, false, false, 0))
{
status = MembershipCreateStatus.InvalidEmail;
return null;
}
if (!SecUtility.ValidateParameter(ref passwordQuestion, false, true, false, 0))
{
status = MembershipCreateStatus.InvalidQuestion;
return null;
}
if (!SecUtility.ValidateParameter(ref passwordAnswer, false, true, false, 0))
{
status = MembershipCreateStatus.InvalidAnswer;
return null;
}
return Membership.Provider.CreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status);
}


================
internal static bool ValidateParameter(ref string param, int maxSize)
{
if (param == null)
{
return false;
}
if (param.Trim().Length < 1)
{
return false;
}
if ((maxSize > 0) && (param.Length > maxSize))
{
return false; //这里返回的
}
return true;
}


2005-09-28 14:07 | zf [未注册用户]

#12楼 [楼主]   回复  引用  查看    

@zf
你调试的时候跟踪一下password,看看是什么值
你可以通过msn与我联系 ugoer#msn.com
2005-09-28 14:29 | uGoer

#13楼    回复  引用  查看    

哈哈,我找了问题所在了,原来的系统里密码位数太少,所以报错!
2005-09-28 14:39 | zf [未注册用户]

#14楼    回复  引用  查看    

问下大侠;
请问我如何改写membershipuser这个实体类啊?
因为他远远不能满足我的要求.
2006-10-24 11:58 | fangyifeng [未注册用户]

#15楼    回复  引用  查看    

收藏
2007-01-06 08:53 | 垃圾猪

#16楼 [TrackBack]   回复  引用  查看    

1.学习资料园子里的
[引用提示]MK2引用了该文章, 地址: http://www.cnblogs.com/fengmk2/archive/2006/11/25/572176.html  
#1楼     回复   引用   查看     
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值