ASP.NET MVC 5 Identity:实现基于组的权限管理(第一部分)
为您的 ASP.NET MVC 应用程序添加基本的基于组的权限管理。适用于复杂度适中的应用程序,这些应用程序在授权权限方面需要更高的粒度,但又不足以迁移到像 Active Directory 这样重量级的解决方案。
在最近的几篇文章中,我们探讨了使用和扩展 ASP.NET Identity 系统的各种方法。我们涵盖了配置数据库连接和使用 Identity 系统使用的 EF Code-First 方法的基础知识,扩展核心 IdentityUser 类以添加我们自己的自定义属性和行为(例如电子邮件地址、名字/姓氏等)。在此过程中,我们还研究了利用 ASP.NET Identity 开箱即用的基本基于角色的帐户管理。
在上一篇文章中,我们弄清楚了如何扩展 IdentityRole 类,这比扩展 IdentityUser 所需的要复杂一些。
Image by Shaun Dunmall | Some Rights Reserved
在这里,我们将更进一步,在核心 ASP.NET Identity 系统提供的基本用户/角色模型之上,构建一个更高级的“权限管理”模型。
- 精细化授权权限管理 - 最低权限原则
- 折衷的解决方案
- 基于先前工作 - 克隆源代码
- 添加组模型
- 更新 ApplicationDbContext 以反映新模型
- 将组管理项添加到 Identity Manager
- 更新 Migrations 配置文件以填充数据库
- 运行 Migrations 并构建数据库
- 下一步:第二部分 控制器、视图和视图模型
注意:如果您使用的是 2014 年 3 月 20 日发布的 Identity 2.0 RTM,则存在一些与此项目不兼容的重大更改。如果您使用的是 Visual Studio 的 MVC 项目模板中当前标准的 Identity 1.0 版本,一切都会正常。我计划在不久的将来发布一篇详细介绍如何使用 Identity 2.0 实现相同功能的文章。
更新 2014/8/11:关于 Identity 2.0,请参阅 ASP.NET Identity 2.0:实现基于组的权限管理
在我们深入探讨之前,值得一提的是,实现一个复杂的权限管理系统并非易事。虽然我们即将研究的模型并不算太难,但在 Web 应用程序的上下文中管理大量精细的权限可能会很复杂。在将此类内容投入生产环境之前,您需要仔细思考并做好计划。
通过仔细的前期规划和精心设计的权限结构,您应该能够为您的站点在臃肿、复杂且痛苦的企业级解决方案(如 Active Directory 或 Windows 身份验证)与开箱即用的过于简单的身份管理之间找到一个折衷的方案。
显然,如果您的安全需求变得过于复杂,可能就需要考虑替代的权限管理系统了。此外,正如 Reddit 上的一位评论者所指出的,ClaimsIdentity
和 ClaimsAuthorizationManager
可能提供额外的选项。有关更多信息,请参阅 使用 .NET 4.5 的声明式安全超越用户名和角色。最后,Identity 2.0 的预览版有望提供额外的灵活性,包括两阶段身份验证。
稍后会详细介绍。首先,一些背景信息。
精细化授权权限管理 - 最低权限原则
良好的安全设计(除其他外)基于最低权限原则。也就是说,“在计算环境的特定抽象层中,每个模块(例如进程、用户或取决于主体的程序)只能访问其合法目的所需的信息和资源”
正如我们现在所知,在 ASP.NET MVC 应用程序中管理对不同功能的访问的主要方式是使用 [Authorize]
属性。我们使用 [Authorize]
装饰特定的控制器方法,并定义哪些角色可以执行该方法。例如,我们可能正在为一家公司构建一个网站。该网站可能包含许多运营或业务领域,如站点管理、人力资源、销售、订单处理等。
一个假设的 PayrollController
可能包含以下方法(除其他外)
假设的 Payroll Controller 的方法
[Authorize(Roles = "HrAdmin, CanEnterPayroll")]
[HttpPost]
public ActionResult EnterPayroll(string id)
{
// . . . Enter some payroll . . .
}
[Authorize(Roles = "HrAdmin, CanEditPayroll, CanProcessPayroll")]
[HttpPost]
public ActionResult EditPayroll(string id)
{
// . . . Edit existing payroll entries . . .
}
[Authorize(Roles = "HrAdmin, CanProcessPayroll")]
[HttpPost]
public ActionResult ProcessPayroll(string id)
{
// . . . Process payroll and cut checks . . .
}
我们从上述信息推断,仅负责输入工资信息的基层员工无权编辑系统中已有的工作。另一方面,公司中有一些人可能需要能够编辑现有的工资信息,这可能包括特定员工部门的经理、人力资源经理本人以及负责处理工资的人员。
实际处理工资单和创建支票以进行支付的操作受到严格限制。只有人力资源经理以及“ProcessPayroll”角色的成员才能执行此操作,我们可以假设他们的人数很少。
最后,我们看到 HrAdmin 角色拥有广泛的权限,包括所有这些功能,并且可能还能够充当人力资源应用程序域内的管理员,为域内的各个用户分配这些以及其他域权限。
Identity 下应用程序授权的局限性
在当前 Identity 系统开箱即用的实现中(即使我们通过最近几篇文章进行了扩展),我们有用户和角色。作为我们安全设置的一部分,用户被分配到一个或多个角色,管理员可以添加或删除用户到各种角色。
应用程序功能对角色的访问通过 [Authorize]
属性硬编码到我们的应用程序中。因此,在生产环境中创建和修改角色几乎没有价值,除非我们实施了其他业务原因。
此外,在当前系统下,每次我们将新用户添加到系统时,都需要为用户分配特定的角色。如果我们的站点包含(例如)“管理员”、“作者”和“用户”,这并不是什么大问题。但是,对于更复杂的站点,包含多个业务领域和多个用户担任多个角色,这可能会变得痛苦。
当安全管理变得痛苦时,我们倾向于采取节省时间的行为,例如忽略最低权限原则,而是授予用户广泛的权限,以便我们不必费心(至少,如果我们没有一个勤奋的系统管理员!)。
折衷的解决方案
在本文中,我们考察了一种扩展 Identity 模型以形成折衷解决方案的方法。适用于复杂度适中的应用程序,这些应用程序在授权权限方面需要更高的粒度,但又不足以迁移到像 Active Directory 这样重量级的解决方案。
我建议在 Identity 组合中添加类似授权组的功能。组被分配各种权限组合,用户被分配到一个或多个组。
为此,我们将创建一个轻微的“幻觉”。我们将把当前我们识别为角色的东西,视为权限。然后,我们将创建这些“角色-权限”的组,并将用户分配到一个或多个组。当然,在后台,我们仍然受限于 Identity 的基本要素:用户和角色。我们还受到必须在 [Authorize]
属性中硬编码“权限”的限制。但是,我们现在可以相对精细地定义这些“角色-权限”,因为对用户分配角色权限的管理将通过分配用户到组来完成,届时该用户将承担每个特定组的所有特定权限。
基于先前工作
我以我们迄今为止构建的基础为起点,从上一篇文章中克隆了项目,在该项目中我们通过继承 IdentityRole
来扩展了我们的角色。在那篇文章中,我们没有做什么惊天动地的事情,但我们更仔细地研究了如何覆盖 ApplicationDbContext
的 OnModelCreating()
方法,并在不损害 ASP.NET 团队创建的底层安全机制的情况下,按照我们的意愿调整 EF 和 Identity 框架。
您可以选择做同样的事情,并跟随我们一起构建,或者您可以克隆本文的最终源代码。
从 Github 获取原始源代码
获取本文的完整源代码
与之前的文章一样,我克隆了初始源代码项目后,重命名了解决方案文件、命名空间、目录和项目文件,因为在本例中,我将将其作为一个新项目推送,而不是作为对旧项目的更改。
接下来,删除现有的 Migrations 文件(但不要删除Migrations 文件夹和Configuration.cs 文件)。我们将在构建数据库之前向代码优先模型添加内容,因此我们不再需要这些文件了。
现在,我们准备开始。
添加 Group 和 ApplicationRoleGroup 模型
首先,当然,我们需要我们的 Group
类。Group
类将代表一组命名的角色,因此我们认为 Group
类拥有一系列角色。但是,由于每个 Group
可以包含零个或多个角色,并且每个角色也可以属于零个或多个组,因此这将是我们在数据库中的多对多映射。因此,我们首先需要一个中间对象 ApplicationRoleGroup
,它映射多对多关系中的外键。
将以下类添加到Models 文件夹
Application Role Group 模型类
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
namespace AspNetGroupBasedPermissions.Models
{
public class ApplicationRoleGroup
{
public virtual string RoleId { get; set; }
public virtual int GroupId { get; set; }
public virtual ApplicationRole Role { get; set; }
public virtual Group Group { get; set; }
}
}
然后将 Group
类添加为Models 中的另一个新类
Group 类
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
namespace AspNetGroupBasedPermissions.Models
{
public class Group
{
public Group() {}
public Group(string name) : this()
{
this.Roles = new List<ApplicationRoleGroup>();
this.Name = name;
}
[Key]
[Required]
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<ApplicationRoleGroup> Roles { get; set; }
}
}
接下来,我们需要为 ApplicationUser
和 Group
创建类似的 Many-to-Many 映射模型。同样,每个用户可以属于零个或多个组,每个组可以包含零个或多个用户。我们已经有了 ApplicationUser
类(尽管需要对其进行一些修改),但我们需要一个 ApplicationUserGroup
类来完成映射。
添加 ApplicationUserGroup 模型
将 ApplicationUserGroup
类添加到 Models 文件夹
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
namespace AspNetGroupBasedPermissions.Models
{
public class ApplicationUserGroup
{
[Required]
public virtual string UserId { get; set; }
[Required]
public virtual int GroupId { get; set; }
public virtual ApplicationUser User { get; set; }
public virtual Group Group { get; set; }
}
}
接下来,我们需要向 ApplicationUser
添加一个 Groups 属性,以便 Entity Framework 能够理解并在使用该属性时使用它来填充组。这意味着我们需要添加一个虚拟属性,当访问该属性时,它返回一个 ICollection<ApplicationUserGroup>
的实例。
按如下方式修改现有的 ApplicationUser 类
修改后的 ApplicationUser 类
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
namespace AspNetGroupBasedPermissions.Models
{
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
: base()
{
this.Groups = new HashSet<ApplicationUserGroup>();
}
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string Email { get; set; }
public virtual ICollection<ApplicationUserGroup> Groups { get; set; }
}
}
更新 ApplicationDbContext 以反映新模型
现在我们已经对模型进行了一些扩展,我们需要更新 ApplicationDbContext
的 OnModelCreating
方法,以便 EF 可以正确地为我们的数据库建模,并处理我们的对象。
** 整个方法会变得有些混乱和杂乱,但仔细阅读代码可以了解其大体内容。不必过于担心理解这段代码的细节——只需尝试大致了解它是如何将模型实体映射到数据库表的。 **
如果您想更好地了解 Identity(或其他尚未公开的 .NET 源代码)的内部工作原理,可以查看 Telerik 的 JustDecompile(免费)或 Red Gate 的 Reflector(用户免费,但现在“免费试用”)。这些优秀的逆向工程工具都能让您深入了解代码下方发生的事情。我就是这样弄清楚这部分的。
按如下方式更新 ApplicationDbContext
的 OnModelCreating()
方法
修改后的 ApplicationDbContext 的 OnModelCreating 方法
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
if (modelBuilder == null)
{
throw new ArgumentNullException("modelBuilder");
}
// Keep this:
modelBuilder.Entity<IdentityUser>().ToTable("AspNetUsers");
// Change TUser to ApplicationUser everywhere else - IdentityUser
// and ApplicationUser essentially 'share' the AspNetUsers Table in the database:
EntityTypeConfiguration<ApplicationUser> table =
modelBuilder.Entity<ApplicationUser>().ToTable("AspNetUsers");
table.Property((ApplicationUser u) => u.UserName).IsRequired();
// EF won't let us swap out IdentityUserRole for ApplicationUserRole here:
modelBuilder.Entity<ApplicationUser>()
.HasMany<IdentityUserRole>((ApplicationUser u) => u.Roles);
modelBuilder.Entity<IdentityUserRole>()
.HasKey((IdentityUserRole r) =>
new { UserId = r.UserId, RoleId = r.RoleId }).ToTable("AspNetUserRoles");
// Add the group stuff here:
modelBuilder.Entity<ApplicationUser>()
.HasMany<ApplicationUserGroup>((ApplicationUser u) => u.Groups);
modelBuilder.Entity<ApplicationUserGroup>()
.HasKey((ApplicationUserGroup r) =>
new { UserId = r.UserId, GroupId = r.GroupId }).ToTable("ApplicationUserGroups");
// And here:
modelBuilder.Entity<Group>()
.HasMany<ApplicationRoleGroup>((Group g) => g.Roles);
modelBuilder.Entity<ApplicationRoleGroup>()
.HasKey((ApplicationRoleGroup gr) =>
new { RoleId = gr.RoleId, GroupId = gr.GroupId }).ToTable("ApplicationRoleGroups");
// And Here:
EntityTypeConfiguration<Group> groupsConfig = modelBuilder.Entity<Group>().ToTable("Groups");
groupsConfig.Property((Group r) => r.Name).IsRequired();
// Leave this alone:
EntityTypeConfiguration<IdentityUserLogin> entityTypeConfiguration =
modelBuilder.Entity<IdentityUserLogin>().HasKey((IdentityUserLogin l) =>
new { UserId = l.UserId, LoginProvider = l.LoginProvider, ProviderKey =
l.ProviderKey }).ToTable("AspNetUserLogins");
entityTypeConfiguration.HasRequired<IdentityUser>((IdentityUserLogin u) => u.User);
EntityTypeConfiguration<IdentityUserClaim> table1 =
modelBuilder.Entity<IdentityUserClaim>().ToTable("AspNetUserClaims");
table1.HasRequired<IdentityUser>((IdentityUserClaim u) => u.User);
// Add this, so that IdentityRole can share a table with ApplicationRole:
modelBuilder.Entity<IdentityRole>().ToTable("AspNetRoles");
// Change these from IdentityRole to ApplicationRole:
EntityTypeConfiguration<ApplicationRole> entityTypeConfiguration1 =
modelBuilder.Entity<ApplicationRole>().ToTable("AspNetRoles");
entityTypeConfiguration1.Property((ApplicationRole r) => r.Name).IsRequired();
}
接下来,我们需要在 ApplicationDbContext
上显式添加一个 Groups
属性。同样,这需要是一个虚拟属性,但在这种情况下,返回类型是 ICollection<Group>
将 Groups 属性添加到 ApplicationDbcontext
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
// Add an instance IDbSet using the 'new' keyword:
new public virtual IDbSet<ApplicationRole> Roles { get; set; }
// ADD THIS:
public virtual IDbSet<Group> Groups { get; set; }
public ApplicationDbContext()
: base("DefaultConnection")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Code we just added above is here . . .
}
// Etc . . .
}
将组管理项添加到 Identity Manager
现在,让我们添加一些代码来帮助我们管理与组相关的各种功能,以及与组相关的用户和角色的管理(“权限”)。仔细查看下面的方法,了解大多数情况下发生了什么,因为对组执行的某些操作可能会对安全范围产生潜在影响。
例如,当我们删除一个组时,我们还需要
- 将所有用户从组中删除。请记住,这里有一个中间表或“关系表”的外键关系——需要先删除相关记录,否则我们通常会收到键约束错误。
- 将该组中的所有角色删除。请记住,这里有一个中间表或“关系表”的外键关系——需要先删除相关记录,否则我们通常会收到键约束错误。
- 从每个用户中删除角色,除非该用户由于属于另一个组而具有相同的角色(这很难思考!)。
同样,当我们向组添加一个角色(“权限”)时,我们需要更新该组中的所有用户以反映添加的权限。
将以下方法添加到现有的 IdentityManager
类的底部
将 Group 方法添加到 Identity Manager 类
public void CreateGroup(string groupName)
{
if (this.GroupNameExists(groupName))
{
throw new System.Exception("A group by that name already exists in the database. Please choose another name.");
}
var newGroup = new Group(groupName);
_db.Groups.Add(newGroup);
_db.SaveChanges();
}
public bool GroupNameExists(string groupName)
{
var g = _db.Groups.Where(gr => gr.Name == groupName);
if (g.Count() > 0)
{
return true;
}
return false;
}
public void ClearUserGroups(string userId)
{
this.ClearUserRoles(userId);
var user = _db.Users.Find(userId);
user.Groups.Clear();
_db.SaveChanges();
}
public void AddUserToGroup(string userId, int GroupId)
{
var group = _db.Groups.Find(GroupId);
var user = _db.Users.Find(userId);
var userGroup = new ApplicationUserGroup()
{
Group = group,
GroupId = group.Id,
User = user,
UserId = user.Id
};
foreach (var role in group.Roles)
{
_userManager.AddToRole(userId, role.Role.Name);
}
user.Groups.Add(userGroup);
_db.SaveChanges();
}
public void ClearGroupRoles(int groupId)
{
var group = _db.Groups.Find(groupId);
var groupUsers = _db.Users.Where(u => u.Groups.Any(g => g.GroupId == group.Id));
foreach (var role in group.Roles)
{
var currentRoleId = role.RoleId;
foreach (var user in groupUsers)
{
// Is the user a member of any other groups with this role?
var groupsWithRole = user.Groups
.Where(g => g.Group.Roles
.Any(r => r.RoleId == currentRoleId)).Count();
// This will be 1 if the current group is the only one:
if (groupsWithRole == 1)
{
this.RemoveFromRole(user.Id, role.Role.Name);
}
}
}
group.Roles.Clear();
_db.SaveChanges();
}
public void AddRoleToGroup(int groupId, string roleName)
{
var group = _db.Groups.Find(groupId);
var role = _db.Roles.First(r => r.Name == roleName);
var newgroupRole = new ApplicationRoleGroup()
{
GroupId = group.Id,
Group = group,
RoleId = role.Id,
Role = (ApplicationRole)role
};
group.Roles.Add(newgroupRole);
_db.SaveChanges();
// Add all of the users in this group to the new role:
var groupUsers = _db.Users.Where(u => u.Groups.Any(g => g.GroupId == group.Id));
foreach (var user in groupUsers)
{
if(!(_userManager.IsInRole(user.Id, roleName)))
{
this.AddUserToRole(user.Id, role.Name);
}
}
}
public void DeleteGroup(int groupId)
{
var group = _db.Groups.Find(groupId);
// Clear the roles from the group:
this.ClearGroupRoles(groupId);
_db.Groups.Remove(group);
_db.SaveChanges();
}
现在我们已经拥有了在后端管理用户、组和角色(“权限”)之间关系所需的核心代码。现在我们需要设置我们的 Migrations 配置文件,以便在运行 EF Migrations 时正确地填充数据库。
更新 Migrations 配置文件以填充数据库
大部分基本模型现在已到位,我们可以运行 EF Migrations 并构建我们的修改后的数据库。但在那之前,我们要更新我们的 Migrations Configuration
类,以便我们在运行 EF Migrations 时用最少量的必需数据来填充数据库。请记住,我们的网站不允许“公共”注册。因此,至少我们需要像以前一样,用一个初始管理员级别的用户来填充它。
与以前不同的是,我们改变了分配和管理角色的方式。今后,我们需要用一个或多个初始组来填充我们的初始用户,并用至少一个具有足够管理员权限的组来填充,这样我们的初始用户就可以在此基础上进行操作。
此代码的编写方式有很多种。此外,根据您的应用程序要求,数据库的填充方式可能会成为一项广泛的计划工作(还记得那个关于更复杂的授权模型需要更多前期规划的说法吗?)。
在这里,我们将更新我们的 Configuration
类,并添加一些新方法。我们将添加一个初始用户、一些可能使用的组,以及一些与管理安全和授权相关的角色。
更新的 Migrations 配置文件
internal sealed class Configuration
: DbMigrationsConfiguration<ApplicationDbContext>
{
IdentityManager _idManager = new IdentityManager();
ApplicationDbContext _db = new ApplicationDbContext();
public Configuration()
{
AutomaticMigrationsEnabled = true;
}
protected override void Seed(ApplicationDbContext context)
{
this.AddGroups();
this.AddRoles();
this.AddUsers();
this.AddRolesToGroups();
this.AddUsersToGroups();
}
string[] _initialGroupNames =
new string[] { "SuperAdmins", "GroupAdmins", "UserAdmins", "Users" };
public void AddGroups()
{
foreach (var groupName in _initialGroupNames)
{
_idManager.CreateGroup(groupName);
}
}
void AddRoles()
{
// Some example initial roles. These COULD BE much more granular:
_idManager.CreateRole("Admin", "Global Access");
_idManager.CreateRole("CanEditUser", "Add, modify, and delete Users");
_idManager.CreateRole("CanEditGroup", "Add, modify, and delete Groups");
_idManager.CreateRole("CanEditRole", "Add, modify, and delete roles");
_idManager.CreateRole("User", "Restricted to business domain activity");
}
string[] _superAdminRoleNames =
new string[] { "Admin", "CanEditUser", "CanEditGroup", "CanEditRole", "User" };
string[] _groupAdminRoleNames =
new string[] { "CanEditUser", "CanEditGroup", "User" };
string[] _userAdminRoleNames =
new string[] { "CanEditUser", "User" };
string[] _userRoleNames =
new string[] { "User" };
void AddRolesToGroups()
{
// Add the Super-Admin Roles to the Super-Admin Group:
var allGroups = _db.Groups;
var superAdmins = allGroups.First(g => g.Name == "SuperAdmins");
foreach (string name in _superAdminRoleNames)
{
_idManager.AddRoleToGroup(superAdmins.Id, name);
}
// Add the Group-Admin Roles to the Group-Admin Group:
var groupAdmins = _db.Groups.First(g => g.Name == "GroupAdmins");
foreach (string name in _groupAdminRoleNames)
{
_idManager.AddRoleToGroup(groupAdmins.Id, name);
}
// Add the User-Admin Roles to the User-Admin Group:
var userAdmins = _db.Groups.First(g => g.Name == "UserAdmins");
foreach (string name in _userAdminRoleNames)
{
_idManager.AddRoleToGroup(userAdmins.Id, name);
}
// Add the User Roles to the Users Group:
var users = _db.Groups.First(g => g.Name == "Users");
foreach (string name in _userRoleNames)
{
_idManager.AddRoleToGroup(users.Id, name);
}
}
// Change these to your own:
string _initialUserName = "jatten";
string _InitialUserFirstName = "John";
string _initialUserLastName = "Atten";
string _initialUserEmail = "jatten@typecastexception.com";
void AddUsers()
{
var newUser = new ApplicationUser()
{
UserName = _initialUserName,
FirstName = _InitialUserFirstName,
LastName = _initialUserLastName,
Email = _initialUserEmail
};
// Be careful here - you will need to use a password which will
// be valid under the password rules for the application,
// or the process will abort:
_idManager.CreateUser(newUser, "Password1");
}
// Configure the initial Super-Admin user:
void AddUsersToGroups()
{
var user = _db.Users.First(u => u.UserName == _initialUserName);
var allGroups = _db.Groups;
foreach (var group in allGroups)
{
_idManager.AddUserToGroup(user.Id, group.Id);
}
}
}
如上所示,我(相当随意地)决定设置一些与用户/组/角色域相关的初始组和角色。如果我们已经知道应用程序其余部分的域结构,我们可能希望将其他角色(“权限”)作为我们配置的一部分,因为角色需要使用 [Authorize]
属性硬编码到我们的控制器中。我们越早确定我们应用程序安全模型的角色结构,就越好。然而,您需要在此处仔细权衡粒度和可管理性。
目前,我们已经有了足够好的起点,可以运行 EF Migrations 并查看我们的数据库是否已成功构建。
运行 Migrations 并构建数据库
如前所述,在执行此操作时,我删除了之前的 Migration 文件,但保留了 Migrations 文件夹(以及修改后的 Configuration.cs 文件)。因此,为了执行迁移,我只需在 Package Manager Console 中输入以下内容:
添加新迁移
PM> Add-Migration init
这会生成一个新的迁移。接下来
构建数据库
PM> Update-Database
如果一切顺利,我们应该能够打开 Visual Studio Server Explorer 中的数据库,看看我们做得怎么样。您应该会看到类似以下的内容
VS Server Explorer 中的数据库
看起来一切顺利!
下一步:控制器、视图模型和视图
本文篇幅过长,所以我决定将其分成两部分。在本文中,我们弄清楚了如何在我们的应用程序中建模用户、组和角色(“权限”),并通过 EF Code-First 和 Migrations 在数据库中进行扩展。
接下来,我们将开始将所有这些内容整合到我们应用程序的业务部分。
下一步:第二部分 - 控制器、视图模型和视图 --->
其他资源和感兴趣的项目
- 本文的源代码在 Github 上
- ASP.NET MVC 5 Identity:扩展和修改角色
- 扩展 ASP.NET MVC 5 中的身份账户并实现基于角色的认证
- ASP.NET MVC 显示一个 HTML 表格,其中包含用于选择行项目的复选框