因为是转载文章 在此标明出处,以前有文章是转的没标明的请谅解,因为有些已经无法找到出处,或者与其它原因。
如有冒犯请联系本人,或删除,或标明出处。
因为好的文章,以前只想收藏,但连接有时候会失效,所以现在碰到好的直接转到自己这里。
前言
上篇的菜鸟去重复之Sql的问题还没有得到满意的答案。如果哪位大哥有相关的资料解释,能够分享给我,那就太谢谢了。
以后每发表一篇博文我都会将以前遗留的问题在前言里指出,直到解决为止。
本文主要在于探讨一下Asp.net Mvc4默认生成的权限的详细内容。
介绍
在VS2012中创建一个默认的带有权限的MVC4 Internet项目,如下图。
生成项目后点运行,在浏览器里点登陆。然后观察项目,此刻生成了数据库,如下。
本文就是针对这样的模版项目里的已有权限全面的分析,希望大家能够从中学到一些东西,如果有问题,请指出。PS:欢迎大家共同讨论进步。
有趣的Attribute
上图中有三个ActionFilter,不清楚ActionFilter的童鞋请点击这里。
AuthorizeAttribute:表示一个特性,该特性用于限制调用方对操作方法的访问。
AllowAnonymousAttribute:表示一个特性,该特性用于标记在授权期间要跳过 AuthorizeAttribute 的控制器和操作。
InitializeSimpleMembershipAttribute:这个特性是来初始化数据库成员关系的,后面会讲到。
Authorize
这个Attribute是用来对用户角色权限限制的,大家应该都清楚,此处就不多说了。
AllowAnonymous
这个Attribute似乎是MVC4才加进去的。大致意思看一下上面的解释应该也了解了。其实就是微软团队在Authorize的实现里面加了对AllowAnonymous的判断,如果方法上有这个Attribute就不限制。
真相在这里(反编译的),这个是Authorize的OnAuthorization方法,有兴趣的童鞋可以看看12行。
1 public virtual void OnAuthorization(AuthorizationContext filterContext)
2 {
3 if (filterContext == null)
4 {
5 throw new ArgumentNullException("filterContext");
6 }
7 if (OutputCacheAttribute.IsChildActionCacheActive(filterContext))
8 {
9 throw new InvalidOperationException(MvcResources.AuthorizeAttribute_CannotUseWithinChildActionCache);
10 }
11 bool inherit = true;
12 if (!filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit) && !filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true))
13 {
14 if (this.AuthorizeCore(filterContext.HttpContext))
15 {
16 HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache;
17 cache.SetProxyMaxAge(new TimeSpan(0L));
18 cache.AddValidationCallback(new HttpCacheValidateHandler(this.CacheValidateHandler), null);
19 }
20 else
21 {
22 this.HandleUnauthorizedRequest(filterContext);
23 }
24 }
25 }
下面就到了主要的关于默认权限的关键Attribute。
InitializeSimpleMembership
我们先来看下它的实现
1 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
2 public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
3 {
4 private static SimpleMembershipInitializer _initializer;
5 private static object _initializerLock = new object();
6 private static bool _isInitialized;
7
8 public override void OnActionExecuting(ActionExecutingContext filterContext)
9 {
10 // Ensure ASP.NET Simple Membership is initialized only once per app start
11 LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
12 }
13
14 private class SimpleMembershipInitializer
15 {
16 public SimpleMembershipInitializer()
17 {
18 Database.SetInitializer<UsersContext>(null);
19
20 try
21 {
22 using (var context = new UsersContext())
23 {
24 if (!context.Database.Exists())
25 {
26 // Create the SimpleMembership database without Entity Framework migration schema
27 ((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
28 }
29 }
30 WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);
31 }
32 catch (Exception ex)
33 {
34 throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);
35 }
36 }
37 }
38 }
这个Atrribute里面实现了创建我们起初见到的数据库。
现在让我们来看看具体是如何实现的。这个创建数据库的方式还是和以前使用Code First有点出入的。
4-12行
只初始化一次就意味着不会调用SimpleMembershipInitializer的构造函数两次。其实看代码我们应该也大致能猜到,这个构造函数里面主要做的就是创建数据库的功能,当然最好只实现一次。
22-29行
public class UsersContext : DbContext
{
public UsersContext()
: base("DefaultConnection")
{
}
public DbSet<UserProfile> UserProfiles { get; set; }
}
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
}
我们从上面可以了解到UsersContext继承DbContext,并以DefaultConnection字符串为连接字符串,有一张表UserProfile。
那这时我们就会有疑问了,在文章开头介绍里的那几个标出来的表是如何生成的呢。先别急,我们继续往下看
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);
这句话的字面意思一看就猜到了。初始化数据库连接使用连接字符串Default,表UserProfile,字段UserId,UserName,自动创建表。
可是我们之前已经使用UserContext的CreateDatabase()创建过数据库和表了。难道再重新创建一次!!?好吧!还是让我们看看MSDN的解释吧!
InitializeDatabaseConnection()
通过连接到包含用户信息的数据库来初始化成员资格系统。
注释
在应用程序启动时调用此方法(在 _AppStart.cshtml 或 _AppStart.vbhtml 文件中),以初始化简单成员资格系统。此方法验证成员资格数据库是否存在。它还打开到用户配置文件表的连接,并在成员资格数据和用户配置文件数据之间建立 数据库关系。
如果希望使用包含用户配置文件信息(用户名、电子邮件地址等)的数据库表,则应指定成员资格系统用于连接到那些信息的连接字符串和表名称。如果不希望使用现有用户配置文件表,则可指定 InitializeDatabaseConnection()
下面是方法的五个参数解释:
connectionStringName
包含用户信息的数据库的连接字符串的名称。如果使用的是 SQL Server Compact,此名称可以是一个不带 .sdf 文件扩展名的数据库文件(.sdf 文件)的名称。
userTableName
包含用户配置文件信息的数据库表的名称。
userIdColumn
包含用户 ID 的数据库列的名称。此列必须以整数 (int) 形式键入。
userNameColumn
包含用户名的数据库列的名称。此列用于匹配用户配置文件数据与成员资格帐户数据。
autoCreateTables
若为 true,则指示应创建用户配置文件表和成员资格表(如果它们不存在)。若为 false,则指示不应自动创建这些表。虽然可以自动创建成员资格表,但数据库本身必须已经存在。
我的理解
这个方法会对已有的数据库进行分析,如果没有提前创建数据库,调用此方法会报错。
如果已经创建一个空的数据库并且不存在参数对应的userTableName表,设置autoCreateTables为true,会自动创建该参数名的表,字段与userIdColumn及userNameColumn一一对应,并创建其余的四个成员资格表。
如果设置autoCreateTables为false,不会自动创建任何表。
设置autoCreateTables为true,如果已经使用UserContext创建user表,要把表名与UserId、UserName的字段名作为参数赋给InitializeDatabaseConnection()方法作为参数,表名不一致,数据库会多创建一个User表,并以方法参数名的那个来使用webSecurity的方法。字段名不一致,使用webSecurity的方法时会报错。
至此,我们已经大致知道起初见到的那几个数据表是如何生成的了。对,就是webSecurity.InitializeDatabaseConnection方法里自动实现的。
小结
本来打算把默认生成权限的这部分内容尽可能的在一篇里面说完,但写本篇博客的过程中发现需要写的有点多。
奈何我本是菜鸟,内容稍微多了点,就越发感觉脑子有点乱,所以打算分成两篇来写。
呵呵!目前还没有实力写长篇,那得需要思维足够清晰,我还需要锻炼啊!
下篇我打算写一下WebSecurity,SimpleMemberShip,RoleProvider之间的关系,以及它们的使用场合Asp.net Mvc4默认权限详细(下),希望有兴趣的童鞋继续关注!
最后,如果本文有什么问题,还请指出。如果对您有帮助,请帮忙推荐给我更多动力!
前言
菜鸟去重复之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行
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 }
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行
8行 this.InitializeCalled就是判断是否初始化过SimpleRoleProvider,本文贴出的第一段代码里面有SimpleMermbership的InitializeCalled设置。
8-26行
或者指定InitializeCalled=true。它就会调用你实现的相关方法。此处我也有点不理解,如果已经自己定制了,直接自己把工作都做完了,还要麻烦地去new SimpleRoleProvider,太蛋疼了吧!我觉得还是直接new 自己的不就行了,还要绕这么个圈子用SimpleRoleProvider。
总结
我确信我写的篇幅有点长了(贴的代码很多,但不贴又不好表述),至少我感觉是这样。
废话了这么多,也不知道大家能从中了解到自己想了解的吗。
呵呵!至少我从头到尾几乎都没有提到如何去实现自己的成员角色控制,这个可能还要看以后在项目中熟练使用了再来分享给大家。
我这里有个链接实现自己的MembershipProvider的,大家可以参考下,如果你觉得微软提供的默认权限不理想的话。
反编译的感觉真的很不错,你可以学到很多东西!
本篇文章与上篇都只是稍微详细的分析了下Asp.net Mvc4的模版权限内容。
我的水平有限,如果文章中有分析不对的地方,还请指出来,大家共同探讨进步哈!
我发现了一条菜鸟奔MVC大牛的资料链接,在这里分享给大家了。