ASP.NET Identity with Repository and Unit of Work

21,070

Solution 1

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&lt;User, System.Int32&gt;.</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"></typeparam>
    /// <param name="entity">The entity.</param>
    /// <returns>T.</returns>
    public T Insert<T>(T entity) where T : class
    {
        return _unitOfWork.Insert(entity);
    }

    /// <summary>
    /// Updates the specified entity.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="entity">The entity.</param>
    /// <returns>T.</returns>
    public T Update<T>(T entity) where T : class
    {
        return _unitOfWork.Update(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
    {
        _unitOfWork.Delete(entity);
    }

    /// <summary>
    /// Finds the specified predicate.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="predicate">The predicate.</param>
    /// <returns>IQueryable&lt;T&gt;.</returns>
    public IQueryable<T> Find<T>(Expression<Func<T, bool>> predicate) where T : class
    {
       return _unitOfWork.Find(predicate);
    }

    /// <summary>
    /// Gets all.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns>IQueryable&lt;T&gt;.</returns>
    public IQueryable<T> GetAll<T>() where T : class
    {
        return _unitOfWork.GetAll<T>();
    }

    /// <summary>
    /// Gets the by identifier.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="id">The identifier.</param>
    /// <returns>T.</returns>
    public T GetById<T>(int id) where T : class
    {
       return _unitOfWork.GetById<T>(id);
    }

    /// <summary>
    /// Executes the query command.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="sql">The SQL.</param>
    /// <returns>DbSqlQuery&lt;T&gt;.</returns>
    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.

Solution 2

"One issue to be aware of is that the UserStore class does not play well when using the unit of work design pattern. Specifically, the UserStore invokes SaveChanges in nearly every method call by default, which makes it easy to prematurely commit a unit of work. To change this behavior, change the AutoSaveChanges flag on the UserStore."

var store = new UserStore<ApplicationUser>(new ApplicationDbContext());
store.AutoSaveChanges = false;

From Scott Allen: http://odetocode.com/blogs/scott/archive/2014/01/03/asp-net-identity-with-the-entity-framework.aspx

Solution 3

Found some sort of solution, which looks generic enough, but I'm still not sure if it's really good and doesn't break Repository/UnitOfWork pattern principles.

I added generic GetDbContext() method to my IUnitOfWork:

public interface IUnitOfWork : IDisposable
{
   void Save();    
   IRepository<TEntity> GetRepository<TEntity>() where TEntity : class;    
   TContext GetDbContext<TContext>() where TContext : DbContext, IDbContext;
}

Its implementation in UnitOfWork class:

public class UnitOfWork<TContext> : IUnitOfWork where TContext : IDbContext, new()
{
    private IDbContext dbContext;
  
    public UnitOfWork()
    {
        this.dbContext = new TContext();
    }

    public T GetDbContext<T>() where T : DbContext, IDbContext
    {
        return this.dbContext as T;
    }

    ...
}

How it's used in a Controller, initializing UserManager:

public class AccountController : ControllerBase
{
    private readonly IUnitOfWork unitOfWork;

    public UserManager<ApplicationUser> UserManager { get; private set; }

    public AccountController()
        : this(new UnitOfWork<MyDbContext>())
    {
    }

    public AccountController(IUnitOfWork unitOfWork)
    {
        this.unitOfWork = unitOfWork;    
        UserStore<ApplicationUser> store = new UserStore<ApplicationUser>(unitOfWork.GetDbContext<MyDbContext>());
        this.UserManager = new UserManager<ApplicationUser>(store);
    }

    ...
}

I suspect GetDbContext() will be used just to workaround some difficulties with ASP.Identity, so might it's not so bad..

Solution 4

If you are using Repository and UnitofWork pattern may be you are using it with DDD (Domain Driven Design) where you declare IRepository or IUnitofWork in Core project along with all other domain model and abstract classes.

Now you make Infrastructure project which implements those interfaces in Core project using concrete data access object for this instance Entity Framework. so DbContext is fine there but yes don't expose it to presentation layer. So at some point if you want to change EF to any other ORM then it will be easier without touching presentation layer where you put your Identity classes separate from Data Access or Infrastructure project. And of course you can use IOC container to instantiate those concrete Repositories from Infrastructure in Controllers of Presentation layer.

Share:
21,070
Aleksanderis
Author by

Aleksanderis

Updated on September 26, 2020

Comments

  • Aleksanderis
    Aleksanderis over 3 years

    I'm learning Repository and Unit of Work patterns in ASP.NET MVC 5 application with Entity Framework 6.

    I had already read a lot of tutorials and articles, but almost all of them are condradictory. Ones say that Repository and Unit of Work patterns are good, others say DbContext is already a repository and unit of work, others say something similar, but offer a completely different approach. I tried all these different approaches (well, maybe not all of them) and still struggling regarding which approach is the most correct one.

    What I currently have is:

    • IRepository and GenericRepository implementing IRepository
    • IUnitOfWork and UnitOfWork implementing IUnitOfWork
    • IDbContext and MyDbContext inherited from IdentityDbContext and implementing IDbContext

    Not sure if I need to paste the code for it, I think it's pretty generic and the problem actually is not with Repository/UnitOfWork as such. The issue I have is with using ASP.NET Identity classes in combination with my Repositories and Unit of Work. I'm sharing same database for membership and for all other data - and I think it's a common scenario. I cannot find the good solution how can I instantiate ASP.NET Identity classes using my repositories.

    UserStore<ApplicationUser> store = new UserStore<ApplicationUser>(_DBCONTEXT_);
    this.UserManager = new UserManager<ApplicationUser>(store);
    

    What should I put in place of DBCONTEXT, so that it would share same DbContext with my UnitOfWork? Or how it can be done in some other way to make ASP.NET Identity to work with UnitOfWork?

    I tried exposing DbContext as public property of UnitOfWork class, something like:

    UserStore<ApplicationUser> store = new UserStore<ApplicationUser>(this.unitOfWork.MyDbContext);
    

    However I don't think it's right - it doesn't work with custom IDbContext interface, and makes the code not good for unit testing.

    I also tried to implement CustomUserStore and CustomRoleStore - in general it worked, but as I was testing it, it was requiring to implement more and more methods. This solution looks too complicated - I really hope there should more simple way.