memberrole是一個(gè)在asp.net 1.1下實(shí)現(xiàn)用戶管理、角色管理、用戶特性信息存儲(chǔ)(profile)等的一個(gè)組件,該組件被asp.net 2.0 beta 2所采用,也就是asp.net 2.0 beta 2中所說(shuō)的membership and roles。如果你在asp.net 1.1下采用了memberrole,那么你的web程序?qū)?huì)很容易的過(guò)渡到asp.net 2.0,另外多個(gè)采取memberrole進(jìn)行用戶管理的web程序需要整合時(shí)也非常容易。我將分4個(gè)專題來(lái)分析memberrole,探索一下memberrole到底是如何工作的,無(wú)論對(duì)cs的構(gòu)架還是對(duì)了解asp.net 2.0都是非常有幫助的。
cs中,運(yùn)用該組件的4個(gè)部分:membership、rolemanager、profile、anonymousidentification的運(yùn)用(整個(gè)memberrole也這四部分功能)。
在分析前,準(zhǔn)備需要一個(gè)工具:reflector.exe,沒(méi)有的朋友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:這是一個(gè)數(shù)值,用來(lái)計(jì)算在線用戶的數(shù)量,例如:15,就表示如果用戶在15分鐘后不活動(dòng)(發(fā)出http請(qǐng)求)cs系統(tǒng)將視該用戶不在線。
name:名稱
type:類的名字空間與所在的程序集合
connectionstringname:數(shù)據(jù)庫(kù)連接字符串節(jié)點(diǎn)的key。通過(guò)這個(gè)key就可以找到連接數(shù)據(jù)庫(kù)的用戶名與密碼
enablepasswordretrieval:是否打開取回秘密功能
enablepasswordreset:是否打開秘密重新設(shè)功能
requiresquestionandanswer:注冊(cè)時(shí)是否需要填寫question與answer
requiresuniqueemail:注冊(cè)時(shí)是否email唯一
passwordformat:密碼的加密格式
applicationname:使用該membership應(yīng)用程序的名稱
description:描述信息
以下4個(gè)參數(shù)是ccs中添加的,目的是給注冊(cè)用戶自動(dòng)開通相冊(cè)和博客
autocreateblog:是否當(dāng)用戶注冊(cè)時(shí)自動(dòng)為該用戶建立一個(gè)blog
defaultbloggroupid:默認(rèn)的建立blog的分組id
autocreategallery:是否當(dāng)用戶注冊(cè)時(shí)自動(dòng)為該用戶建立一個(gè)相冊(cè)
defaultgallerygroupid:默認(rèn)的建立相冊(cè)的分組id
用reflector.exe打開memberrole.dll,你可以看到以下的內(nèi)容:

再打開microsoft.scalablehosting.configuration節(jié)點(diǎn)

