前言
菜鳥去重復之Sql的問題還沒有得到滿意的答案。如果哪位大哥有相關的資料解釋,能夠分享給我,那就太謝謝了。
以后每發表一篇博文我都會將以前遺留的問題在前言里指出,直到解決為止。
本文主要在於探討一下Asp.net Mvc4默認生成的權限的詳細內容。
本文篇幅貼的代碼有點多,難免枯燥乏味,奈何水平有限,不貼不行,還請見諒!
無可奈何的表名
還記得這張圖片不
是不是感覺這些表名看起來很不爽,非要有個webpages前綴。
於是我第一時間想到是不是有方法來設置這些表名。在上篇博客我們已經知道了是
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);
這行代碼內部創建了上圖的四個表。而這個方法內部是如何創建這些表的呢?我也不知道。反編譯吧!反編譯的代碼有點多,我把主要的貼出來然后一步步分析下
1 private static void InitializeProviders(DatabaseConnectionInfo connect, string userTableName, string userIdColumn, string userNameColumn, bool autoCreateTables) 2 { 3 SimpleMembershipProvider simpleMembership = Membership.Provider as SimpleMembershipProvider; 4 if (simpleMembership != null) 5 { 6 InitializeMembershipProvider(simpleMembership, connect, userTableName, userIdColumn, userNameColumn, autoCreateTables); 7 } 8 SimpleRoleProvider provider = Roles.Provider as SimpleRoleProvider; 9 if (provider != null) 10 { 11 InitializeRoleProvider(provider, connect, userTableName, userIdColumn, userNameColumn, autoCreateTables); 12 } 13 Initialized = true; 14 } 15 16 internal static void InitializeMembershipProvider(SimpleMembershipProvider simpleMembership, DatabaseConnectionInfo connect, string userTableName, string userIdColumn, string userNameColumn, bool createTables) 17 { 18 if (simpleMembership.InitializeCalled) 19 { 20 throw new InvalidOperationException(WebDataResources.Security_InitializeAlreadyCalled); 21 } 22 simpleMembership.ConnectionInfo = connect; 23 simpleMembership.UserIdColumn = userIdColumn; 24 simpleMembership.UserNameColumn = userNameColumn; 25 simpleMembership.UserTableName = userTableName; 26 if (createTables) 27 { 28 simpleMembership.CreateTablesIfNeeded(); 29 } 30 else 31 { 32 simpleMembership.ValidateUserTable(); 33 } 34 simpleMembership.InitializeCalled = true; 35 }
在InitializeDatabaseConnection方法中調用了InitializeProviders方法(里面的SimpleMembership和SimpleRoleProvider后面會說到)。
我們還是直接看26-33行,createTables是bool型,就是我們的autoCreateTables參數,只是換了個變量名。
從上我們可以知道如果設置autoCreateTables為true,調用simpleMembership.CreateTablesIfNeeded方法。反之,調用simpleMembership.ValidateUserTable方法。下來讓我們看下我們需要的CreateTablesIfNeeded實現
1 internal void CreateTablesIfNeeded() 2 { 3 using (IDatabase database = this.ConnectToDatabase()) 4 { 5 if (!CheckTableExists(database, this.UserTableName)) 6 { 7 database.Execute("CREATE TABLE " + this.SafeUserTableName + "(" + this.SafeUserIdColumn + " int NOT NULL PRIMARY KEY IDENTITY, " + this.SafeUserNameColumn + " nvarchar(56) NOT NULL UNIQUE)", new object[0]); 8 } 9 if (!CheckTableExists(database, OAuthMembershipTableName)) 10 { 11 database.Execute("CREATE TABLE " + OAuthMembershipTableName + " (Provider nvarchar(30) NOT NULL, ProviderUserId nvarchar(100) NOT NULL, UserId int NOT NULL, PRIMARY KEY (Provider, ProviderUserId))", new object[0]); 12 } 13 if (!CheckTableExists(database, MembershipTableName)) 14 { 15 database.Execute("CREATE TABLE " + MembershipTableName + " (\r\n UserId int NOT NULL PRIMARY KEY,\r\n CreateDate datetime ,\r\n ConfirmationToken nvarchar(128) ,\r\n IsConfirmed bit DEFAULT 0,\r\n LastPasswordFailureDate datetime ,\r\n PasswordFailuresSinceLastSuccess int NOT NULL DEFAULT 0,\r\n Password nvarchar(128) NOT NULL,\r\n PasswordChangedDate datetime ,\r\n PasswordSalt nvarchar(128) NOT NULL,\r\n PasswordVerificationToken nvarchar(128) ,\r\n PasswordVerificationTokenExpirationDate datetime)", new object[0]); 16 } 17 } 18 }
1-17行 我們一眼就看出來,是這個方法使用Sql創建表,而表名正是其中的幾個變量:SafeUserTableName,OAuthMembershipTableName,MembershipTableName。而這幾個變量是什么樣的!?
1 private string SafeUserTableName 2 { 3 get 4 { 5 return ("[" + this.UserTableName + "]"); 6 } 7 } 8 9 public string UserTableName { get; set; } 10 11 internal static string OAuthMembershipTableName 12 { 13 get 14 { 15 return "webpages_OAuthMembership"; 16 } 17 } 18 19 internal static string MembershipTableName 20 { 21 get 22 { 23 return "webpages_Membership"; 24 } 25 }
這下我們終於知道了:OAuthMembershipTableName,MembershipTableName是沒有Set方法的,而UserTableName可以Set。所以想通過這種途徑改表名的想法不可行了。
WebSecurity
在我們創建的帶有權限的MVC4項目中,在AccountController里我們見到的很多的WebSecurity,登錄注冊注銷內部都調用了WebSercurity的靜態方法。而這些方法又是如何實現的,內部都干了些什么呢?
MSDN上有WebSecurity的解釋,其中有這么幾句
WebSecurity 類在后台與 ASP.NET 成員資格提供程序交互,后者可完成執行安全任務所需的低級工作。ASP.NET Web Pages 中的默認成員資格提供程序為 SimpleMembershipProvider 類
WebSecurity 類不包括用於創建角色和向用戶分配角色的功能。
如果你不想將 WebSecurity 類用於自己的網站,則必須將網站配置為使用標准 ASP.NET 成員資格和角色提供程序。此外,你不得調用 InitializeDatabaseConnection() 方法。將仍然加載 SimpleMembershipProvider 和 SimpleRoleProvider 類,但會將方法和屬性調用傳遞給標准成員資格和角色提供程序。
WebSecurity 和 SimpleMembershipProvider 僅實現哈希選項,該選項被視為這些選項中最安全的選項。因此,WebSecurity 不允許你恢復用戶的密碼;WebSecurity 限制密碼恢復選項的目的,是讓你為用戶創建新密碼。
也就是說其實WebSecurity內部使用的使用的登錄注冊注銷等的一些方法其實是直接調用SimpleMembershipProvider的方法(反編譯也可以印證這一點)。
當然WebSecurity和SimpleMembershipProvider還是有一些區別的,有興趣的童鞋可以點開鏈接或者反編譯自己比較下,此處就多說了。
那我們想要知道登錄注冊注銷的內部方法,就可以直接去看SimpleMemberShipProvider了。
SimpleMemberShipProvider
這里,我們可以看到MSDN里對這個類的解釋
WebSecurity 幫助器類是建議用於管理用戶(成員資格)帳戶、密碼和執行其他成員資格任務的方法。
SimpleMembershipProvider 類可以管理成員資格任務;
但是,由於 WebSecurity 提供了一種實現成員資格的更簡單方法,因此不建議使用該類。
SimpleMembershipProvider 類旨在供需要對成員資格進程實施更准確控制的開發人員使用
從上我們知道,雖然WebSecurity和SimpleMemebershipProvider功能差不多,但WebSecurity更方便簡潔,微軟建議使用WebSecurity。如果想有更精確地控制則使用SimpleMembershipProvider。
好吧!我們還是繼續我們的分析吧!此處以注冊里的創建賬戶為例。
CreateUserAndAccount(String username, String password, Boolean requireConfirmationToken)創建新的用戶配置文件和新的成員資格帳戶。
參數解釋
userName 用戶名 password 密碼
requireConfirmationToken (可選)若指定必須確認用戶帳戶,則為 true;否則為 false。默認值為 false。
返回string 可以發送給用戶以確認用戶帳戶的令牌。
1 public override string CreateAccount(string userName, string password, bool requireConfirmationToken) 2 { 3 this.VerifyInitialized(); 4 if (password.IsEmpty()) 5 { 6 throw new MembershipCreateUserException(MembershipCreateStatus.InvalidPassword); 7 } 8 string str = Crypto.HashPassword(password); 9 if (str.Length > 0x80) 10 { 11 throw new MembershipCreateUserException(MembershipCreateStatus.InvalidPassword); 12 } 13 if (userName.IsEmpty()) 14 { 15 throw new MembershipCreateUserException(MembershipCreateStatus.InvalidUserName); 16 } 17 using (IDatabase database = this.ConnectToDatabase()) 18 { 19 int num = GetUserId(database, this.SafeUserTableName, this.SafeUserNameColumn, this.SafeUserIdColumn, userName); 20 if (num == -1) 21 { 22 throw new MembershipCreateUserException(MembershipCreateStatus.ProviderError); 23 } 24 object obj2 = database.QuerySingle("SELECT COUNT(*) FROM [" + MembershipTableName + "] WHERE UserId = @0", new object[] { num }); 25 if (<CreateAccount>o__SiteContainer22.<>p__Site23 == null) 26 { 27 <CreateAccount>o__SiteContainer22.<>p__Site23 = CallSite<Func<CallSite, object, bool>>.Create(Binder.UnaryOperation(CSharpBinderFlags.None, ExpressionType.IsTrue, typeof(SimpleMembershipProvider), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) })); 28 } 29 if (<CreateAccount>o__SiteContainer22.<>p__Site23.Target(<CreateAccount>o__SiteContainer22.<>p__Site23, ((dynamic) obj2)[0] > 0)) 30 { 31 throw new MembershipCreateUserException(MembershipCreateStatus.DuplicateUserName); 32 } 33 string str2 = null; 34 object obj3 = DBNull.Value; 35 if (requireConfirmationToken) 36 { 37 str2 = GenerateToken(); 38 obj3 = str2; 39 } 40 int num2 = 0; 41 if (database.Execute("INSERT INTO [" + MembershipTableName + "] (UserId, [Password], PasswordSalt, IsConfirmed, ConfirmationToken, CreateDate, PasswordChangedDate, PasswordFailuresSinceLastSuccess) VALUES (@0, @1, @2, @3, @4, @5, @5, @6)", new object[] { num, str, string.Empty, !requireConfirmationToken, obj3, DateTime.UtcNow, num2 }) != 1) 42 { 43 throw new MembershipCreateUserException(MembershipCreateStatus.ProviderError); 44 } 45 return str2; 46 } 47 }
上面貼出的並不是完整的CreateUserAndAccount方法實現,只是里面調用的一個主要方法。還有一個CreateUserRow方法。
在調用CreateAccount方法之前會調用CreateUserRow方法,在我們自己創建的UserProfile或者自動創建的UserTable中插入我們要創建的UserName(當然支持插入其他字段,有個字典類型參數用來傳遞我們自己定義的字段)。
然后調用這個CreateAccount方法在自動創建的webpages_Membership表中插入用戶的注冊名、密碼、時間等詳細信息(41行)。
我們可以清楚地看到我們創建用戶的時候內部是使用的Sql語句執行的。並沒有多么復雜的邏輯,就是在自動創建的成員資格表里進行的增刪改一系列的操作。
而在我們實際使用(簡單方便)的過程中,需要用到這些表映射實體(webpages_Roles、webpages_Memebership等)的地方很少,就算有也可以通過GetUserId,GetAllUser等的一些方法獲取需要的集合或者單值。
其實有時想想,既然微軟幾乎已經實現了我們需要用到的所有功能,那改不改表名已經無所謂了。改了也幾乎用不到了。而且這是內置的權限功能,本身就是作為樣板。
如果是簡單業務,對權限要求不是特別高的,完全可以勝任。
當然對於要求有更完善苛刻權限的,那就必須得自己去實現一套,SimpleMembership里的解釋已經說了,還是有些功能它並沒有實現的,並不適合。
SimpleRoleProvider
先看下MSDN解釋
在 ASP.NET Web Pages 網站中,可以通過使用頁面的 Roles 屬性管理和測試角色。例如,若要確定用戶是否屬於特定角色,可以調用 Roles.IsUserInRole 方法。
根據設計,如在所有 ASP.NET 角色提供程序使用的 RoleProvider 基類中定義的那樣,SimpleRoleProvider 類並不實現可以在 ASP.NET 角色提供程序中實現的所有功能。如果你的網站需要全部角色提供程序功能,則可以跳過 Web Pages 角色系統的初始化(也就是說,不調用 WebSecurity.InitializeDatabaseConnection()),然后啟用標准成員資格和角色提供程序。在這種情況下,對 SimpleRoleProvider 類的調用將傳遞給標准提供程序(在 SimpleRoleProvider 類文檔中稱為“前面的提供程序”)。
在我們最初貼出的第一段代碼(InitializeProviders)里,我們可以看到兩行獲取MembershipProvider和RolePorvider的代碼。
SimpleMembershipProvider simpleMembership = Membership.Provider as SimpleMembershipProvider;
SimpleRoleProvider provider = Roles.Provider as SimpleRoleProvider;
之前已經提到過提供默認的SimpleRoleProvider和SimpleMemberShip。Memebership和Roles都有一個只包含get方法的Provider(對應的MembershipProvider和RoleProvider)屬性。而它們內部的公共靜態方法使用的都調用Provider提供的方法。那么初始化默認就是調用SimpleMembership和SimpleRoleProvider。也就是說下面幾行代碼是等效的。
1 ///直接使用Merbership.Provider 2 SimpleMembershipProvider testProvider = Membership.Provider as SimpleMembershipProvider; 3 testProvider.CreateUserAndAccount(model.UserName, model.Password);
4 //使用WebSecurity 5 WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
6 //如果Membership存在CreateUserAndAccount方法並實現了(其實沒有實現) 7 Membership.CreateUserAndAccount(model.UserName, model.Password);
RoleProvider和MembershipProvider使用方式差不多。只是Membership內部很多方法都沒有實現,而Roles很多需要用到的都實現了。所以一般使用默認的權限需要用到兩個
靜態類。一個是Websecurity,一個是Roles。Roles的具體方法大家可以打開鏈接看,此處就不一一說明了。
MSDN上解釋的這句--不調用 WebSecurity.InitializeDatabaseConnection()),然后啟用標准成員資格和角色提供程序”。在這種情況下,對 SimpleRoleProvider 類的調用將傳遞給標准提供程序(在 SimpleRoleProvider 類文檔中稱為“前面的提供程序”)。是什么意思呢?反編譯!!!!
1 public SimpleRoleProvider(RoleProvider previousProvider) 2 { 3 this._previousProvider = previousProvider; 4 } 5 6 public override void CreateRole(string roleName) 7 { 8 if (!this.InitializeCalled) 9 { 10 this.PreviousProvider.CreateRole(roleName); 11 } 12 else 13 { 14 using (IDatabase database = this.ConnectToDatabase()) 15 { 16 if (FindRoleId(database, roleName) != -1) 17 { 18 throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, WebDataResources.SimpleRoleProvider_RoleExists, new object[] { roleName })); 19 } 20 if (database.Execute("INSERT INTO " + RoleTableName + " (RoleName) VALUES (@0)", new object[] { roleName }) != 1) 21 { 22 throw new ProviderException(WebDataResources.Security_DbFailure); 23 } 24 } 25 } 26 }
1-4行 是SimpleRoleProvider的一個構造方法,可以傳入你自己定制的RoleProvider。
8行 this.InitializeCalled就是判斷是否初始化過SimpleRoleProvider,本文貼出的第一段代碼里面有SimpleMermbership的InitializeCalled設置。
8-26行 你就會蛋疼的發現,它要進行判斷來決定是使用你自己定制的RoleProvider還是SimpleRoleProvider。而只要你不調用Websecurity.InitializeDatabaseConnection
或者指定InitializeCalled=true。它就會調用你實現的相關方法。此處我也有點不理解,如果已經自己定制了,直接自己把工作都做完了,還要麻煩地去new SimpleRoleProvider,太蛋疼了吧!我覺得還是直接new 自己的不就行了,還要繞這么個圈子用SimpleRoleProvider。
總結
我確信我寫的篇幅有點長了(貼的代碼很多,但不貼又不好表述),至少我感覺是這樣。
廢話了這么多,也不知道大家能從中了解到自己想了解的嗎。
呵呵!至少我從頭到尾幾乎都沒有提到如何去實現自己的成員角色控制,這個可能還要看以后在項目中熟練使用了再來分享給大家。
我這里有個鏈接實現自己的MembershipProvider的,大家可以參考下,如果你覺得微軟提供的默認權限不理想的話。
反編譯的感覺真的很不錯,你可以學到很多東西!
本篇文章與上篇都只是稍微詳細的分析了下Asp.net Mvc4的模版權限內容。
我的水平有限,如果文章中有分析不對的地方,還請指出來,大家共同探討進步哈!
我發現了一條菜鳥奔MVC大牛的資料鏈接,在這里分享給大家了。
寫文章不容易,如果對您有用,那就推薦一下吧!