前言
菜鸟去重复之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 + " ( UserId int NOT NULL PRIMARY KEY, CreateDate datetime , ConfirmationToken nvarchar(128) , IsConfirmed bit DEFAULT 0, LastPasswordFailureDate datetime , PasswordFailuresSinceLastSuccess int NOT NULL DEFAULT 0, Password nvarchar(128) NOT NULL, PasswordChangedDate datetime , PasswordSalt nvarchar(128) NOT NULL, PasswordVerificationToken nvarchar(128) , 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大牛的资料链接,在这里分享给大家了。
写文章不容易,如果对您有用,那就推荐一下吧!