這次我們只關(guān)注兩個(gè)類membershipconfig、membershipconfighandler。membershipconfighandler實(shí)現(xiàn)了iconfigurationsectionhandler接口。也就是說(shuō),cs啟動(dòng)后如果調(diào)用configurationsettings.getconfig("memberrolesprototype/membership"),系統(tǒng)將會(huì)自動(dòng)的調(diào)用membershipconfighandler中的create方法把web.config中memberrolesprototype/membership的配置內(nèi)容讀入進(jìn)行處理。先看以下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;
}
代碼有點(diǎn)長(zhǎng),其實(shí)這里就是把web.config下面membership節(jié)點(diǎn)的配置信息讀入,進(jìn)行初始化,然后通過(guò)gettype與activator.createinstance方法反射后實(shí)例化一個(gè)membershipprovider。
membershipprovider又是什么,繼續(xù)看看:
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;
}
原來(lái)membershipprovider是實(shí)現(xiàn)繼承了providerbase的一個(gè)類,其實(shí)providerbase并沒(méi)有什么,看看代碼
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僅僅只是保護(hù)了兩個(gè)虛屬性的類,設(shè)置這個(gè)類只是一種設(shè)計(jì)模式,沒(méi)有特別的用途。我們把重點(diǎn)集中到membershipprovider上,membershipprovider類是provider構(gòu)架的一種體現(xiàn)形式,provider構(gòu)架常常用到對(duì)數(shù)據(jù)庫(kù)的訪問(wèn)上,實(shí)現(xiàn)多數(shù)據(jù)庫(kù),同時(shí)也可以很好的隔離數(shù)據(jù)層與業(yè)務(wù)層的代碼有利于協(xié)作開發(fā)。具體實(shí)現(xiàn)是這樣的,先把要操作數(shù)據(jù)庫(kù)的方法抽象出來(lái),單獨(dú)的放入一個(gè)abstract類,例如:membershipprovider,然后通過(guò)繼承,把這些抽象的方法全部的實(shí)現(xiàn)出來(lái),例如:sqlmembershipprovider。這樣有什么好處呢?對(duì)于三層構(gòu)架的web應(yīng)用程序來(lái)說(shuō),中間的業(yè)務(wù)邏輯層調(diào)用操作數(shù)據(jù)庫(kù)的方法是從membershipprovider,對(duì)于該層來(lái)說(shuō)它并不關(guān)心該抽象層是如何被繼承后實(shí)現(xiàn)抽象方法的。它只關(guān)心抽象層中是否有它想要的方法。如果能理解到這里,嘿嘿,我們的機(jī)會(huì)就來(lái)了,因?yàn)檫@隔離了具體的數(shù)據(jù)庫(kù)操作實(shí)現(xiàn),更進(jìn)一步說(shuō)就是隔離了使用何種數(shù)據(jù)庫(kù)。再?gòu)膱F(tuán)隊(duì)協(xié)作考慮,假設(shè)你的membershipprovider定義的完善后,繼承membershipprovider實(shí)現(xiàn)sql server操作的sqlmembershipprovider類中的方法無(wú)論怎么改變,都不會(huì)影響其它人員在業(yè)務(wù)邏輯層的開發(fā)。聽起來(lái)這種方式好像很美,但是要實(shí)現(xiàn)這樣的功能還需要一個(gè)關(guān)鍵的技術(shù),那就是反射,所以我們看到在配置的字段里有這樣的字節(jié):。
type="openlab.autoregister.csautobloggallerymembershipprovider, openlab.csaddons"
這就是為了通過(guò)反射找到membershipprovider具體方法實(shí)現(xiàn)的類而做的準(zhǔn)備。
注:由于分析的代碼來(lái)源于寶玉的ccs,在ccs中為了實(shí)現(xiàn)注冊(cè)后能自動(dòng)開通博客與相冊(cè)又繼承了sqlmembershipprovider類,重寫了幾個(gè)方法(關(guān)鍵是其中的initialize與createuser方法)。
只要你繼承membershipprovider類,對(duì)其中的抽象方法進(jìn)行實(shí)現(xiàn),無(wú)論你是實(shí)現(xiàn)對(duì)access的操作,還是其它數(shù)據(jù)庫(kù),都是沒(méi)有問(wèn)題的。在asp.net 2.0 beta2中的membership已經(jīng)提供access與sql server兩種數(shù)據(jù)庫(kù)操作實(shí)現(xiàn)。不過(guò)memberrole.dll程序集中只實(shí)現(xiàn)了sqlmembershipprovider,也就是對(duì)sql server的操作。
membership是一個(gè)沒(méi)有表示層的運(yùn)用組件,它包含了邏輯層與數(shù)據(jù)操作層,數(shù)據(jù)層我在這個(gè)專題中就不多做解釋,他的實(shí)現(xiàn)是在sqlmembershipprovider類中,你可以通過(guò)reflector.exe慢慢研究。上面的文字說(shuō)過(guò),實(shí)現(xiàn)provider模式后業(yè)務(wù)邏輯層對(duì)數(shù)據(jù)層具體實(shí)現(xiàn)就不關(guān)心了,在memberrole.dll中的membership,它把所有的這些邏輯用一個(gè)類包裝起來(lái),那就是membership類:

對(duì)于引用memberrole.dll實(shí)現(xiàn)membership功能的web app(dnn,cs就是典型)來(lái)說(shuō),只要做合適的配置之后就可以直接使用了。
例如,要建立一個(gè)用戶只要調(diào)用membership.createuser的靜態(tài)方法,就可以完成(membership會(huì)根據(jù)內(nèi)部的方法和具體的數(shù)據(jù)庫(kù)操作,把用戶信息寫入數(shù)據(jù)庫(kù))。
對(duì)于使用membership來(lái)開發(fā)web app的程序員來(lái)說(shuō),完全可以不必要了解這些細(xì)節(jié),asp 2.0 beta2就沒(méi)有提供這樣的機(jī)會(huì),ms只要求你會(huì)用就可以了,不過(guò)很慶幸的是cs給我們提供了這樣的一個(gè)機(jī)會(huì),了解這些操作的實(shí)質(zhì)(包括url rewrite等功能也是一樣,這些功能都可以在asp 2.0 beta2中直接使用,而不要構(gòu)架如何代碼)。
這個(gè)專題只是大致的講解了membership,下一個(gè)專題,我們將更深入的去看看membership的數(shù)據(jù)層的操作實(shí)現(xiàn)以及數(shù)據(jù)庫(kù)表的設(shè)計(jì)(包括存儲(chǔ)過(guò)程)。
新聞熱點(diǎn)
疑難解答
圖片精選