首页 > 开发 > 综合 > 正文

Community Server专题八:MemberRole之Membership

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

memberrole是一个在asp.net 1.1下实现用户管理、角色管理、用户特性信息存储(profile)等的一个组件,该组件被asp.net 2.0 beta 2所采用,也就是asp.net 2.0 beta 2中所说的membership and roles。如果你在asp.net 1.1下采用了memberrole,那么你的web程序将会很容易的过渡到asp.net 2.0,另外多个采取memberrole进行用户管理的web程序需要整合时也非常容易。我将分4个专题来分析memberrole,探索一下memberrole到底是如何工作的,无论对cs的构架还是对了解asp.net 2.0都是非常有帮助的。

cs中,运用该组件的4个部分:membership、rolemanager、profile、anonymousidentification的运用(整个memberrole也这四部分功能)。

在分析前,准备需要一个工具:reflector.exe,没有的朋友google一下,下载它。

本次专题分析membership,先看一下cs中membership的配置文件(web.config中):

<membership userisonlinetimewindow="15" >

              <providers>

                   <add

                       name="communityserversqlprovider"             

                       type="openlab.autoregister.csautobloggallerymembershipprovider, openlab.csaddons"

                       connectionstringname="sitesqlserver"

                       enablepasswordretrieval="false"

                       enablepasswordreset="true"

                       requiresquestionandanswer="false"

                       requiresuniqueemail="true"

                       passwordformat="hashed"

                       applicationname="dev"

                       description="stores and retrieves membership data from the local microsoft sql server database"

                       autocreateblog="false"

                       defaultbloggroupid="3"

                       autocreategallery="false"

                       defaultgallerygroupid="2"

                       maxinvalidpasswordattempts = "999"

                       passwordattemptwindow = "999"

                       minrequiredpasswordlength = "4"

                       minrequirednonalphanumericcharacters = "0"

                   />

              </providers>

</membership>

userisonlinetimewindow:这是一个数值,用来计算在线用户的数量,例如:15,就表示如果用户在15分钟后不活动(发出http请求)cs系统将视该用户不在线。

name:名称

type:类的名字空间与所在的程序集合

connectionstringname:数据库连接字符串节点的key。通过这个key就可以找到连接数据库的用户名与密码

enablepasswordretrieval:是否打开取回秘密功能

enablepasswordreset:是否打开秘密重新设功能

requiresquestionandanswer:注册时是否需要填写question与answer

requiresuniqueemail:注册时是否email唯一

passwordformat:密码的加密格式

applicationname:使用该membership应用程序的名称

description:描述信息

以下4个参数是ccs中添加的,目的是给注册用户自动开通相册和博客

autocreateblog:是否当用户注册时自动为该用户建立一个blog

defaultbloggroupid:默认的建立blog的分组id

autocreategallery:是否当用户注册时自动为该用户建立一个相册

defaultgallerygroupid:默认的建立相册的分组id

用reflector.exe打开memberrole.dll,你可以看到以下的内容:

再打开microsoft.scalablehosting.configuration节点

这次我们只关注两个类membershipconfig、membershipconfighandler。membershipconfighandler实现了iconfigurationsectionhandler接口。也就是说,cs启动后如果调用configurationsettings.getconfig("memberrolesprototype/membership"),系统将会自动的调用membershipconfighandler中的create方法把web.config中memberrolesprototype/membership的配置内容读入进行处理。先看以下create做了些什么:

