Search code examples
c#genericsasp.net-coreentity-framework-6

Using ApplicationUser in a Generic Repository pattern


Is it possible to use asp net core's default authentication in a generic repository pattern and if so, how?

I am able to create a project that uses a generic repository pattern for all of my code, except the authentication side of the project which uses the default way.

My repository looks like:

using System.Linq;
using DemoWebsite.Interfaces;
using Microsoft.EntityFrameworkCore;

namespace DemoWebsite.Data
{
 public class Repository<T> : IRepository<T> where T : class
{
    protected readonly DbContext Context;
    protected DbSet<T> DbSet;

    public Repository(ApplicationDbContext context)
    {
        Context = context;
        DbSet = context.Set<T>();
    }

    public void Add(T entity)
    {
        Context.Set<T>().Add(entity);

        Save();
    }

    public T Get<TKey>(TKey id)
    {
        return DbSet.Find(id);
    }

    public IQueryable<T> GetAll()
    {
        return DbSet;
    }

    public void Update(T entity)
    {
        Save();
    }

    private void Save()
    {
        Context.SaveChanges();
    }
}
}

My ApplicationDbContext class:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);
    }
}

Solution

  • Yes it's possible, but it's quite a bit of effort to do so. You'll have to Implement IUserStore and possibly IRoleStore (interfaces here and here).

    When implementing this interfaces, you also have to implement all of the feature interfaces (for getting passwords, two-factor authentication (here), security stamp, etc.).

    For an example implementation you can always look at the source of the EF Core Identity provider.

    Like this

    public abstract class UserStore<TUser, TRole, TContext, TKey, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim> :
        IUserLoginStore<TUser>,
        IUserRoleStore<TUser>,
        IUserClaimStore<TUser>,
        IUserPasswordStore<TUser>,
        IUserSecurityStampStore<TUser>,
        IUserEmailStore<TUser>,
        IUserLockoutStore<TUser>,
        IUserPhoneNumberStore<TUser>,
        IQueryableUserStore<TUser>,
        IUserTwoFactorStore<TUser>,
        IUserAuthenticationTokenStore<TUser>
        where TUser : IdentityUser<TKey, TUserClaim, TUserRole, TUserLogin>
        where TRole : IdentityRole<TKey, TUserRole, TRoleClaim>
        where TContext : DbContext
        where TKey : IEquatable<TKey>
        where TUserClaim : IdentityUserClaim<TKey>
        where TUserRole : IdentityUserRole<TKey>
        where TUserLogin : IdentityUserLogin<TKey>
        where TUserToken : IdentityUserToken<TKey>
        where TRoleClaim : IdentityRoleClaim<TKey>
    {
        ...
    }
    

    Your user store could look like:

    public class GenericUserStore<TUser> : IUserStore<TUser>,
        IUserPasswordStore<TUser> where TUser : ApplicationUser
    {
        public GenericUserStore(IRepository<TUser> userRepo) { }
    
        public Task<TUser> FindByIdAsync(string userId, CancellationToken cancellationToken)
        {
            return userRepo.Get(userId);
        }
    
        ...
    }
    

    and same for RoleStore. Then finally replace the EF Provider registration with your own ones (source of EF here).

    userStoreType = typeof(GenericUserStore<,,,>).MakeGenericType(userType, roleType, contextType, keyType);
    roleStoreType = typeof(GenericRoleStore<,,>).MakeGenericType(roleType, contextType, keyType);
    
    var services = new ServiceCollection();
    services.AddScoped(
        typeof(IUserStore<>).MakeGenericType(userType),
        userStoreType);
    services.AddScoped(
        typeof(IRoleStore<>).MakeGenericType(roleType),
        roleStoreType);
    

    Then UserManager and other Identity classes will use your User/Role store. But it's more trouble then it's worth, unless you have an existing DB structure which can't be mapped to EF/known providers.