24

我工作的一個ASP.NET MVC 5項目在VS2013,.NET 4.5.1,使用實體框架6代碼優先。我有一個體面的大小數據庫建立起來,有點工作(項目大約兩週)。我想現在整合用戶認證,但我不知道如何處理它。在花了大量時間研究之後,我決定給新的ASP.NET Identity框架一個寫自定義成員或角色提供者的機會。我感到困惑的是如何使它與現有的數據庫/模型一起工作。集成ASP.NET身份進入現有的DbContext

目前,我有叫Employee對象保存基本員工信息(現在)。在整天思考了這個問題後,我決定將認證從其中解耦到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<Address> Addresses { get; set; } 
    public virtual Company Company { get; set; } 
    public virtual ICollection<Email> Emails { get; set; } 
    public virtual ICollection<Phone> Phones { get; set; } 

    public Employee() { 
     this.Addresses = new List<Address>(); 
     this.Emails = new List<Email>(); 
     this.Phones = new List<Phone>(); 
    } 
} 

而且我DbContext派生類:

public class DatabaseContext : DbContext { 
    static DatabaseContext() { 
     Database.SetInitializer<DatabaseContext>(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<string> 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<Coordinate>(); 

     /// Configs... 

     base.OnModelCreating(modelBuilder); 
    } 
} 

回答

14

,我結束了建設自己的身份執行。首先,我所做的就是拿出我現有的Employee對象,並將其擴展爲繼承自IUser<int>IUser<int>是身份2.0(目前在阿爾法)的一部分,其允許主鍵類型被配置成比其它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的財產,有沒有obsoleting它的清潔方法除了簡單地把它留空?

移動的,我還增加了PasswordHash屬性。我添加了自己的Role對象,繼承自IRole<int>。最後,EmployeeRole對象每個都有一個ICollection<T>相互鏈接。另一方面,Identity的實體框架實現手動創建映射表UserRoles,而不是利用它自己的配置功能,我似乎無法理解背後的推理。它創建的UserRole沒有獲得通過到*Store的IT實現,但它並沒有真正做什麼特別的東西不是充當鏈接等。在我的實現中,我只是使用已經建立的鏈接,這當然會在數據庫中創建一個映射表,但不會毫無意義地暴露給應用程序。我只是覺得好奇。

移動再度登場,與我的配置對象我繼續實現我自己的IUserStoreIRoleStore類創造性地稱爲EmployeeStoreRoleStore

public class EmployeeStore : IQueryableUserStore<Employee, int>, IUserStore<Employee, int>, IUserPasswordStore<Employee, int>, IUserRoleStore<Employee, int>, IDisposable { 
    private bool Disposed; 
    private IDatabaseRepository<Role> RolesRepository { get; set; } 
    private IDatabaseRepository<Employee> EmployeesRepository { get; set; } 

    public EmployeeStore(
     IDatabaseRepository<Role> rolesRepository, 
     IDatabaseRepository<Employee> employeesRepository) { 
     this.RolesRepository = rolesRepository; 
     this.EmployeesRepository = employeesRepository; 
    } 

    #region IQueryableUserStore Members 
    public IQueryable<Employee> 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<Employee> FindByIdAsync(
     int employeeId) { 
     this.ThrowIfDisposed(); 

     return Task.FromResult<Employee>(this.EmployeesRepository.FindSingleOrDefault(
      u => 
       (u.Id == employeeId))); 
    } 

