ASP.NET Identity DbContext confusion

143,451

Solution 1

I would use a single Context class inheriting from IdentityDbContext. This way you can have the context be aware of any relations between your classes and the IdentityUser and Roles of the IdentityDbContext. There is very little overhead in the IdentityDbContext, it is basically a regular DbContext with two DbSets. One for the users and one for the roles.

Solution 2

There is a lot of confusion about IdentityDbContext, a quick search in Stackoverflow and you'll find these questions:
" Why is Asp.Net Identity IdentityDbContext a Black-Box?
How can I change the table names when using Visual Studio 2013 AspNet Identity?
Merge MyDbContext with IdentityDbContext"

To answer to all of these questions we need to understand that IdentityDbContext is just a class inherited from DbContext.
Let's take a look at IdentityDbContext source:

/// <summary>
/// Base class for the Entity Framework database context used for identity.
/// </summary>
/// <typeparam name="TUser">The type of user objects.</typeparam>
/// <typeparam name="TRole">The type of role objects.</typeparam>
/// <typeparam name="TKey">The type of the primary key for users and roles.</typeparam>
/// <typeparam name="TUserClaim">The type of the user claim object.</typeparam>
/// <typeparam name="TUserRole">The type of the user role object.</typeparam>
/// <typeparam name="TUserLogin">The type of the user login object.</typeparam>
/// <typeparam name="TRoleClaim">The type of the role claim object.</typeparam>
/// <typeparam name="TUserToken">The type of the user token object.</typeparam>
public abstract class IdentityDbContext<TUser, TRole, TKey, TUserClaim, TUserRole, TUserLogin, TRoleClaim, TUserToken> : DbContext
    where TUser : IdentityUser<TKey, TUserClaim, TUserRole, TUserLogin>
    where TRole : IdentityRole<TKey, TUserRole, TRoleClaim>
    where TKey : IEquatable<TKey>
    where TUserClaim : IdentityUserClaim<TKey>
    where TUserRole : IdentityUserRole<TKey>
    where TUserLogin : IdentityUserLogin<TKey>
    where TRoleClaim : IdentityRoleClaim<TKey>
    where TUserToken : IdentityUserToken<TKey>
{
    /// <summary>
    /// Initializes a new instance of <see cref="IdentityDbContext"/>.
    /// </summary>
    /// <param name="options">The options to be used by a <see cref="DbContext"/>.</param>
    public IdentityDbContext(DbContextOptions options) : base(options)
    { }

    /// <summary>
    /// Initializes a new instance of the <see cref="IdentityDbContext" /> class.
    /// </summary>
    protected IdentityDbContext()
    { }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of Users.
    /// </summary>
    public DbSet<TUser> Users { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of User claims.
    /// </summary>
    public DbSet<TUserClaim> UserClaims { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of User logins.
    /// </summary>
    public DbSet<TUserLogin> UserLogins { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of User roles.
    /// </summary>
    public DbSet<TUserRole> UserRoles { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of User tokens.
    /// </summary>
    public DbSet<TUserToken> UserTokens { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of roles.
    /// </summary>
    public DbSet<TRole> Roles { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of role claims.
    /// </summary>
    public DbSet<TRoleClaim> RoleClaims { get; set; }

    /// <summary>
    /// Configures the schema needed for the identity framework.
    /// </summary>
    /// <param name="builder">
    /// The builder being used to construct the model for this context.
    /// </param>
    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<TUser>(b =>
        {
            b.HasKey(u => u.Id);
            b.HasIndex(u => u.NormalizedUserName).HasName("UserNameIndex").IsUnique();
            b.HasIndex(u => u.NormalizedEmail).HasName("EmailIndex");
            b.ToTable("AspNetUsers");
            b.Property(u => u.ConcurrencyStamp).IsConcurrencyToken();

            b.Property(u => u.UserName).HasMaxLength(256);
            b.Property(u => u.NormalizedUserName).HasMaxLength(256);
            b.Property(u => u.Email).HasMaxLength(256);
            b.Property(u => u.NormalizedEmail).HasMaxLength(256);
            b.HasMany(u => u.Claims).WithOne().HasForeignKey(uc => uc.UserId).IsRequired();
            b.HasMany(u => u.Logins).WithOne().HasForeignKey(ul => ul.UserId).IsRequired();
            b.HasMany(u => u.Roles).WithOne().HasForeignKey(ur => ur.UserId).IsRequired();
        });

        builder.Entity<TRole>(b =>
        {
            b.HasKey(r => r.Id);
            b.HasIndex(r => r.NormalizedName).HasName("RoleNameIndex");
            b.ToTable("AspNetRoles");
            b.Property(r => r.ConcurrencyStamp).IsConcurrencyToken();

            b.Property(u => u.Name).HasMaxLength(256);
            b.Property(u => u.NormalizedName).HasMaxLength(256);

            b.HasMany(r => r.Users).WithOne().HasForeignKey(ur => ur.RoleId).IsRequired();
            b.HasMany(r => r.Claims).WithOne().HasForeignKey(rc => rc.RoleId).IsRequired();
        });

        builder.Entity<TUserClaim>(b => 
        {
            b.HasKey(uc => uc.Id);
            b.ToTable("AspNetUserClaims");
        });

        builder.Entity<TRoleClaim>(b => 
        {
            b.HasKey(rc => rc.Id);
            b.ToTable("AspNetRoleClaims");
        });

        builder.Entity<TUserRole>(b => 
        {
            b.HasKey(r => new { r.UserId, r.RoleId });
            b.ToTable("AspNetUserRoles");
        });

        builder.Entity<TUserLogin>(b =>
        {
            b.HasKey(l => new { l.LoginProvider, l.ProviderKey });
            b.ToTable("AspNetUserLogins");
        });

        builder.Entity<TUserToken>(b => 
        {
            b.HasKey(l => new { l.UserId, l.LoginProvider, l.Name });
            b.ToTable("AspNetUserTokens");
        });
    }
}


Based on the source code if we want to merge IdentityDbContext with our DbContext we have two options:

First Option:
Create a DbContext which inherits from IdentityDbContext and have access to the classes.

   public class ApplicationDbContext 
    : IdentityDbContext
{
    public ApplicationDbContext()
        : base("DefaultConnection")
    {
    }

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

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

    // Add additional items here as needed
}


Extra Notes:

1) We can also change asp.net Identity default table names with the following solution:

    public class ApplicationDbContext : IdentityDbContext
    {    
        public ApplicationDbContext(): base("DefaultConnection")
        {
        }

        protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<IdentityUser>().ToTable("user");
            modelBuilder.Entity<ApplicationUser>().ToTable("user");

            modelBuilder.Entity<IdentityRole>().ToTable("role");
            modelBuilder.Entity<IdentityUserRole>().ToTable("userrole");
            modelBuilder.Entity<IdentityUserClaim>().ToTable("userclaim");
            modelBuilder.Entity<IdentityUserLogin>().ToTable("userlogin");
        }
    }

2) Furthermore we can extend each class and add any property to classes like 'IdentityUser', 'IdentityRole', ...

    public class ApplicationRole : IdentityRole<string, ApplicationUserRole>
{
    public ApplicationRole() 
    {
        this.Id = Guid.NewGuid().ToString();
    }

    public ApplicationRole(string name)
        : this()
    {
        this.Name = name;
    }

    // Add any custom Role properties/code here
}


// Must be expressed in terms of our custom types:
public class ApplicationDbContext 
    : IdentityDbContext<ApplicationUser, ApplicationRole, 
    string, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>
{
    public ApplicationDbContext()
        : base("DefaultConnection")
    {
    }

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

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

    // Add additional items here as needed
}

To save time we can use AspNet Identity 2.0 Extensible Project Template to extend all the classes.

Second Option:(Not recommended)
We actually don't have to inherit from IdentityDbContext if we write all the code ourselves.
So basically we can just inherit from DbContext and implement our customized version of "OnModelCreating(ModelBuilder builder)" from the IdentityDbContext source code

Solution 3

This is a late entry for folks, but below is my implementation. You will also notice I stubbed-out the ability to change the the KEYs default type: the details about which can be found in the following articles:

NOTES:
It should be noted that you cannot use Guid's for your keys. This is because under the hood they are a Struct, and as such, have no unboxing which would allow their conversion from a generic <TKey> parameter.

THE CLASSES LOOK LIKE:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser, CustomRole, string, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
    #region <Constructors>

    public ApplicationDbContext() : base(Settings.ConnectionString.Database.AdministrativeAccess)
    {
    }

    #endregion

    #region <Properties>

    //public DbSet<Case> Case { get; set; }

    #endregion

    #region <Methods>

    #region

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        //modelBuilder.Configurations.Add(new ResourceConfiguration());
        //modelBuilder.Configurations.Add(new OperationsToRolesConfiguration());
    }

    #endregion

    #region

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

    #endregion

    #endregion
}

    public class ApplicationUser : IdentityUser<string, CustomUserLogin, CustomUserRole, CustomUserClaim>
    {
        #region <Constructors>

        public ApplicationUser()
        {
            Init();
        }

        #endregion

        #region <Properties>

        [Required]
        [StringLength(250)]
        public string FirstName { get; set; }

        [Required]
        [StringLength(250)]
        public string LastName { get; set; }

        #endregion

        #region <Methods>

        #region private

        private void Init()
        {
            Id = Guid.Empty.ToString();
        }

        #endregion

        #region public

        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);

            // Add custom user claims here

            return userIdentity;
        }

        #endregion

        #endregion
    }

    public class CustomUserStore : UserStore<ApplicationUser, CustomRole, string, CustomUserLogin, CustomUserRole, CustomUserClaim>
    {
        #region <Constructors>

        public CustomUserStore(ApplicationDbContext context) : base(context)
        {
        }

        #endregion
    }

    public class CustomUserRole : IdentityUserRole<string>
    {
    }

    public class CustomUserLogin : IdentityUserLogin<string>
    {
    }

    public class CustomUserClaim : IdentityUserClaim<string> 
    { 
    }

    public class CustomRoleStore : RoleStore<CustomRole, string, CustomUserRole>
    {
        #region <Constructors>

        public CustomRoleStore(ApplicationDbContext context) : base(context)
        {
        } 

        #endregion
    }

    public class CustomRole : IdentityRole<string, CustomUserRole>
    {
        #region <Constructors>

        public CustomRole() { }
        public CustomRole(string name) 
        { 
            Name = name; 
        }

        #endregion
    }

