I have found working with ASP.Net Identity 2.0 and EF6 a bit challenging. The biggest drawback is the lack of documentation or conflicting documentation.
I am using WebApi 2.0, EF6 and ASP.Net Identity 2.0. At first it was tough to get going but once it's working, it's been good.
I created my own Identity classes. At the moment I don't care about extending the identity classes I just want to generate the tables and log into the system.
CustomRole
public class CustomRole : IdentityRole<int, CustomUserRole>
{
public CustomRole() { }
public CustomRole(string name) { Name = name; }
}
CustomUserClaim
public class CustomUserClaim : IdentityUserClaim<int> { }
CustomUserLogin
public class CustomUserLogin : IdentityUserLogin<int> { }
CustomUserRole
public class CustomUserRole : IdentityUserRole<int> {}
User
public class User : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Active { get; set; }
}
I don't like the naming of the Identity tables, so I changed the names.
DataContext
public class DataContext : IdentityDbContext<User, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
public DataContext() : base("DefaultConnection"){}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<CustomUserRole>().ToTable("UserRoles", "Security");
modelBuilder.Entity<CustomUserLogin>().ToTable("UserLogins", "Security");
modelBuilder.Entity<CustomUserClaim>().ToTable("UserClaims", "Security");
modelBuilder.Entity<CustomRole>().ToTable("Roles", "Security");
modelBuilder.Entity<User>().ToTable("Users", "Security");
}
}
I found getting the UserManager a bit of a pain.
I created a static class to handle it. The UserStore does handle the lifecycle of the DataContext, but you'll have to call dispose for this to happen. This could cause problems if you are using this DataContext reference elsewhere. I'll eventually wire it into my DI container, but for now this is what I have:
public class Identity
{
public static UserManager<User, int> GetUserManager()
{
var store = new UserStore<User, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>(new DataContext());
var userManager = new UserManager<User, int>(store);
return userManager;
}
}
I use the Unit of Work pattern for most my data access. It works good. There are some cases where I have data that needs more control than the unit of work exposes for these cases I exposed the DataContext. If that still does not work for me, I'll fallback to using a repository.
public class UnitOfWork : IUnitOfWork
{
private readonly IContainer _container;
public UnitOfWork(IContainer container) :this()
{
_container = container;
}
public DataContext Context { get; set; }
public UnitOfWork()
{
Context = new DataContext();
}
public void Dispose()
{
try
{
Commit();
}
finally
{
Context.Dispose();
}
}
public IUnitOfWorkTransaction BeginTransaction()
{
return new UnitOfWorkTransaction(this);
}
public void Commit()
{
Commit(null);
}
public void Commit(DbContextTransaction transaction)
{
try
{
Context.SaveChanges();
if (transaction != null)
{
transaction.Commit();
}
}
catch (DbEntityValidationException ex)
{
var errors = FormatError(ex);
throw new Exception(errors, ex);
}
catch
{
if (transaction != null)
{
transaction.Rollback();
}
throw;
}
finally
{
}
}
private static string FormatError(DbEntityValidationException ex)
{
var build = new StringBuilder();
foreach (var error in ex.EntityValidationErrors)
{
var errorBuilder = new StringBuilder();
foreach (var validationError in error.ValidationErrors)
{
errorBuilder.AppendLine(string.Format("Property '{0}' errored:{1}", validationError.PropertyName, validationError.ErrorMessage));
}
build.AppendLine(errorBuilder.ToString());
}
return build.ToString();
}
public T Insert<T>(T entity) where T: class
{
var instance = _container.TryGetInstance<IUnitOfWorkInterception<T>>();
if (instance != null)
{
instance.Intercept(entity, this);
}
var set = Context.Set<T>();
var item = set.Add(entity);
return item;
}
public T Update<T>(T entity) where T : class
{
var set = Context.Set<T>();
set.Attach(entity);
Context.Entry(entity).State = EntityState.Modified;
return entity;
}
public void Delete<T>(T entity) where T : class
{
var set = Context.Set<T>();
set.Remove(entity);
}
public IQueryable<T> Find<T>(Expression<Func<T, bool>> predicate) where T : class
{
var set = Context.Set<T>();
return set.Where(predicate);
}
public IQueryable<T> GetAll<T>() where T : class
{
return Context.Set<T>();
}
public T GetById<T>(int id) where T : class
{
var set = Context.Set<T>();
return set.Find(id);
}
public DbSqlQuery<T> ExecuteQueryCommand<T>(string sql) where T : class
{
var set = Context.Set<T>();
return set.SqlQuery(sql);
}
private class CommitInterception
{
public object Instance { get; set; }
public Action<object, IUnitOfWork> PostCommit { get; set; }
}
}
public class UnitOfWorkTransaction : IUnitOfWorkTransaction
{
private readonly UnitOfWork _unitOfWork;
private readonly DbContextTransaction _transaction;
public UnitOfWorkTransaction(UnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
_transaction = _unitOfWork.Context.Database.BeginTransaction();
Context = unitOfWork.Context;
}
public void Dispose()
{
_unitOfWork.Commit(_transaction);
}
public DataContext Context { get; set; }
public void Commit()
{
_unitOfWork.Commit();
}
public void Rollback()
{
_transaction.Rollback();
}
public T Insert<T>(T entity) where T : class
{
return _unitOfWork.Insert(entity);
}
public T Update<T>(T entity) where T : class
{
return _unitOfWork.Update(entity);
}
public void Delete<T>(T entity) where T : class
{
_unitOfWork.Delete(entity);
}
public IQueryable<T> Find<T>(Expression<Func<T, bool>> predicate) where T : class
{
return _unitOfWork.Find(predicate);
}
public IQueryable<T> GetAll<T>() where T : class
{
return _unitOfWork.GetAll<T>();
}
public T GetById<T>(int id) where T : class
{
return _unitOfWork.GetById<T>(id);
}
public DbSqlQuery<T> ExecuteQueryCommand<T>(string sql) where T : class
{
return _unitOfWork.ExecuteQueryCommand<T>(sql);
}
}
Here are a few examples of it in action. I have an nHibernate background and like defining a transaction in the scope of a using
so I implemented in my unit of work.
using (var trans = _unitOfWork.BeginTransaction())
{
var newAgency = trans.Insert(new Database.Schema.Agency() { Name = agency.Name, TaxId = agency.TaxId });
}
Another example of using the "Find" off of the Unit of Work:
var users = _unitOfWork.Find<Database.Schema.User>(s => s.Active && s.Agency_Id == agencyId)
.Select(u=> new {Label = u.FirstName + " " + u.LastName, Value = u.Id})
.ToList();
User Creation and User Sign-In
I use ASP.NET Identity for the sign-In and user creation and my Unit of Work for everything else.
Testing
I would not try to test ASP.NET Identity. For one I'm sure Microsoft did a pretty good job testing it. I'm sure they did a better job than you or I could do. If you really want to test around the ASP.NET Identity code put it behind an interface and mock out the interface.
DbContext
is a repository - if you choose to use it as the repository then you are tightly-coupled to EF and prevent any type of unit testing on classes that require a repository. - D Stanley