簡體   English   中英

將ASP.NET標識集成到現有的DbContext中

[英]Integrating ASP.NET Identity into Existing DbContext

我正在使用VS2013,.NET 4.5.1中的ASP.NET MVC 5項目,該項目使用Entity Framework 6 Code-First。 我有一個體面的數據庫建立和有點工作(項目大約兩周)。 我想現在整合用戶身份驗證,但我不知道如何處理它。 在花費了大部分時間進行研究之后,我決定為新的ASP.NET身份框架提供一個必須編寫自定義成員資格或角色提供程序的鏡頭。 我感到困惑的是如何使用現有的數據庫/模型完成所有工作。

目前我有一個名為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);
    }
}

因此,在花了大約一天閱讀和閱讀后,我最終構建了自己的Identity實現。 首先,我做的是獲取現有的Employee對象並將其擴展為繼承自IUser<int> IUser<int>是一個接口,它是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對象,繼承自IRole<int> 最后, EmployeeRole對象每個都有一個ICollection<T>鏈接。 另外注意,Identity的Entity Framework實現手動創建映射表UserRoles而不是利用它自己的配置功能,我似乎無法理解它背后的原因。 它創建的UserRole會傳遞到它實現的*Store ,但除了充當鏈接之外,它並沒有真正做任何特殊的事情。 在我的實現中,我只使用已經建立的鏈接,當然在數據庫中創建了一個映射表,但是沒有毫無意義地暴露給應用程序。 我發現它好奇。

繼續前進,使用我配置的對象,我繼續創建自己的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實現,所以我決定改用它。 我們會看到這是怎么回事......

現在,所有這些都有效,並且令人驚訝的是根本沒有崩潰,或者至少還沒有。 話雖如此,我擁有所有這些精彩的身份實現,但我似乎無法弄清楚如何在我的MVC應用程序中利用它們。 由於這不屬於這個問題的范圍,我將繼續開設一個新問題。

我將此作為問題的答案,以防將來有人遇到此問題。 當然,如果有人在我發布的代碼中看到錯誤,請告訴我。

請查看SimpleSecurity Project源代碼 ,以獲取如何擴展ASP.NET Identity的數據庫上下文以包含新表的示例。 這可能適合您的情況。 以下是通過從ASP.NET標識上下文繼承來定義新上下文的方法。

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將ASP.NET Identity與MVC應用程序分離並擴展它。

由於您的Employee類似乎是成員資格的用戶配置文件,因此我將根據您在ASP.NET身份中自定義用戶配置文件的方式來定制它,這將在此處討論 基本上你的Employee類需要繼承IdentityUser,你會從Employee中刪除Password屬性,因為這是在IdentityUser中定義的,框架在那里查找它。 然后在定義上下文時,您將使用Employee類,因此它看起來像這樣

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

沒有一個解決方案適合所有情況,但對於我的項目,我發現最簡單的事情是擴展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()));
    }

另一個可能的選擇是在DomainUser類和繼承自IdentityUser的ApplicationUser類之間創建1-1關系。 這將減少域模型和Identity機制之間的耦合,特別是如果您使用WithRequiredDependent而不創建雙向導航屬性,如下所示:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM