Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
163 views
in Technique[技术] by (71.8m points)

c# - Integrating ASP.NET Identity into Existing DbContext

I'm working on an ASP.NET MVC 5 project in VS2013, .NET 4.5.1, that uses Entity Framework 6 Code-First. I have a decent size database built out and somewhat working (project is about two weeks old). I want to integrate user authentication now, but I'm not sure how to approach it. After spending most of the day researching I've decided to give the new ASP.NET Identity framework a shot over having to write custom Membership or Role providers. What I'm confused about is how to make it all work with the existing database/model I have.

Currently I have an object called Employee that holds basic employee information (for now). After having pondered the question all day, I decided to decouple authentication from it into a User object, which is what Identity wants anyway. That being said how do I make it all work?

Here's my Employee class:

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>();
    }
}

And my DbContext derived class:

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);
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

So after spending about a day or so reading and reading, I ended up building my own Identity implementation. First what I did was take my existing Employee object and extended it to inherit from IUser<int>. IUser<int> is an interface that's a part of Identity 2.0 (currently in alpha) that allows the primary key type to be configured to something other than string as was default in 1.0. Because of the way I'm storing data, my implementation was really specific. For example, an Employee can have multiple Email objects related to it, and for my application I wanted to use emails as the user names. So, I simply set the UserName property to return the Employee's work email:

public string UserName {
    get {
        if (this.WorkEmail != null) {
            return this.WorkEmail.Address;
        }

        return null;
    }
    set {
        /// This property is non-settable.
    }
}

Side note, since I'm not going to be using the setter for the property, is there a cleaner way of obsoleting it other than simply leaving it empty?

Moving on, I also added the PasswordHash property. I added my own Role object, inheriting from IRole<int>. Lastly the Employee and Role objects each have an ICollection<T> linking to each other. Another side note, the Entity Framework implementation of Identity manually creates the mapping table UserRoles rather than leveraging it's own configuration capabilities and I can't seem to understand the reasoning behind it. The UserRole it creates does get passed into the *Stores it implements, but it doesn't really do anything special other than act as a link. In my implementation I simply used the already established link, which of course creates a mapping table in the database, but is not pointlessly exposed into the application. I just find it curious.

Moving on again, with my configured objects I went ahead and implemented my own IUserStore and IRoleStore classes creatively called EmployeeStore and RoleStore:

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
}

Now, what I noticed was that the Entity Framework implementation was creating what looked like a mini-repository. Since my project was already using my own Repository implementation, I decided to leverage it instead. We'll see how that goes...

Now, all of this works and surprisingly does not crash at all, or at least hasn't yet. That being said, I have all of these wonderful Identity implementations, yet I can't seem to figure out how to leverage them inside my MVC application. Since that falls out of scope for this question, I'll go ahead and open a new one addressing that.

I'm leaving this as the answer to the question in case someone else runs into this in the future. Of course, if anyone sees an error in the code I've posted, please let me know.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...