5

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?

2
  • Try opening up SQL Profiler and watch what query is sent to the database during identityContext.SaveChanges(); Commented Feb 8, 2014 at 0:39
  • Oh cool, I didn't there was an SQL Profiler. I will check that out. I figured out what my issue was, despite the hours of head-to-desking. I'm going to post my answer soon. Commented Feb 8, 2014 at 1:21

1 Answer 1

8

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));
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

I spent also a lot of time trying to figure out why the user is not created :) Instead of just checking the return value. Maybe throwing an exception would have been more appropriate in this case.
Thanks, helped me to figure out my problem: I've tried adding a user with a password that did not fullfil the password requirements, so the user was not created at all without any notice.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.