Decoupling Asp.Net Identity 2.0 from Entity Framework in MVC5: Part 1
July 16, 2014 4 Comments
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
- 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
-
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