public virtual object create(object parent, object configcontextobj, xmlnode section)
{
      membershipconfig config1 = new membershipconfig(parent as membershipconfig);
      int num1 = -1;
      configutils.getandremovepositiveintegerattribute(section, "userisonlinetimewindow", ref num1);
      if (num1 > 0)
      {
            config1.userisonlinetimewindow = num1;
      }
      string text1 = null;
      configutils.getandremovestringattribute(section, "hashalgorithm", ref text1);
      if ((text1 != null) && (text1.length > 0))
      {
            config1.hashalgorithmtype = text1;
      }
      configutils.checkforunrecognizedattributes(section);
      membershipprovider provider1 = null;
      foreach (xmlnode node1 in section.childnodes)
      {
            if (node1.nodetype != xmlnodetype.element)
            {
                  continue;
            }
            if (node1.name != "providers")
            {
                  throw new configurationexception("unrecognized tag: " + node1.name, node1);
            }
            foreach (xmlnode node2 in node1.childnodes)
            {
                  if (node2.nodetype == xmlnodetype.element)
                  {
                        if (node2.name != "add")
                        {
                              throw new configurationexception("unrecognized tag: " + node2.name, node2);
                        }
                        if (provider1 != null)
                        {
                              throw new configurationexception("only one provider can be configured", node2);
                        }
                        string text2 = null;
                        string text3 = null;
                        configutils.getandremoverequirednonemptystringattribute(node2, "name", ref text2);
                        configutils.getandremoverequirednonemptystringattribute(node2, "type", ref text3);
                        namevaluecollection collection1 = new namevaluecollection();
                        foreach (xmlattribute attribute1 in node2.attributes)
                        {
                              if ((attribute1.name != null) && (attribute1.name.length > 0))
                              {
                                    collection1.add(attribute1.name, attribute1.value);
                              }
                        }
                        provider1 = (membershipprovider) activator.createinstance(type.gettype(text3, true));
                        provider1.initialize(text2, collection1);
                        config1.provider = provider1;
                  }
            }
      }
      return config1;
}

代码有点长,其实这里就是把web.config下面membership节点的配置信息读入,进行初始化,然后通过gettype与activator.createinstance方法反射后实例化一个membershipprovider。

membershipprovider又是什么,继续看看:

public abstract class membershipprovider : providerbase
{
      // events
      public event membershipvalidatepasswordeventhandler validatingpassword;
 
      // methods
      protected membershipprovider();
      public abstract bool changepassword(string username, string oldpassword, string newpassword);
      public abstract bool changepasswordquestionandanswer(string username, string password, string newpasswordquestion, string newpasswordanswer);
      public abstract membershipuser createuser(string username, string password, string email, string passwordquestion, string passwordanswer, bool isapproved, object provideruserkey, out membershipcreatestatus status);
      protected virtual byte[] decryptpassword(byte[] encodedpassword);
      public abstract bool deleteuser(string username, bool deleteallrelateddata);
      internal string encodepassword(string pass, int passwordformat, string salt);
      protected virtual byte[] encryptpassword(byte[] password);
      public abstract membershipusercollection findusersbyemail(string emailtomatch, int pageindex, int pagesize, out int totalrecords);
      public abstract membershipusercollection findusersbyname(string usernametomatch, int pageindex, int pagesize, out int totalrecords);
      internal string generatesalt();
      public abstract membershipusercollection getallusers(int pageindex, int pagesize, out int totalrecords);
      public abstract int getnumberofusersonline();
      public abstract string getpassword(string username, string answer);
      public abstract membershipuser getuser(object provideruserkey, bool userisonline);
      public abstract membershipuser getuser(string username, bool userisonline);
      public abstract string getusernamebyemail(string email);
      protected virtual void onvalidatingpassword(validatepasswordeventargs e);
      public abstract string resetpassword(string username, string answer);
      internal string unencodepassword(string pass, int passwordformat);
      public abstract bool unlockuser(string username);
      public abstract void updateuser(membershipuser user);
      public abstract bool validateuser(string username, string password);
 
      // properties
      public abstract string applicationname { get; set; }
      public abstract bool enablepasswordreset { get; }
      public abstract bool enablepasswordretrieval { get; }
      public abstract int maxinvalidpasswordattempts { get; }
      public abstract int minrequirednonalphanumericcharacters { get; }
      public abstract int minrequiredpasswordlength { get; }
      public abstract int passwordattemptwindow { get; }
      public abstract membershippasswordformat passwordformat { get; }
      public abstract string passwordstrengthregularexpression { get; }
      public abstract bool requiresquestionandanswer { get; }
      public abstract bool requiresuniqueemail { get; }
 
