Decoupling Asp.Net Identity 2.0 from Entity Framework in MVC5: Part 1


Usually the Asp.Net Identity 2.0 framework is aptly suited for most simple applications. However in complex line of business applications there is often a need to extend this base framework to map to growing application needs and architecture.
Today we are going to take a spin around the Asp.Net Identity 2.0 and see how we can de-couple it from the Entity framework and extend it further.

The implementation of the ApplicationDbContext that comes as a part of the standard template when we add Asp.Net Identity 2.0 to a MVC5 application is something like:

public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager) {
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        return userIdentity;
    }
}

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext()
        : base("DefaultConnection", throwIfV1Schema: false) {
    }

    static ApplicationDbContext() {
        Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer());
    }

    public static ApplicationDbContext Create() {
        return new ApplicationDbContext();
    }
}

The IdentityDbContext does inherit from DbContext and in additions takes care of the Asp.Net Identity entities and related entities.

We will cover the entire post in 3 parts

Part 1

  • Implement the IdentityUser, IdentityRole and IdentityUserRole classes to support role based configuration for Asp.Net Identity framework
  • Make the ApplicationDbContext work with DbContext instead of IdentityDbContext<TUser> class
Part 2
  • Implement the UserStore without Claims and External Logins support to work with the new IdentityUser class
  • Implement RoleStore to work with the new IdentityRole class

Part 3

  • Implement Custom UserManager class to work with our new UserStore and IdentityUser entity
  • Implement Custom RoleManager class to work with our new RoleStore and IdentiyRole entity
  • A sample MVC5¬†application working with our custom system implementing basic user authentication and registration process using Asp.Net Identity

IdentityUser and Configuration class

public class IdentityUser: IUser
{
    #region Constructors

    public IdentityUser()
    {
        this.Id = Guid.NewGuid().ToString();
        this.Roles = (ICollection<IdentityUserRole>)new List<IdentityUserRole>();
    }

    public IdentityUser(string username): this()
    {
        this.UserName = username;
    }

    #endregion

    #region IUser<string> Members

    public string Id { get; set; }

    [Index("UserNameIndex", IsUnique=true)]
    public virtual string UserName { get; set; }

    public virtual string PasswordHash { get; set; }

    public virtual string SecurityStamp { get; set; }

    public virtual string Email { get; set; }

    public virtual int AccessFailedCount { get; set; }

    public virtual bool EmailConfirmed { get; set; }

    public virtual ICollection<IdentityUserRole> Roles { get; set; }

    #endregion

    #region Methods

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<IdentityUser> manager)
    {
        var identity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        return identity;
    }

    #endregion
}

public class IdentityUserConfiguration: EntityTypeConfiguration<IdentityUser>
{
    public IdentityUserConfiguration()
    {
        HasKey(t => t.Id);
        Property(t => t.Id).HasMaxLength(128);
        Property(t => t.UserName).IsRequired().HasMaxLength(256);
        Property(t => t.PasswordHash).IsRequired();
        Property(t => t.SecurityStamp).IsRequired();
        Property(t => t.Email).IsRequired().HasMaxLength(256);
        Property(t => t.EmailConfirmed).IsRequired();

        HasMany(t => t.Roles)
            .WithRequired(t => t.User)
            .HasForeignKey(t => t.UserId);

        ToTable("AspNetUsers");
    }
}

IdentityRole and configuration class

public class IdentityRole: IRole
{
    #region Constructors

    public IdentityRole()
    {
        this.Id = Guid.NewGuid().ToString();
    }

    public IdentityRole(string roleName): this()
    {
        this.Name = roleName;
    }

    public IdentityRole(string roleName, string id)
    {
        this.Name = roleName;
        this.Id = id;
    }

    #endregion

    #region IRole<string> Members

    public string Id { get; set; }

    [Index("RoleNameIndex", IsUnique=true)]
    public virtual string Name { get; set; }

    #endregion
}

public class IdentityRoleConfiguration: EntityTypeConfiguration<IdentityRole>
{
    public IdentityRoleConfiguration()
    {
        HasKey(t => t.Id);
        Property(t => t.Id).HasMaxLength(128);
        Property(t => t.Name).IsRequired().HasMaxLength(256);

        ToTable("AspNetRoles");
    }
}

IdentityUserRole and Configuration class

public class IdentityUserRole
{
    #region Properties

    public string UserId { get; set; }
    public virtual IdentityUser User { get; set; }

    public string RoleId { get; set; }
    public virtual IdentityRole Role { get; set; }

    #endregion

    #region Constructors

    public IdentityUserRole()
    {
    }

    public IdentityUserRole(string userId, string roleId)
    {
        this.UserId = userId;
        this.RoleId = roleId;
    }

    #endregion
}

public class IdentityUserRoleConfiguration: EntityTypeConfiguration<IdentityUserRole>
{
    public IdentityUserRoleConfiguration()
    {
        HasKey(t => new
        {
            t.RoleId,
            t.UserId
        });

        Property(t => t.UserId).HasColumnName("UserId");
        Property(t => t.RoleId).HasColumnName("RoleId");

        ToTable("AspNetUserRoles");
    }
}

And our new ApplicationDbContext class now becomes

public class ApplicationDbContext: DbContext
{
    static ApplicationDbContext()
    {
        Database.SetInitializer<ApplicationDbContext>(null);
    }

    public ApplicationDbContext()
        : base("DefaultConnection")
    {
        this.Configuration.ValidateOnSaveEnabled = true;
        this.Configuration.AutoDetectChangesEnabled = true;
    }

    public new IDbSet<T> Set<T>() where T : class
    {
        return base.Set<T>();
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.AddFromAssembly(typeof(IdentityModel.Configurations.IdentityUserConfiguration).Assembly);
    }
}

In the next part we will look at the implementations of:

  • UserStore
  • RoleStore