Gup*_*R4c 24 c# entity-framework ef-code-first entity-framework-6 asp.net-identity
我正在使用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>();
}
}
Run Code Online (Sandbox Code Playgroud)
而我的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);
}
}
Run Code Online (Sandbox Code Playgroud)
Gup*_*R4c 14
因此,在花了大约一天阅读和阅读后,我最终构建了自己的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.
}
}
Run Code Online (Sandbox Code Playgroud)
旁注,因为我不打算使用该属性的setter,除了简单地将它留空之外,还有一种更简洁的方式来淘汰它吗?
接下来,我还添加了该PasswordHash
属性.我添加了自己的Role
对象,继承自IRole<int>
.最后Employee
,Role
每个对象都有一个相互ICollection<T>
链接.另一方面,Identity的Entity Framework实现手动创建映射表,UserRoles
而不是利用它自己的配置功能,我似乎无法理解它背后的原因.在UserRole
它创建并获得通过到*Store
它实现了S,但它并没有真正做什么特别的东西不是充当链接等.在我的实现中,我只使用已经建立的链接,当然在数据库中创建了一个映射表,但是没有毫无意义地暴露给应用程序.我发现它好奇.
再继续前进,我的配置对象我继续实现我自己IUserStore
和IRoleStore
类创造性地称为EmployeeStore
和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
}
Run Code Online (Sandbox Code Playgroud)
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
}
Run Code Online (Sandbox Code Playgroud)
现在,我注意到实体框架实现正在创建看起来像迷你存储库的东西.由于我的项目已经在使用我自己的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());
}
}
Run Code Online (Sandbox Code Playgroud)
该SimpleSecurity项目从你的MVC应用解耦ASP.NET身份并将其扩展.
由于您的Employee类似乎是成员资格的用户配置文件,因此我将根据您在ASP.NET身份中自定义用户配置文件的方式来定制它,这将在此处讨论.基本上你的Employee类需要从IdentityUser继承,你会从Employee中删除Password属性,因为这是在IdentityUser中定义的,框架在那里查找它.然后在定义上下文时,您将使用Employee类,因此它看起来像这样
public class DatabaseContext : IdentityDbContext<Employee>
{
...
}
Run Code Online (Sandbox Code Playgroud)
没有一个解决方案适合所有情况,但对于我的项目,我发现最简单的事情是扩展IdentityUser
和IdentityDbContext
类.下面是伪代码,它专注于您需要更改/添加以使其正常工作的最低限度.
对于您的用户类:
public class DomainUser : IdentityUser
{
public DomainUser(string userName) : base(userName) {}
public DomainUser() {}
}
Run Code Online (Sandbox Code Playgroud)
对于您的DbContext实现:
public class DomainModelContext : IdentityDbContext<DomainUser>
{
public DomainModelContext()
: base() {}
public DomainModelContext(string nameOrConnectionString)
: base(nameOrConnectionString) {}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
Run Code Online (Sandbox Code Playgroud)
在Startup.Auth.cs中:
public static Func<UserManager<DomainUser>> UserManagerFactory { get; set; }
static Startup()
{
UserManagerFactory = () => new UserManager<DomainUser>(new UserStore<DomainUser>(new DomainModelContext()));
}
Run Code Online (Sandbox Code Playgroud)
另一个可能的选择是在DomainUser类和继承自IdentityUser的ApplicationUser类之间创建1-1关系.这将减少域模型和Identity机制之间的耦合,特别是如果您使用WithRequiredDependent而不创建双向导航属性,如下所示:
modelBuilder.Entity<ApplicationUser>().HasRequired(au => au.DomainUser).WithRequiredPrincipal();
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
29469 次 |
最近记录: |