    public Task<Employee> FindByNameAsync(
     string userName) { 
     this.ThrowIfDisposed(); 

     return Task.FromResult<Employee>(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<string> GetPasswordHashAsync(
     Employee employee) { 
     this.ThrowIfDisposed(); 

     if (employee == null) { 
      throw new ArgumentNullException("employee"); 
     } 

     return Task.FromResult<string>(employee.PasswordHash); 
    } 

    public Task<bool> HasPasswordAsync(
     Employee employee) { 
     return Task.FromResult<bool>(!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<int>(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<int>(0); 
    } 

    public Task<IList<string>> GetRolesAsync(
     Employee employee) { 
     this.ThrowIfDisposed(); 

     if (employee == null) { 
      throw new ArgumentNullException("employee"); 
     } 

     return Task.FromResult<IList<string>>(employee.Roles.Select(
      r => 
       r.Name).ToList()); 
    } 

    public Task<bool> 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<bool>(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<int>(0); 
    } 
    #endregion 
} 

RoleStore

public class RoleStore : IQueryableRoleStore<Role, int>, IRoleStore<Role, int>, IDisposable { 
    private bool Disposed; 
    private IDatabaseRepository<Role> RolesRepository { get; set; } 

    public RoleStore(
     IDatabaseRepository<Role> rolesRepository) { 
     this.RolesRepository = rolesRepository; 
    } 

    #region IQueryableRoleStore Members 
    public IQueryable<Role> 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<Role> FindByIdAsync(
     int roleId) { 
     this.ThrowIfDisposed(); 

     return Task.FromResult<Role>(this.RolesRepository.FindSingleOrDefault(
      r => 
       (r.Id == roleId))); 
    } 

    public Task<Role> FindByNameAsync(
     string roleName) { 
     this.ThrowIfDisposed(); 

     return Task.FromResult<Role>(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 
} 

現在,我注意到實體框架的實現是創建了一個看起來像一個迷你倉庫的東西。由於我的項目已經使用我自己的Repository實現,我決定改用它。我們將看到如何繼續下去......

現在,這一切工作和令人驚訝的完全不死機,或至少目前還沒有。話雖如此,我擁有所有這些美妙的Identity實現,但我似乎無法弄清楚如何在我的MVC應用程序中利用它們。既然這個問題超出了這個問題的範圍,我會繼續開發一個解決這個問題的新問題。

我要離開這個作爲答案的問題的情況下,別人運行到這個在未來。當然,如果有人在我發佈的代碼中看到錯誤,請告訴我。

6

看看在SimpleSecurity Project source code爲ASP.NET身份的數據庫上下文如何延長一個例子包括新表格。這可能適合你的情況。以下是通過繼承ASP.NET Identity上下文來定義新上下文的方式。

public class SecurityContext : IdentityDbContext<ApplicationUser> 
{ 
    public SecurityContext() 
     : base("SimpleSecurityConnection") 
    { 
    } 


    public DbSet<Resource> Resources { get; set; } 
    public DbSet<OperationsToRoles> OperationsToRoles { get; set; } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     base.OnModelCreating(modelBuilder); 
     modelBuilder.Configurations.Add(new ResourceConfiguration()); 
     modelBuilder.Configurations.Add(new OperationsToRolesConfiguration()); 
    } 
} 

SimpleSecurity Project decouples ASP.NET Identity from your MVC application並擴展它。

由於您的Employee類似乎是會員我想看看它的剪裁以適應how you customize the user profile in ASP.NET Identity, which is discussed here用戶配置文件。基本上你的Employee類需要繼承自IdentityUser,並且你將從Employee中移除Password屬性,因爲這是在IdentityUser中定義的,並且框架在那裏查找它。然後定義你的背景下,當您將使用Employee類代替它會因此支出約一天左右讀,看完之後是這個樣子

public class DatabaseContext : IdentityDbContext<Employee> 
{ 
    ... 
} 
+0

你不能只是覆蓋「密碼」屬性派生類? – GFoley83

+0

@ GFoley83 - 重寫密碼屬性的目的是什麼? –

+0

沒有太重要的東西,真的只是語義。當基類被包含在不同的程序集/第三方DLL中時,我通常更喜歡讓派生類包含所有屬性,因爲它並不總是清楚/直觀地表明您實際繼承的屬性。 – GFoley83

6

沒有一個解決方案適合所有情況,但我的項目,我發現,最容易做的事情是延長IdentityUserIdentityDbContext類。下面是僞代碼,重點是最低限度需要更改/添加才能正常工作。

對於你的用戶等級:

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

    public DomainUser() {} 
} 

爲了您的DbContext實現:

public class DomainModelContext : IdentityDbContext<DomainUser> 
{ 
    public DomainModelContext() 
     : base() {} 

    public DomainModelContext(string nameOrConnectionString) 
     : base(nameOrConnectionString) {} 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     base.OnModelCreating(modelBuilder); 
    } 
} 

而且在Startup.Auth.cs:

public static Func<UserManager<DomainUser>> UserManagerFactory { get; set; } 

    static Startup() 
    { 
     UserManagerFactory =() => new UserManager<DomainUser>(new UserStore<DomainUser>(new DomainModelContext())); 
    } 

另一個潛在選擇是創建一個1-1 DomainUser類與繼承自Identit的ApplicationUser類之間的關係yUser。這將減少耦合您的域模型和身份之間的機制,特別是如果你使用WithRequiredDependent而無需創建一個雙向導航屬性,像這樣:

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