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>
{
/// <summary>
/// Initializes a new instance of the <see cref="CustomRole"/> class.
/// </summary>
public CustomRole() { }
/// <summary>
/// Initializes a new instance of the <see cref="CustomRole"/> class.
/// </summary>
/// <param name="name">The name.</param>
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>
{
/// <summary>
/// Gets or sets the first name.
/// </summary>
/// <value>The first name.</value>
public string FirstName { get; set; }
/// <summary>
/// Gets or sets the last name.
/// </summary>
/// <value>The last name.</value>
public string LastName { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this <see cref="User"/> is active.
/// </summary>
/// <value><c>true</c> if active; otherwise, <c>false</c>.</value>
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
{
/// <summary>
/// Gets the user manager.
/// </summary>
/// <returns>UserManager<User, System.Int32>.</returns>
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;
}
//private readonly List<CommitInterception> _postInterceptions = new List<CommitInterception>();
public DataContext Context { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="UnitOfWork"/> class.
/// </summary>
public UnitOfWork()
{
Context = new DataContext();
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <exception cref="System.NotImplementedException"></exception>
public void Dispose()
{
//Chuck was here
try
{
Commit();
}
finally
{
Context.Dispose();
}
}
/// <summary>
/// Begins the transaction.
/// </summary>
/// <returns>IUnitOfWorkTransaction.</returns>
public IUnitOfWorkTransaction BeginTransaction()
{
return new UnitOfWorkTransaction(this);
}
/// <summary>
/// Commits this instance.
/// </summary>
public void Commit()
{
Commit(null);
}
/// <summary>
/// Commits transaction.
/// </summary>
public void Commit(DbContextTransaction transaction)
{
//Lee was here.
try
{
Context.SaveChanges();
if (transaction != null)
{
transaction.Commit();
}
//foreach (var interception in _postInterceptions)
//{
// interception.PostCommit(interception.Instance, this);
//}
}
catch (DbEntityValidationException ex)
{
var errors = FormatError(ex);
throw new Exception(errors, ex);
}
catch
{
if (transaction != null)
{
transaction.Rollback();
}
throw;
}
finally
{
// _postInterceptions.Clear();
}
}
/// <summary>
/// Formats the error.
/// </summary>
/// <param name="ex">The ex.</param>
/// <returns>System.String.</returns>
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();
}
/// <summary>
/// Inserts the specified entity.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entity">The entity.</param>
/// <returns>``0.</returns>
public T Insert<T>(T entity) where T: class
{
var instance = _container.TryGetInstance<IUnitOfWorkInterception<T>>();
if (instance != null)
{
instance.Intercept(entity, this);
// _postInterceptions.Add(new CommitInterception() { Instance = entity, PostCommit = (d,f) => instance.PostCommit(d as T, f) });
}
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;
}
/// <summary>
/// Deletes the specified entity.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entity">The entity.</param>
public void Delete<T>(T entity) where T : class
{
var set = Context.Set<T>();
set.Remove(entity);
}
/// <summary>
/// Finds the specified predicate.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="predicate">The predicate.</param>
/// <returns>IQueryable{``0}.</returns>
public IQueryable<T> Find<T>(Expression<Func<T, bool>> predicate) where T : class
{
var set = Context.Set<T>();
return set.Where(predicate);
}
/// <summary>
/// Gets all.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>IQueryable{``0}.</returns>
public IQueryable<T> GetAll<T>() where T : class
{
return Context.Set<T>();
}
/// <summary>
/// Gets the by identifier.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="id">The identifier.</param>
/// <returns>``0.</returns>
public T GetById<T>(int id) where T : class
{
var set = Context.Set<T>();
return set.Find(id);
}
/// <summary>
/// Executes the query command.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql">The SQL.</param>
/// <returns>DbSqlQuery{``0}.</returns>
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;
/// <summary>
/// Initializes a new instance of the <see cref="UnitOfWorkTransaction"/> class.
/// </summary>
/// <param name="unitOfWork">The unit of work.</param>
public UnitOfWorkTransaction(UnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
_transaction = _unitOfWork.Context.Database.BeginTransaction();
Context = unitOfWork.Context;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
_unitOfWork.Commit(_transaction);
}
public DataContext Context { get; set; }
/// <summary>
/// Commits this instance.
/// </summary>
public void Commit()
{
_unitOfWork.Commit();
}
/// <summary>
/// Rollbacks this instance.
/// </summary>
public void Rollback()
{
_transaction.Rollback();
}
/// <summary>
/// Inserts the specified entity.
/// </summary>
/// <typeparam name="T"