Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Seeding Identity 2.0 database

I have an ASP.NET MVC 5 project (razor engine) which has Identity 2.0 with Individual User Accounts. I am using Visual Studio Professional 2013

I have not found any clear example (why doesn't it come out of the box?) of HOW I can seed the Identity 2.0 database and all examples I see are half backed because they don't say WHERE exactly you have to implement that.

I used enable-migrations which created a Migrations folder with a Configuration.cs file. It has a Seed method override but when I place a breakpoint it notice it is never executed, in fact the Identity database is not even populated with the schema either.

So where and what I have to do so that the Identity 2.0 schema is created on the database the first time (the connection string is correct and the empty database exists). And how do I rig up the Seeding?

On IdentityModels.cs I have this: public class ApplicationDbContext : IdentityDbContext { ublic ApplicationDbContext(): base("DefaultConnection", throwIfV1Schema: false) { }

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

    protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder) {
          base.OnModelCreating(modelBuilder);
          // to avoid the "has no keys" errors when running Update-Database on PM
          modelBuilder.Entity<IdentityRole>().HasKey<string>(r => r.Id).ToTable("AspNetRoles");
          modelBuilder.Entity<IdentityUser>().ToTable("AspNetUsers");
          modelBuilder.Entity<IdentityUserLogin>().HasKey(l => new { l.UserId, l.LoginProvider, l.ProviderKey }).ToTable("AspNetUserLogins");
          modelBuilder.Entity<IdentityUserRole>().HasKey(r => new { r.RoleId, r.UserId }).ToTable("AspNetUserRoles");
          modelBuilder.Entity<IdentityUserClaim>().ToTable("AspNetUserClaims");
     }
}    

In Migrations/Configuration.cs (added by PM>Enable-Migrations) I have this:

internal sealed class Configuration : DbMigrationsConfiguration<Models.ApplicationDbContext> {
    public Configuration() {
        AutomaticMigrationsEnabled = false;
    }

    protected override void Seed(Models.ApplicationDbContext context) {
         WriteReferenceData();
    }
}

In my Global.asax.cs file on the Application_Start() method I added this:

System.Data.Entity.Database.SetInitializer<Models.ApplicationDbContext>(new System.Data.Entity.MigrateDatabaseToLatestVersion<Models.ApplicationDbContext, Migrations.Configuration>());

And in IdentityConfig.cs I have this DB Initializer as well though it seems to be orphan because I don't know where to plug this in:

public class ApplicationDbInitializer : System.Data.Entity.DropCreateDatabaseIfModelChanges<Models.ApplicationDbContext> {
    protected override void Seed(ApplicationDbContext context) {
        WriteReferenceData();
        base.Seed(context);
    }
}

And finally the WriteReferenceData method is in some other class that does this more or less:

System.Data.Entity.DbContextTransaction transaction = null;
try {
    System.Data.Entity.DbContext ctx = Models.ApplicationDbContext.Create();
    transaction = ctx.Database.BeginTransaction();
    CreateRoles(ctx);
    CreateUsers(ctx);
    CreateRoleAssociations(ctx);
    ctx.SaveChanges();
    transaction.Commit();
    succeeded = true;
}
catch (Exception ex) {
    if (transaction != null { transaction.Rollback(); transaction.Dispose(); }
    succeeed = false;
}
return succeeded;
like image 605
Lord of Scripts Avatar asked Jul 18 '14 17:07

Lord of Scripts


3 Answers

EF has two different Seed methods. One that is used with database initializers and another that is used with Migrations. Since you've enabled Migrations, I'll describe how to do this with the Migrations Seed method here...

First of all, even though you've enabled Migrations, by default EF still uses the CreateDatabaseIfNotExists database initializer. Which means when you run your app, the first time the ApplicationDbContext is accessed, the initializer is called and it'll create the database tables from your Code First mappings if the tables don't exist already. You're not seeing the schema created because you probably haven't accessed the db context. In a new web app, this is typically triggered the first time you register a user or try to log in.

To seed the ASP.NET Identity tables, there are two things you need to do. The first is to add seed logic to the Seed method in Configuration.cs. The second is to trigger update-database... either by running it in the Package Manager Console or by using the MigrateDatabaseToLatestVersion database initializer.

Here's an example of what you can put into the Seed method to create a role and a user...

public Configuration()
{
    AutomaticMigrationsEnabled = true;

    // ...
}

protected override void Seed(MyProject.Web.Models.ApplicationDbContext context)
{
    if (!context.Roles.Any())
    {
        var roleStore = new RoleStore<IdentityRole>(context);
        var roleManager = new RoleManager<IdentityRole>(roleStore);
        var role = new IdentityRole{
            Name = "Administrator"
        };
        roleManager.Create(role);
    }

    if (!context.Users.Any())
    {
        var userStore = new UserStore<ApplicationUser>(context);
        var userManager = new ApplicationUserManager(userStore);

        var user = new ApplicationUser {
            Email = "[email protected]",
            UserName = "SuperUser"
        };
        userManager.Create(user, "MySecretPassword1234");
        userManager.AddToRole(user.Id, "Administrator");
    }
}

After this is done, you can run Update-Database from the Package Manager Console to migrate the database to the latest schema and run the Seed method.

Or you can change EF to use the MigrateDatabaseToLatestVersion initializer by modifying the context in IdentityModels.cs...

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext()
        : base("DefaultConnection", throwIfV1Schema: false)
    {
        Database.SetInitializer<ApplicationDbContext>(new MigrateDatabaseToLatestVersion<ApplicationDbContext, Configuration>());
    }
}