Solution 4

If you drill down through the abstractions of the IdentityDbContext you'll find that it looks just like your derived DbContext. The easiest route is Olav's answer, but if you want more control over what's getting created and a little less dependency on the Identity packages have a look at my question and answer here. There's a code example if you follow the link, but in summary you just add the required DbSets to your own DbContext subclass.

Share:
143,451

Related videos on Youtube

PussInBoots
Author by

PussInBoots

Fullstack. Anything web. But mostly .NET, MVC, Razor Pages, C#, HTML, CSS, JavaScript, Docker, Kubernetes, Azure etc.

Updated on July 29, 2022

Comments

  • PussInBoots
    PussInBoots over 1 year

    A default MVC 5 App comes with this piece of code in IdentityModels.cs - this piece of code is for all the ASP.NET Identity operations for the default templates:

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

    If I scaffold a new controller using views with Entity Framework and create a "New data context..." in the dialog, I get this generated for me:

    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Linq;
    using System.Web;
    
    namespace WebApplication1.Models
    {
        public class AllTheOtherStuffDbContext : DbContext
        {
            // You can add custom code to this file. Changes will not be overwritten.
            // 
            // If you want Entity Framework to drop and regenerate your database
            // automatically whenever you change your model schema, please use data migrations.
            // For more information refer to the documentation:
            // http://msdn.microsoft.com/en-us/data/jj591621.aspx
    
            public AllTheOtherStuffDbContext() : base("name=AllTheOtherStuffDbContext")
            {
            }
    
            public System.Data.Entity.DbSet<WebApplication1.Models.Movie> Movies { get; set; }
    
        }
    } 
    

    If I scaffold another controller + view using EF, say for instance for an Animal model, this new line would get autogenerated right under public System.Data.Entity.DbSet<WebApplication1.Models.Movie> Movies { get; set; } - like this:

    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Linq;
    using System.Web;
    
    namespace WebApplication1.Models
    {
        public class AllTheOtherStuffDbContext : DbContext
        {
            // You can add custom code to this file. Changes will not be overwritten.
            // 
            // If you want Entity Framework to drop and regenerate your database
            // automatically whenever you change your model schema, please use data migrations.
            // For more information refer to the documentation:
            // http://msdn.microsoft.com/en-us/data/jj591621.aspx
    
            public AllTheOtherStuffDbContext() : base("name=AllTheOtherStuffDbContext")
            {
            }
    
            public System.Data.Entity.DbSet<WebApplication1.Models.Movie> Movies { get; set; }
            public System.Data.Entity.DbSet<WebApplication1.Models.Animal> Animals { get; set; }
    
        }
    } 
    

    ApplicationDbContext (for all the ASP.NET Identity stuff) inherits from IdentityDbContext which in turn inherits from DbContext. AllOtherStuffDbContext (for my own stuff) inherits from DbContext.

    So my question is:

    Which of these two (ApplicationDbContext and AllOtherStuffDbContext) should I use for all my other own models? Or should I just use the default autogenerated ApplicationDbContext since it shouldn't be a problem using it since it derives from the base class DbContext, or will there be some overhead? You should use only one DbContext object in your app for all your models (I've read this somewhere) so I should not even consider using both ApplicationDbContext and AllOtherStuffDbContext in a single app? Or what is best practice in MVC 5 with ASP.NET Identity?

    • PussInBoots
      PussInBoots over 10 years
      By the way; this is superugly and is unnecessary for my eyes while scanning the document: public System.Data.Entity.DbSet<WebApplication1.Models.Movie> Movies { get; set; } - the System.Data.Entity and WebApplication1.Models part. Can't it be removed from the declaration and instead add the namespaces in the using statements section?
    • SB2055
      SB2055 over 10 years
      Puss - yes to your comment. That should work just fine.
    • Stanislav Prusac
      Stanislav Prusac over 8 years
      This is a good and working, example (MVC 6) and lib of implementation with ASP.NET 5 Identity (>= v3) framework without Entity Framework for MongoDB.Driver (>= v2.1.0) github.com/saan800/SaanSoft.AspNet.Identity3.MongoDB
  • Dave
    Dave about 10 years
    That is for a single MVC5 project but not desirable when the derived DbContext is shared amongst multiple projects, some not MVC5, where some do not need the Identity support.
  • anIBMer
    anIBMer about 10 years
    Voted for same database for easier maintainability and better relational integrity. Because the user entity and role entity will be related into other application objects easily.
  • RickAndMSFT
    RickAndMSFT about 9 years
    @Dave - It complicates partitioning of user data by using two different contexts. Does your MVC app partition data by user but the other apps don't. Sharing the same data layer is common, but I don't think it's common that some projects need the data portioned by user, and some don't.
  • Mike Devenney
    Mike Devenney about 9 years
    Is anyone aware of a how-to on extracting the ApplicationDBContext from an MVC project and including it in an existing EF data layer? Merging the two, as described above seems to be the right approach, but I'm working on a time constrained project. I want to do it right the first time through but would love a heads up on all the gotchas that lie in front of me...
  • SeanOB
    SeanOB about 9 years
    After looking for about an hour this answer pointed me in the right direction - but I wasn't sure how to implement it (for a very literal person me). So if it helps someone else, I found the simplest way is open the IdentityModels.cs, and add your new DbSet in the ApplicationDbContext class.
  • Abhishek
    Abhishek almost 9 years
    Note this would mean you cannot use EFHooks or something else that uses up the inheritance link
  • Saber
    Saber about 8 years
    @mike-devenney Here goes your answer about merging the two context layers, hope it helps.
  • Mike Devenney
    Mike Devenney over 7 years
    Thanks Arvand, I missed this and oddly enough stumbled back across it 1.5 years later while looking into the topic again. : )