Search code examples
entity-frameworkasp.net-coreasp.net-identityidentityserver4

Trying to migrate tables in code for .net core


I am setting up IdentityServer4 with Aspnet.Identity. I followed a tutorial which shows how to create the database and seed in code. I have a method that looks like this:

public static void SeedIdentityServerDatabase(this IApplicationBuilder app)
{
    using (var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
    {
        var serviceProvider = scope.ServiceProvider;
        var persistedGrantDbContext = serviceProvider.GetRequiredService<PersistedGrantDbContext>();
        var configurationDbContext = serviceProvider.GetRequiredService<ConfigurationDbContext>();

        persistedGrantDbContext.Database.Migrate();
        configurationDbContext.Database.Migrate();

        CreateIdentityResources(configurationDbContext);
        CreateApiResources(configurationDbContext);
        CreateClients(configurationDbContext);
    }
}

This works with no issues and creates my database plus the tables / data needed for IdentityServer4.

I am now reading the IdentityServer documentation for implementing with AspNet.Identity but it shows MVC rather than Api and I want to continue doing it the way I have been.

So I updated my DbContext to this

public class DatabaseContext : IdentityDbContext<User>
{
    // ReSharper disable once SuggestBaseTypeForParameter
    public DatabaseContext(DbContextOptions<DatabaseContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);
    }
}

And my User class to this:

public class User : IdentityUser
{
}

My startup now has this:

services.Configure<Config>(Configuration.GetSection("ConnectionStrings"));

var buildServiceProvider = services.BuildServiceProvider();
var config = buildServiceProvider.GetService<IOptions<Config>>();

services.AddDbContext<DatabaseContext>(options => options.UseSqlServer(config.Value.ConnectionString));
services.AddIdentity<User, IdentityRole>().AddEntityFrameworkStores<DatabaseContext>()
    .AddDefaultTokenProviders();

And I tried to update my seed method to this:

public static void SeedIdentityServerDatabase(this IApplicationBuilder app)
{
    using (var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
    {
        var serviceProvider = scope.ServiceProvider;
        var persistedGrantDbContext = serviceProvider.GetRequiredService<PersistedGrantDbContext>();
        var configurationDbContext = serviceProvider.GetRequiredService<ConfigurationDbContext>();
        var databaseContext = serviceProvider.GetRequiredService<DatabaseContext>();

        persistedGrantDbContext.Database.Migrate();
        configurationDbContext.Database.Migrate();
        databaseContext.Database.Migrate();

        CreateIdentityResources(configurationDbContext);
        CreateApiResources(configurationDbContext);
        CreateClients(configurationDbContext);
        CreateUsers(serviceProvider);
    }
}

private static void CreateUsers(IServiceProvider provider)
{
    const string email = "[email protected]";
    var userManager = provider.GetRequiredService<UserManager<User>>();
    var administrator = userManager.FindByEmailAsync(email).Result;

    if (administrator != null) return;

    administrator = new User {UserName = email, Email = email, EmailConfirmed = true};
    var result = userManager.CreateAsync(administrator, "password").Result;

    if (!result.Succeeded)
        throw new Exception(result.Errors.First().Description);

    result = userManager.AddClaimsAsync(administrator, new[]
    {
        new Claim(JwtClaimTypes.Name, "Test Test"),
        new Claim(JwtClaimTypes.GivenName, "Test"),
        new Claim(JwtClaimTypes.FamilyName, "Test"),
        new Claim(JwtClaimTypes.Email, email),
        new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean)
    }).Result;

    if (!result.Succeeded)
        throw new Exception(result.Errors.First().Description);
}

When it gets to the CreateUsers method, it fails stating:

SqlException: Invalid object name 'AspNetUsers'.

Here are the tables it does create:

enter image description here

Does anyone know what I am doing wrong?


Solution

  • I am not happy with the solution....But what I have done is just follow the normal way of doing things. So first of all, mapping any AspNetIdentity tables as I see fit:

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);
    
        builder.Entity<User>(m => m.ToTable("Users"));
        builder.Entity<IdentityRole>(m => m.ToTable("Roles"));
        builder.Entity<IdentityRoleClaim<string>>(m => m.ToTable("RoleClaims"));
        builder.Entity<IdentityUserClaim<string>>(m => m.ToTable("UserClaims"));
        builder.Entity<IdentityUserLogin<string>>(m => m.ToTable("UserLogins"));
        builder.Entity<IdentityUserRole<string>>(m => m.ToTable("UserRoles"));
        builder.Entity<IdentityUserToken<string>>(m => m.ToTable("UserTokens"));
    }
    

    And then calling add-migration:

    add-migration Init -Context DatabaseContext
    

    followed by update-database

    update-database -Context DatabaseContext
    

    Because the commands in the documentation set up 2 DbContexts, I now have mutliple which is why I have to speficy the context in both add-migration and update-database.

    Once I ran these two commands, I then changed my seed method to this:

    public static void SeedIdentityServerDatabase(this IApplicationBuilder app)
    {
        using (var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
        {
            var serviceProvider = scope.ServiceProvider;
            var persistedGrantDbContext = serviceProvider.GetRequiredService<PersistedGrantDbContext>();
            var configurationDbContext = serviceProvider.GetRequiredService<ConfigurationDbContext>();
    
            persistedGrantDbContext.Database.Migrate();
            configurationDbContext.Database.Migrate();
    
            CreateIdentityResources(configurationDbContext);
            CreateApiResources(configurationDbContext);
            CreateClients(configurationDbContext);
            CreateUsers(serviceProvider);
        }
    }
    

    As you can see, I have removed the databaseContext.Database.Migrate(); call as it isn't needed anymore.

    The CreateUsers method now runs and seeds properly.