首页 > 开发 > 综合 > 正文

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

2024-07-21 02:29:26
字体:
来源:转载
供稿:网友

专题八的上篇大致讨论了memberrole中的membership实现,对于运用membership进行足够,但是对于想更深入了解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, [email protected], @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专题需要叙述的问题。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表