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
181 views
in Technique[技术] by (71.8m points)

c# - ASP.NET Identity with Repository and Unit of Work

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.

See Question&Answers more detail:os

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

1 Reply

0 votes
by (71.8m points)

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"

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

...