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:
Does anyone know what I am doing wrong?
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.