Now when you run the application, the first time the database context is used it'll run update-database and seed it.

like image 133
Anthony Chu Avatar answered Jan 04 '23 13:01

Anthony Chu


I think I can solve the riddle as to WHY the Seed is never triggered: because the Seed is called only when the application is trying to connect to the database, and NOT when the application starts.

I have created examples and I have used your code successfully with and without migrations. But since you want to use it with migrations enabled, below is the sample code.

VERY IMPORTANT: To actually see the breakpoint inside Seed, run the application, press Login and use the credentials from the seed function to get access to the application. In case you get "Invalid username or password", watch out for the manager.PasswordValidator property in IdentityConfig.cs :: Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context).

  1. Create a new ASP.NET MVC 5 project in VS2013

  2. Update all packages using Update-Package command.

  3. Enable-Migrations

  4. In the Configuration.cs created by migrations add the following code:

    internal sealed class Configuration : DbMigrationsConfiguration
        {
            public Configuration()
            {
                AutomaticMigrationsEnabled = false;
            }
    
        protected override void Seed(ApplicationDbContext context)
        {
            bool itWorks = WriteReferenceData(context);
            base.Seed(context);
        }
    
        private bool WriteReferenceData(ApplicationDbContext ctx)
        {
            DbContextTransaction transaction = null;
            bool succeeded = false;
            try
            {
                transaction = ctx.Database.BeginTransaction();
                CreateRoles(ctx);
                CreateUsers(ctx);
                ctx.SaveChanges();
                transaction.Commit();
                succeeded = true;
            }
            catch (Exception ex)
            {
                if (transaction != null) { transaction.Rollback(); transaction.Dispose(); }
                succeeded = false;
            }
            return succeeded;
        }
    
        private void CreateRoles(ApplicationDbContext ctx)
        {
            // Out of the box
            // ctx.Roles.AddOrUpdate(
            //     new IdentityRole { Name = "Administrator" },
            //     new IdentityRole { Name = "Guest" }
            //     );
    
            // Another approach
            var RoleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(ctx));
            var roleName = "Administrator";
    
            //Create role if it does not exist
            if (!RoleManager.RoleExists(roleName))
            {
                var roleresult = RoleManager.Create(new IdentityRole(roleName));
            }
        }
    
        private void CreateUsers(ApplicationDbContext ctx)
        {
            // Out of the box approach
            // ctx.Users.AddOrUpdate(
            //     new ApplicationUser { Email = "[email protected]", UserName = "[email protected]" }
            //     );
    
            // Another approach
            var UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(ctx));
            var user = new ApplicationUser() { UserName = "[email protected]", Email="[email protected]"};
            var password = "Wh@tever777";
            var adminresult = UserManager.Create(user, password);
    
            //Add User Admin to Role Administrator
            if (adminresult.Succeeded)
            {
                var result = UserManager.AddToRole(user.Id, "Administrator");
            }
        }
    }
    
  5. In Global.asax.cs :: Application_Start(), add the following line:

    Database.SetInitializer<ApplicationDbContext>(new MigrateDatabaseToLatestVersion<ApplicationDbContext, Configuration>());

  6. Run!

If you also need a sample of code with migrations disabled, let me know.

like image 23
Corneliu Serediuc Avatar answered Jan 04 '23 13:01

Corneliu Serediuc


So we do something similar in our samples package in the following way (To seed the db with our admin user)

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

    static ApplicationDbContext()
    {
        // Set the database intializer which is run once during application start
        // This seeds the database with admin user credentials and admin role
        Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer());
    }

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

// This is useful if you do not want to tear down the database each time you run the application.
// public class ApplicationDbInitializer : DropCreateDatabaseAlways<ApplicationDbContext>
// This example shows you how to create a new database if the Model changes
public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext> 
{
    protected override void Seed(ApplicationDbContext context) {
        DoYourSeedingHere(context);
        base.Seed(context);
    }

}
like image 24
Hao Kung Avatar answered Jan 04 '23 11:01

Hao Kung