      // fields
      private membershipvalidatepasswordeventhandler _eventhandler;
      private const int salt_size_in_bytes = 0x10;
}

原来membershipprovider是实现继承了providerbase的一个类,其实providerbase并没有什么,看看代码

public abstract class providerbase
{
      // methods
      protected providerbase();
      public virtual void initialize(string name, namevaluecollection config);
 
      // properties
      public virtual string description { get; }
      public virtual string name { get; }
 
      // fields
      private string _description;
      private bool _initialized;
      private string _name;
}

providerbase仅仅只是保护了两个虚属性的类,设置这个类只是一种设计模式,没有特别的用途。我们把重点集中到membershipprovider上,membershipprovider类是provider构架的一种体现形式,provider构架常常用到对数据库的访问上,实现多数据库,同时也可以很好的隔离数据层与业务层的代码有利于协作开发。具体实现是这样的,先把要操作数据库的方法抽象出来,单独的放入一个abstract类,例如:membershipprovider,然后通过继承,把这些抽象的方法全部的实现出来,例如:sqlmembershipprovider。这样有什么好处呢?对于三层构架的web应用程序来说,中间的业务逻辑层调用操作数据库的方法是从membershipprovider,对于该层来说它并不关心该抽象层是如何被继承后实现抽象方法的。它只关心抽象层中是否有它想要的方法。如果能理解到这里,嘿嘿,我们的机会就来了,因为这隔离了具体的数据库操作实现,更进一步说就是隔离了使用何种数据库。再从团队协作考虑,假设你的membershipprovider定义的完善后,继承membershipprovider实现sql server操作的sqlmembershipprovider类中的方法无论怎么改变,都不会影响其它人员在业务逻辑层的开发。听起来这种方式好像很美,但是要实现这样的功能还需要一个关键的技术,那就是反射,所以我们看到在配置的字段里有这样的字节:。

type="openlab.autoregister.csautobloggallerymembershipprovider, openlab.csaddons"

这就是为了通过反射找到membershipprovider具体方法实现的类而做的准备。

注:由于分析的代码来源于宝玉的ccs,在ccs中为了实现注册后能自动开通博客与相册又继承了sqlmembershipprovider类,重写了几个方法(关键是其中的initialize与createuser方法)。

只要你继承membershipprovider类,对其中的抽象方法进行实现,无论你是实现对access的操作,还是其它数据库,都是没有问题的。在asp.net 2.0 beta2中的membership已经提供access与sql server两种数据库操作实现。不过memberrole.dll程序集中只实现了sqlmembershipprovider,也就是对sql server的操作。

membership是一个没有表示层的运用组件,它包含了逻辑层与数据操作层,数据层我在这个专题中就不多做解释,他的实现是在sqlmembershipprovider类中,你可以通过reflector.exe慢慢研究。上面的文字说过,实现provider模式后业务逻辑层对数据层具体实现就不关心了,在memberrole.dll中的membership,它把所有的这些逻辑用一个类包装起来,那就是membership类:

对于引用memberrole.dll实现membership功能的web app(dnn,cs就是典型)来说,只要做合适的配置之后就可以直接使用了。

例如,要建立一个用户只要调用membership.createuser的静态方法,就可以完成(membership会根据内部的方法和具体的数据库操作,把用户信息写入数据库)。

对于使用membership来开发web app的程序员来说,完全可以不必要了解这些细节,asp 2.0 beta2就没有提供这样的机会,ms只要求你会用就可以了,不过很庆幸的是cs给我们提供了这样的一个机会,了解这些操作的实质(包括url rewrite等功能也是一样,这些功能都可以在asp 2.0 beta2中直接使用,而不要构架如何代码)。

这个专题只是大致的讲解了membership,下一个专题,我们将更深入的去看看membership的数据层的操作实现以及数据库表的设计(包括存储过程)。


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