I have an ASP.NET Core 2.1.0 application using EF Core 2.1.0.
How do I go about seeding the database with Admin user and give him/her an Admin role? I cannot find an
ASP.Net Core 3.1
That's how I do it using the EntityTypeBuilder
:
Role Configuration:
public class RoleConfiguration : IEntityTypeConfiguration<IdentityRole>
{
private const string adminId = "2301D884-221A-4E7D-B509-0113DCC043E1";
private const string employeeId = "7D9B7113-A8F8-4035-99A7-A20DD400F6A3";
private const string sellerId = "78A7570F-3CE5-48BA-9461-80283ED1D94D";
private const string customerId = "01B168FE-810B-432D-9010-233BA0B380E9";
public void Configure(EntityTypeBuilder<IdentityRole> builder)
{
builder.HasData(
new IdentityRole
{
Id = adminId,
Name = "Administrator",
NormalizedName = "ADMINISTRATOR"
},
new IdentityRole
{
Id = employeeId,
Name = "Employee",
NormalizedName = "EMPLOYEE"
},
new IdentityRole
{
Id = sellerId,
Name = "Seller",
NormalizedName = "SELLER"
},
new IdentityRole
{
Id = customerId,
Name = "Customer",
NormalizedName = "CUSTOMER"
}
);
}
}
User Configuration:
public class AdminConfiguration : IEntityTypeConfiguration<ApplicationUser>
{
private const string adminId = "B22698B8-42A2-4115-9631-1C2D1E2AC5F7";
public void Configure(EntityTypeBuilder<ApplicationUser> builder)
{
var admin = new ApplicationUser
{
Id = adminId,
UserName = "masteradmin",
NormalizedUserName = "MASTERADMIN",
FirstName = "Master",
LastName = "Admin",
Email = "Admin@Admin.com",
NormalizedEmail = "ADMIN@ADMIN.COM",
PhoneNumber = "XXXXXXXXXXXXX",
EmailConfirmed = true,
PhoneNumberConfirmed = true,
BirthDate = new DateTime(1980,1,1),
SecurityStamp = new Guid().ToString("D"),
UserType = UserType.Administrator
};
admin.PasswordHash = PassGenerate(admin);
builder.HasData(admin);
}
public string PassGenerate(ApplicationUser user)
{
var passHash = new PasswordHasher<ApplicationUser>();
return passHash.HashPassword(user, "password");
}
}
Assigning Roles To Users:
public class UsersWithRolesConfig : IEntityTypeConfiguration<IdentityUserRole<string>>
{
private const string adminUserId = "B22698B8-42A2-4115-9631-1C2D1E2AC5F7";
private const string adminRoleId = "2301D884-221A-4E7D-B509-0113DCC043E1";
public void Configure(EntityTypeBuilder<IdentityUserRole<string>> builder)
{
IdentityUserRole<string> iur = new IdentityUserRole<string>
{
RoleId = adminRoleId,
UserId = adminUserId
};
builder.HasData(iur);
}
}
Finally in the DB Context class:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//If you have alot of data configurations you can use this (works from ASP.Net core 2.2):
//This will pick up all configurations that are defined in the assembly
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
//Instead of this:
modelBuilder.ApplyConfiguration(new RoleConfiguration());
modelBuilder.ApplyConfiguration(new AdminConfiguration());
modelBuilder.ApplyConfiguration(new UsersWithRolesConfig());
}
The below worked for me without any other configuration, check the comments. The IDs have to be predefined to avoid seeding a new user with an new role named Administrator everytime OnModelCreating() method is executed. I am using AspNetCore 3.1.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//Seeding a 'Administrator' role to AspNetRoles table
modelBuilder.Entity<IdentityRole>().HasData(new IdentityRole {Id = "2c5e174e-3b0e-446f-86af-483d56fd7210", Name = "Administrator", NormalizedName = "ADMINISTRATOR".ToUpper() });
//a hasher to hash the password before seeding the user to the db
var hasher = new PasswordHasher<IdentityUser>();
//Seeding the User to AspNetUsers table
modelBuilder.Entity<IdentityUser>().HasData(
new IdentityUser
{
Id = "8e445865-a24d-4543-a6c6-9443d048cdb9", // primary key
UserName = "myuser",
NormalizedUserName = "MYUSER",
PasswordHash = hasher.HashPassword(null, "Pa$$w0rd")
}
);
//Seeding the relation between our user and role to AspNetUserRoles table
modelBuilder.Entity<IdentityUserRole<string>>().HasData(
new IdentityUserRole<string>
{
RoleId = "2c5e174e-3b0e-446f-86af-483d56fd7210",
UserId = "8e445865-a24d-4543-a6c6-9443d048cdb9"
}
);
}
As user cannot be seeded in a normal way in Identity just like other tables are seeded using .HasData()
of .NET Core 2.1.
Microsoft Recommendation: For data that requires calls to external API, such as ASP.NET Core Identity users creation it is recommended to use custom initialization logic.
Seed Roles in .NET Core 2.1 using code given below in ApplicationDbContext
Class :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// 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);
modelBuilder.Entity<IdentityRole>().HasData(new IdentityRole { Name = "Admin", NormalizedName = "Admin".ToUpper() });
}
Seed Users With Roles by Following the steps given below.
Step 1: New class creation
public static class ApplicationDbInitializer
{
public static void SeedUsers(UserManager<IdentityUser> userManager)
{
if (userManager.FindByEmailAsync("abc@xyz.com").Result==null)
{
IdentityUser user = new IdentityUser
{
UserName = "abc@xyz.com",
Email = "abc@xyz.com"
};
IdentityResult result = userManager.CreateAsync(user, "PasswordHere").Result;
if (result.Succeeded)
{
userManager.AddToRoleAsync(user, "Admin").Wait();
}
}
}
}
Step 2: Now Modify ConfigureServices
method in Startup.cs
class.
Before Modification:
services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
After Modification:
services.AddDefaultIdentity<IdentityUser>().AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
Step 3: Modify parameters of Configure
Method in Startup.cs
class.
Before Modification :
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//..........
}
After modification :
public void Configure(IApplicationBuilder app, IHostingEnvironment env, UserManager<IdentityUser> userManager)
{
//..........
}
Step 4 : Calling method of our Seed (ApplicationDbInitializer
) class:
ApplicationDbInitializer.SeedUsers(userManager);
Note: You can also Seed Roles just like users by Injecting the RoleManager
along with UserManager
.
Actually a User
Entity can be seeded in OnModelCreating
, one thing to consider: the ID
s should be predefined. If type string
is used for TKey
identity entities, then there is no problem at all.
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// any guid
const string ADMIN_ID = "a18be9c0-aa65-4af8-bd17-00bd9344e575";
// any guid, but nothing is against to use the same one
const string ROLE_ID = ADMIN_ID;
builder.Entity<IdentityRole>().HasData(new IdentityRole
{
Id = ROLE_ID,
Name = "admin",
NormalizedName = "admin"
});
var hasher = new PasswordHasher<UserEntity>();
builder.Entity<UserEntity>().HasData(new UserEntity
{
Id = ADMIN_ID,
UserName = "admin",
NormalizedUserName = "admin",
Email = "some-admin-email@nonce.fake",
NormalizedEmail = "some-admin-email@nonce.fake",
EmailConfirmed = true,
PasswordHash = hasher.HashPassword(null, "SOME_ADMIN_PLAIN_PASSWORD"),
SecurityStamp = string.Empty
});
builder.Entity<IdentityUserRole<string>>().HasData(new IdentityUserRole<string>
{
RoleId = ROLE_ID,
UserId = ADMIN_ID
});
}
If you are referring to Identity users, the way we did was to add hardcoded values in DbContext.OnModelCreating:
builder.Entity<Role>().HasData(new Role { Id = 2147483645, Name = UserRole.Admin.ToString(), NormalizedName = UserRole.Admin.ToString().ToUpper(), ConcurrencyStamp = "123c90a4-dfcb-4e77-91e9-d390b5b6e21b" });
And user:
builder.Entity<User>().HasData(new User
{
Id = 2147483646,
AccessFailedCount = 0,
PasswordHash = "SomePasswordHashKnownToYou",
LockoutEnabled = true,
FirstName = "AdminFName",
LastName = "AdminLName",
UserName = "admin",
Email = "admin@gmail.com",
EmailConfirmed = true,
InitialPaymentCompleted = true,
MaxUnbalancedTech = 1,
UniqueStamp = "2a1a39ef-ccc0-459d-aa9a-eec077bfdd22",
NormalizedEmail = "ADMIN@GMAIL.COM",
NormalizedUserName = "ADMIN",
TermsOfServiceAccepted = true,
TermsOfServiceAcceptedTimestamp = new DateTime(2018, 3, 24, 7, 42, 35, 10, DateTimeKind.Utc),
SecurityStamp = "ce907fd5-ccb4-4e96-a7ea-45712a14f5ef",
ConcurrencyStamp = "32fe9448-0c6c-43b2-b605-802c19c333a6",
CreatedTime = new DateTime(2018, 3, 24, 7, 42, 35, 10, DateTimeKind.Utc),
LastModified = new DateTime(2018, 3, 24, 7, 42, 35, 10, DateTimeKind.Utc)
});
builder.Entity<UserRoles>().HasData(new UserRoles() { RoleId = 2147483645, UserId = 2147483646 });
I wish there was some better/cleaner way to do it.
Here is how I did it in the end. I created a DbInitializer.cs
class to do the seeding of all my data (including the admin user).
Here's the code for the methods relating to the seeding of the user accounts:
private static async Task CreateRole(RoleManager<IdentityRole> roleManager,
ILogger<DbInitializer> logger, string role)
{
logger.LogInformation($"Create the role `{role}` for application");
IdentityResult result = await roleManager.CreateAsync(new IdentityRole(role));
if (result.Succeeded)
{
logger.LogDebug($"Created the role `{role}` successfully");
}
else
{
ApplicationException exception = new ApplicationException($"Default role `{role}` cannot be created");
logger.LogError(exception, GetIdentiryErrorsInCommaSeperatedList(result));
throw exception;
}
}
private static async Task<ApplicationUser> CreateDefaultUser(UserManager<ApplicationUser> userManager, ILogger<DbInitializer> logger, string displayName, string email)
{
logger.LogInformation($"Create default user with email `{email}` for application");
ApplicationUser user = new ApplicationUser
{
DisplayUsername = displayName,
Email = email,
UserName = email
};
IdentityResult identityResult = await userManager.CreateAsync(user);
if (identityResult.Succeeded)
{
logger.LogDebug($"Created default user `{email}` successfully");
}
else
{
ApplicationException exception = new ApplicationException($"Default user `{email}` cannot be created");
logger.LogError(exception, GetIdentiryErrorsInCommaSeperatedList(identityResult));
throw exception;
}
ApplicationUser createdUser = await userManager.FindByEmailAsync(email);
return createdUser;
}
private static async Task SetPasswordForUser(UserManager<ApplicationUser> userManager, ILogger<DbInitializer> logger, string email, ApplicationUser user, string password)
{
logger.LogInformation($"Set password for default user `{email}`");
IdentityResult identityResult = await userManager.AddPasswordAsync(user, password);
if (identityResult.Succeeded)
{
logger.LogTrace($"Set password `{password}` for default user `{email}` successfully");
}
else
{
ApplicationException exception = new ApplicationException($"Password for the user `{email}` cannot be set");
logger.LogError(exception, GetIdentiryErrorsInCommaSeperatedList(identityResult));
throw exception;
}
}
My Program.cs
looks like this:
public class Program
{
public static async Task Main(string[] args)
{
var host = BuildWebHost(args);
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
Console.WriteLine(services.GetService<IConfiguration>().GetConnectionString("DefaultConnection"));
try
{
var context = services.GetRequiredService<PdContext>();
var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();
var dbInitializerLogger = services.GetRequiredService<ILogger<DbInitializer>>();
await DbInitializer.Initialize(context, userManager, roleManager, dbInitializerLogger);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while migrating the database.");
}
}
host.Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}