注:本文系作者原創,但可隨意轉載。
最近做一個Web平台系統,系統包含3個角色,“管理員, 企業用戶, 評審專家”, 分別有不同的功能。一直以來都是使用微軟封裝好的Microsoft.AspNet.Identity.dll程序集來進行身份驗證和角色控制。
在MVC項目中,生成項目結構中,甚至已經包含了創建好的AccountController,可以直接使用進行賬號管理。不過最近一次使用Identity功能,是在Visual Studio 2013的Preview版本中,現在升級到了RC和Ultimate版,整個程序集已經徹底翻新了。想進行角色控制,我了個擦發現Account控制器中初始化的一個控制類UserManager已經根本不包含角色控制的方法了。。。於是乎,開始上網查資料,搜索"ASP.NET Identity Role(Manager)",但發現大部分都是零幾年的文章了,微軟官方博客里倒是看到了相關的講解,很詳細,但尼瑪是Preview版本的啊,后面有人追問正式版的也沒有答復。於是懶得再找了,打開dotPeek對整個程序集進行反編譯,看一下內部代碼到底是如何實現角色控制的。
先看一下本文問題背景的具體情況。下面有一段代碼介紹了AccountController的構造方法,其中的LCDEUser,LCDEDbContext是我定義的繼承了IdentityUser和DbContext的應用程序的賬號實體類和數據庫上下文類,由於Identity本身是基於EntityFramework來實現的。這里就不再介紹EF,假定您對EF已經了解。Account控制器的構造方法中初始化了一個UserManager管理類,這個類可以進行創建賬號,修改密碼。。。等各種賬號管理功能,還有一個方法叫AddToRole(..),意思是把一個賬號(User)與一個角色(Role)進行關聯,關鍵是現在根本就不存在任何角色,UserManager類也不提供創建角色的方法。

1 public class AccountController : Controller 2 { 3 public AccountController() 4 : this(new UserManager<LCDEUser>(new UserStore<LCDEUser>(new LCDEDbContext()))) 5 { 6 } 7 8 public AccountController(UserManager<LCDEUser> userManager) 9 { 10 UserManager = userManager; 11 } 12 13 public UserManager<LCDEUser> UserManager { get; private set; } 14 }
Identity功能主要由3個程序集組成。Microsoft.AspNet.Identity.Core / EntityFramework / Owin 其中Owin和第三方登陸相關,此處我暫時用不到就先不看了。
用反編譯工具打開程序集后如圖1。
圖1 圖2
圖2中是Identity自動生成的幾張數據庫表,看起來和左邊程序中的幾個類名一致。打開一看果然 左側程序集中的IdentityRole, IdentityUser, IdentityUserClaim, IdentityUserLogin, IdentityUserRole分別是實體模型類,包含的屬性和數據庫中的表的字段一致。其中的EntityStore作為泛型類提供了幾個基本操作Create,Delete,GetById。 RoleStore<T>用來操作和角色控制相關的實體數據,UserStore<T>用來操作和賬號相關的實體數據,其中提供了諸多的方法,而這兩個類的訪問權限又是Public的,可以認為只是使用這兩個類也可以進行一些數據實體的訪問和存儲操作。
圖3
圖3是使用反編譯工具打開Core.dll后看到的內部構造。其中東西頗多不一一介紹,主要介紹一下我們要用到的。打開后發現其中有一個UserManager類,這不正是我們用來進行賬號管理的類么,還有一個RoleManager類,肯定就是進行角色控制的了,可是沒有相關文檔,項目的初始架構中又沒有幫我們初始化這個東西,而它的構造函數又有點復雜(主要是傳入參數中還要new 一些RoleStore, DbContext之類的東西),不打開看看相關代碼或文檔,想要直接使用還是有點困難撒,主要是萬一用錯了折騰起來太費時間。
打開RoleManager類后看到這么一段代碼:

1 public class RoleManager<TRole> : IDisposable where TRole : IRole 2 { 3 private bool _disposed; 4 private IIdentityValidator<TRole> _roleValidator; 5 6 protected IRoleStore<TRole> Store { get; private set; } 7 8 public IIdentityValidator<TRole> RoleValidator 9 { 10 get 11 { 12 return this._roleValidator; 13 } 14 set 15 { 16 if (value == null) 17 throw new ArgumentNullException("value"); 18 this._roleValidator = value; 19 } 20 } 21 22 public RoleManager(IRoleStore<TRole> store) 23 { 24 if (store == null) 25 throw new ArgumentNullException("store"); 26 this.Store = store; 27 this.RoleValidator = (IIdentityValidator<TRole>) new RoleValidator<TRole>(this); 28 } 29 }
它的構造函數需要傳遞一個RoleStore進去,實際上RoleManager就是提供了比RoleStore更多方法,方法名稱更具可讀性,更方便編程的一個類,因此不推薦直接使用RoleStore來進行數據的訪問及存儲。而RoleStore的構造方法需要傳遞一個DbContext給它,搞清楚怎么初始化它,就可以正式使用了。下面參考一段,對數據庫進行初始化,建立種子數據的代碼,其中包括的對UserManager和RoleManager的具體使用。

1 public class LCDEDbInitializer : DropCreateDatabaseIfModelChanges<LCDEDbContext> 2 { 3 protected override void Seed(Models.LCDEDbContext context) 4 { 5 using (var userManager = new UserManager<LCDEUser>(new UserStore<LCDEUser>(context))) 6 { 7 using (var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(context))) 8 { 9 // 添加系統角色 10 if (!roleManager.RoleExists("admin")) 11 { 12 roleManager.Create(new IdentityRole("admin")); 13 } 14 if (!roleManager.RoleExists("expert")) 15 { 16 roleManager.Create(new IdentityRole("expert")); 17 } 18 if (!roleManager.RoleExists("enterprise")) 19 { 20 roleManager.Create(new IdentityRole("enterprise")); 21 } 22 // 創建賬號 23 var user = new LCDEUser() { UserName = "admin", Name = "admin", Phone = "00000000000" }; 24 if (userManager.Create(user, "000000") != IdentityResult.Success) 25 { 26 throw new Exception("初始化系統管理員賬號失敗"); 27 } 28 var expert = new LCDEUser() { UserName = "expert", Name = "專家", Phone = "15888888888" }; 29 if (userManager.Create(expert, "000000") != IdentityResult.Success) 30 { 31 throw new Exception("初始化專家賬號失敗"); 32 } 33 var enterprise = new LCDEUser() { UserName = "enterprise", Name = "XXXXXXXX科技有限公司", Phone = "15888888888" }; 34 if (userManager.Create(enterprise, "000000") != IdentityResult.Success) 35 { 36 throw new Exception("初始化企業賬號失敗"); 37 } 38 // 為賬號分配角色 39 userManager.AddToRole(user.Id, "admin"); 40 userManager.AddToRole(expert.Id, "expert"); 41 userManager.AddToRole(enterprise.Id, "enterprise"); 42 43 context.SaveChanges(); 44 base.Seed(context); 45 } 46 } 47 } 48 }
在使用中還有一些其他需要注意的事項,在MVC中,通常我會做一個BaseController,其中包含了dbcontext,usermanager的實例創建, 假如首先實例化一個dbcontext,再去實例化一個usermanager,由於usermanager的實例化需要傳入一個dbcontext,我嘗試將實例化好的dbcontext實體傳入,在本機運行時,沒有問題,但發布到服務器上時,會出現DataReader的使用沖突,於是在usermanager進行構造時,要傳入一個new DbContext()給它。