Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Identity Framework seed method does not save newly created user when called from Global.asax.cs

I am trying to seed an "admin" account in Identity framework during application start up. The majority of our application is not set up through code-first Entity framework models, so I need to do this without extending one of the IDatabaseInitializer classes. I am using the same database as these database-first models.

This is an ASP.NET MVC 5 application.

In Global.asax.cs, I have the following relevant code.

using (var context = new IdentityContext(EnvironmentSettings.Current.DatabaseConnections.CreateDbConnection("Extranet").ConnectionString))
{
    context.SeedAdminAccount(EnvironmentSettings.DefaultAdminAccount.UserName, EnvironmentSettings.DefaultAdminAccount.Password).Wait();
}

The connection string is an Azure SQL server. The username is an email address, and the password is a string of characters, including a bang.

The class IdentityContext looks like this.

public class IdentityContext : IdentityDbContext<IdentityUser>
{
    public IdentityContext(string connectionString) : base(connectionString)
    {
        Debug.WriteLine(connectionString);
        Initialize();
    }

    void Initialize()
    {
        Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
        Database.SetInitializer<IdentityContext>(new CreateInitializer());
    }

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

        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

        modelBuilder.Entity<IdentityUser>().ToTable("IdentityUser", "dbo");
        modelBuilder.Entity<IdentityRole>().ToTable("IdentityRole", "dbo");
        modelBuilder.Entity<IdentityUserClaim>().ToTable("IdentityUserClaim", "dbo");
        modelBuilder.Entity<IdentityUserLogin>().ToTable("IdentityUserLogin", "dbo");
        modelBuilder.Entity<IdentityUserRole>().ToTable("IdentityUserRole", "dbo");
    }
}

context.SeedAdminAccount() is an extension of IdentityContext. It looks like this.

public static class IdentityContextExtensions
{
    public static async Task SeedAdminAccount(this IdentityContext identityContext, string username, string password)
    {
        var userManager = new UserManager<IdentityUser>(new UserStore<IdentityUser>(identityContext));

        //var user = await userManager.FindAsync(EnvironmentSettings.DefaultAdminAccount.UserName, EnvironmentSettings.DefaultAdminAccount.Password);
        var user = await userManager.FindAsync(username, password);

        if (user != null) return;

        user = new IdentityUser() { UserName = username };

        var role = new IdentityUserRole { Role = new IdentityRole(Role.Admin) };
        user.Roles.Add(role);

        await userManager.CreateAsync(user, password);

        identityContext.SaveChanges();
    }
}

And lastly, CreateInitializer looks like this (although it is not called).

public class CreateInitializer : CreateDatabaseIfNotExists<IdentityContext>
{
    protected override async void Seed(IdentityContext context)
    {
        var user = new
        {
            EnvironmentSettings.DefaultAdminAccount.UserName,
            EnvironmentSettings.DefaultAdminAccount.Password
        };

        await context.SeedAdminAccount(user.UserName, user.Password);

        base.Seed(context);
    }
}

Okay, so with all of that out of the way, here's what works:

1) Application startup successfully creates an instance of IdentityContext.

2) Identity framework creates the correct tables with the modified table names.

3) SeedAdminAccount() is called with the correct parameters.

4) userManager.FindAsync() does not find the user (because it doesn't exist).

5) SeedAdminAccount() continues to each statement in its body and returns successfully.

Here's where I'm stuck:

1) Although it appears that my seed method is working correctly, no rows are saved to the IdentityUser table, or any other Identity framework tables.

2) If I use the same code from a controller action, a user is created and stored in the IdentityUser table successfully.

What am I missing here? Am I using the wrong context during application start up? Is there some sort of exception happening that I can't see?

like image 876
aholmes Avatar asked Feb 07 '14 22:02

aholmes


1 Answers

Being at a complete loss, I decided to check the return value of userManager.CreateAsync(). I noticed the Errors field was non-empty, with the error being "The name specified contains invalid characters".

Turns out, I forgot to overload UserValidator to use my EmailUserValidator class in the SeedAdminAccount() method.

I also changed how I'm storing roles. This is what my seed method looks like now.

public static void SeedAdminAccount(this IdentityContext identityContext, string username, string password)
{
    var userManager = new UserManager<IdentityUser>(new UserStore<IdentityUser>(identityContext));
    userManager.UserValidator = new EmailUserValidator<IdentityUser>(userManager);

    var user = userManager.Find(username, password);

    if (user != null) return;

    SeedUserRoles(identityContext);

    user = new IdentityUser() { UserName = username };

    var result = userManager.Create(user, password);

    if (result.Succeeded)
    {
        userManager.AddToRole(user.Id, Role.Administrator);
    }
    else
    {
        var e = new Exception("Could not add default account.");

        var enumerator = result.Errors.GetEnumerator();
        foreach(var error in result.Errors)
        {
            e.Data.Add(enumerator.Current, error);
        }

        throw e;
    }
}

public static void SeedUserRoles(this IdentityContext identityContext)
{
    var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(identityContext));

    foreach(var role in Role.Roles)
    {
        var roleExists = roleManager.RoleExists(role);

        if (roleExists) continue;

        roleManager.Create(new IdentityRole(role));
    }
}
like image 165
aholmes Avatar answered Sep 21 '22 00:09

aholmes