将ASP.NET标识集成到现有的DbContext中

我正在使用VS2013,.NET 4.5.1中的ASP.NET MVC 5项目,该项目使用Entity Framework 6 Code-First。 我有一个体面的数据库建立和有点工作(项目大约两周)。 我想现在整合用户身份validation,但我不知道如何处理它。 在花费了大部分时间进行研究之后,我决定为新的ASP.NET身份框架提供必须编写自定义成员资格或角色提供程序的机会。 我感到困惑的是如何使用现有的数据库/模型完成所有工作。

目前我有一个名为Employee的对象,它包含基本的员工信息(暂时)。 在整天思考了这个问题之后,我决定将身份validation与它分离成一个User对象,这正是Identity想要的。 这就是说我如何使它全部工作?

这是我的Employee类:

 public class Employee : Person { public int EmployeeId { get; set; } public byte CompanyId { get; set; } public string Name { get { return String.Format("{0} {1}", this.FirstName, this.LastName); } } public string Password { get; set; } public bool IsActive { get; set; } public virtual ICollection
Addresses { get; set; } public virtual Company Company { get; set; } public virtual ICollection Emails { get; set; } public virtual ICollection Phones { get; set; } public Employee() { this.Addresses = new List
(); this.Emails = new List(); this.Phones = new List(); } }

而我的DbContext派生类:

 public class DatabaseContext : DbContext { static DatabaseContext() { Database.SetInitializer(new DatabaseInitializer()); } public DatabaseContext() : base("Name=DatabaseContext") { this.Database.Initialize(true); } public DatabaseContext( string connectionString) : base(connectionString) { this.Database.Initialize(true); } /// DbSets... public override int SaveChanges() { try { return base.SaveChanges(); } catch (DbEntityValidationException e) { IEnumerable errors = e.EntityValidationErrors.SelectMany( x => x.ValidationErrors).Select( x => String.Format("{0}: {1}", x.PropertyName, x.ErrorMessage)); throw new DbEntityValidationException(String.Join("; ", errors), e.EntityValidationErrors); } } protected override void OnModelCreating( DbModelBuilder modelBuilder) { modelBuilder.Ignore(); /// Configs... base.OnModelCreating(modelBuilder); } } 

因此,在花了大约一天阅读和阅读后,我最终构建了自己的Identity实现。 首先,我所做的是获取现有的Employee对象并将其扩展为inheritance自IUserIUser是一个接口,它是Identity 2.0的一部分(当前处于alpha状态),允许将主键类型配置为string以外的其他类型,默认情况下为1.0。 由于我存储数据的方式,我的实现非常具体。 例如, Employee可以有多个与之相关的Email对象,对于我的应用程序,我想使用电子邮件作为用户名。 所以,我只需设置UserName属性即可返回Employee的工作电子邮件:

 public string UserName { get { if (this.WorkEmail != null) { return this.WorkEmail.Address; } return null; } set { /// This property is non-settable. } } 

旁注,因为我不打算使用该属性的setter,除了简单地将它留空之外,还有一种更简洁的方式来淘汰它吗?

继续,我还添加了PasswordHash属性。 我添加了自己的Role对象,inheritance自IRole 。 最后, EmployeeRole对象每个都有一个ICollection链接。 另一方面,Identity的Entity Framework实现手动创建映射表UserRoles而不是利用它自己的配置function,我似乎无法理解它背后的原因。 它创建的UserRole会传递到它实现的*Store ,但除了充当链接之外,它并没有真正做任何特殊的事情。 在我的实现中,我只使用已经建立的链接,当然在数据库中创建了一个映射表,但是没有毫无意义地暴露给应用程序。 我发现它好奇。

继续前进,使用我配置的对象,我继续创建自己的IUserStoreIRoleStore类,创造性地称为EmployeeStoreRoleStore

 public class EmployeeStore : IQueryableUserStore, IUserStore, IUserPasswordStore, IUserRoleStore, IDisposable { private bool Disposed; private IDatabaseRepository RolesRepository { get; set; } private IDatabaseRepository EmployeesRepository { get; set; } public EmployeeStore( IDatabaseRepository rolesRepository, IDatabaseRepository employeesRepository) { this.RolesRepository = rolesRepository; this.EmployeesRepository = employeesRepository; } #region IQueryableUserStore Members public IQueryable Users { get { return this.EmployeesRepository.Set; } } #endregion #region IUserStore Members public async Task CreateAsync( Employee employee) { this.ThrowIfDisposed(); if (employee == null) { throw new ArgumentNullException("employee"); } await this.EmployeesRepository.AddAndCommitAsync(employee); } public async Task DeleteAsync( Employee employee) { this.ThrowIfDisposed(); if (employee == null) { throw new ArgumentNullException("employee"); } await this.EmployeesRepository.RemoveAndCommitAsync(employee); } public Task FindByIdAsync( int employeeId) { this.ThrowIfDisposed(); return Task.FromResult(this.EmployeesRepository.FindSingleOrDefault( u => (u.Id == employeeId))); } public Task FindByNameAsync( string userName) { this.ThrowIfDisposed(); return Task.FromResult(this.EmployeesRepository.FindSingleOrDefault( e => (e.UserName == userName))); } public async Task UpdateAsync( Employee employee) { this.ThrowIfDisposed(); if (employee == null) { throw new ArgumentNullException("employee"); } await this.EmployeesRepository.CommitAsync(); } #endregion #region IDisposable Members public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } protected void Dispose( bool disposing) { this.Disposed = true; } private void ThrowIfDisposed() { if (this.Disposed) { throw new ObjectDisposedException(base.GetType().Name); } } #endregion #region IUserPasswordStore Members public Task GetPasswordHashAsync( Employee employee) { this.ThrowIfDisposed(); if (employee == null) { throw new ArgumentNullException("employee"); } return Task.FromResult(employee.PasswordHash); } public Task HasPasswordAsync( Employee employee) { return Task.FromResult(!String.IsNullOrEmpty(employee.PasswordHash)); } public Task SetPasswordHashAsync( Employee employee, string passwordHash) { this.ThrowIfDisposed(); if (employee == null) { throw new ArgumentNullException("employee"); } employee.PasswordHash = passwordHash; return Task.FromResult(0); } #endregion #region IUserRoleStore Members public Task AddToRoleAsync( Employee employee, string roleName) { this.ThrowIfDisposed(); if (employee == null) { throw new ArgumentNullException("employee"); } if (String.IsNullOrEmpty(roleName)) { throw new ArgumentNullException("roleName"); } Role role = this.RolesRepository.FindSingleOrDefault( r => (r.Name == roleName)); if (role == null) { throw new InvalidOperationException("Role not found"); } employee.Roles.Add(role); return Task.FromResult(0); } public Task> GetRolesAsync( Employee employee) { this.ThrowIfDisposed(); if (employee == null) { throw new ArgumentNullException("employee"); } return Task.FromResult>(employee.Roles.Select( r => r.Name).ToList()); } public Task IsInRoleAsync( Employee employee, string roleName) { this.ThrowIfDisposed(); if (employee == null) { throw new ArgumentNullException("employee"); } if (String.IsNullOrEmpty(roleName)) { throw new ArgumentNullException("roleName"); } return Task.FromResult(employee.Roles.Any( r => (r.Name == roleName))); } public Task RemoveFromRoleAsync( Employee employee, string roleName) { this.ThrowIfDisposed(); if (employee == null) { throw new ArgumentNullException("employee"); } if (String.IsNullOrEmpty(roleName)) { throw new ArgumentNullException("roleName"); } Role role = this.RolesRepository.FindSingleOrDefault( r => (r.Name == roleName)); if (role == null) { throw new InvalidOperationException("Role is null"); } employee.Roles.Remove(role); return Task.FromResult(0); } #endregion } 

RoleStore

 public class RoleStore : IQueryableRoleStore, IRoleStore, IDisposable { private bool Disposed; private IDatabaseRepository RolesRepository { get; set; } public RoleStore( IDatabaseRepository rolesRepository) { this.RolesRepository = rolesRepository; } #region IQueryableRoleStore Members public IQueryable Roles { get { return this.RolesRepository.Set; } } #endregion #region IRoleStore Members public async Task CreateAsync( Role role) { this.ThrowIfDisposed(); if (role == null) { throw new ArgumentNullException("role"); } await this.RolesRepository.AddAndCommitAsync(role); } public async Task DeleteAsync( Role role) { this.ThrowIfDisposed(); if (role == null) { throw new ArgumentNullException("role"); } await this.RolesRepository.RemoveAndCommitAsync(role); } public Task FindByIdAsync( int roleId) { this.ThrowIfDisposed(); return Task.FromResult(this.RolesRepository.FindSingleOrDefault( r => (r.Id == roleId))); } public Task FindByNameAsync( string roleName) { this.ThrowIfDisposed(); return Task.FromResult(this.RolesRepository.FindSingleOrDefault( r => (r.Name == roleName))); } public async Task UpdateAsync( Role role) { this.ThrowIfDisposed(); if (role == null) { throw new ArgumentNullException("role"); } await this.RolesRepository.CommitAsync(); } #endregion #region IDisposable Members public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } protected void Dispose( bool disposing) { this.Disposed = true; } private void ThrowIfDisposed() { if (this.Disposed) { throw new ObjectDisposedException(base.GetType().Name); } } #endregion } 

现在,我注意到entity framework实现正在创建看起来像迷你存储库的东西。 由于我的项目已经在使用我自己的Repository实现,所以我决定改用它。 我们会看到这是怎么回事……

现在,所有这些都有效,并且令人惊讶的是根本没有崩溃,或者至少还没有。 话虽如此,我拥有所有这些精彩的身份实现,但我似乎无法弄清楚如何在我的MVC应用程序中利用它们。 由于这不属于这个问题的范围,我将继续开设一个新问题。

我将此作为问题的答案,以防万一其他人将来遇到这个问题。 当然,如果有人在我发布的代码中看到错误,请告诉我。

请查看SimpleSecurity Project源代码 ,以获取如何扩展ASP.NET Identity的数据库上下文以包含新表的示例。 这可能适合您的情况。 以下是通过从ASP.NET标识上下文inheritance来定义新上下文的方法。

 public class SecurityContext : IdentityDbContext { public SecurityContext() : base("SimpleSecurityConnection") { } public DbSet Resources { get; set; } public DbSet OperationsToRoles { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Configurations.Add(new ResourceConfiguration()); modelBuilder.Configurations.Add(new OperationsToRolesConfiguration()); } } 

SimpleSecurity Project将ASP.NET Identity与MVC应用程序分离并扩展它。

由于您的Employee类似乎是成员资格的用户配置文件,因此我将根据您在ASP.NET身份中自定义用户配置文件的方式来定制它,这将在此处讨论 。 基本上你的Employee类需要inheritanceIdentityUser,你会从Employee中删除Password属性,因为这是在IdentityUser中定义的,框架在那里查找它。 然后在定义上下文时,您将使用Employee类,因此它看起来像这样

 public class DatabaseContext : IdentityDbContext { ... } 

没有一个解决方案适合所有情况,但对于我的项目,我发现最简单的事情是扩展IdentityUserIdentityDbContext类。 下面是伪代码,它专注于您需要更改/添加以使其工作的最低限度。

对于您的用户类:

 public class DomainUser : IdentityUser { public DomainUser(string userName) : base(userName) {} public DomainUser() {} } 

对于您的DbContext实现:

 public class DomainModelContext : IdentityDbContext { public DomainModelContext() : base() {} public DomainModelContext(string nameOrConnectionString) : base(nameOrConnectionString) {} protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); } } 

在Startup.Auth.cs中:

  public static Func> UserManagerFactory { get; set; } static Startup() { UserManagerFactory = () => new UserManager(new UserStore(new DomainModelContext())); } 

另一个可能的选择是在DomainUser类和inheritance自IdentityUser的ApplicationUser类之间创建1-1关系。 这将减少域模型和Identity机制之间的耦合,特别是如果您使用WithRequiredDependent而不创建双向导航属性,如下所示:

 modelBuilder.Entity().HasRequired(au => au.DomainUser).WithRequiredPrincipal();