DbInitializer dotnet core 2.0

Seed database with users and roles in dotnet core 2.0

In this post I will explain how to seed database users roles dotnet core 2.0 ef. For this we use the UserManager and RoleManager of the AspNetCore Identity framework. In that way, your application already has a default user and role for in example logging into the application. You can use it for test data but also for new installations of your application. In my case I will create a default Administrator user with the Administrator role attached to it for new installations of the application.

Seeding a database in dotnet core 2.0 is different than earlier versions. I followed the basics of the new documentation of Microsoft to get to a good solution. So I first created a new project with the authentication set to Individual Accounts.

Program.cs

In the program.cs class I changed the code to the following:

public class Program
{
    public static void Main(string[] args)
    {
        var host = BuildWebHost(args);

        using (var scope = host.Services.CreateScope())
        {
            var services = scope.ServiceProvider;
            try
            {
                var context = services.GetRequiredService<ApplicationDbContext>();
                var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
                var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();

                var dbInitializerLogger = services.GetRequiredService<ILogger<DbInitializer>>();
                DbInitializer.Initialize(context, userManager, roleManager, dbInitializerLogger).Wait();
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred while seeding the database.");
            }
        }

        host.Run();
    }

    public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .Build();
}

This is almost the same code as the documentation but I also injected a RoleManager and UserManager so we can create the default user and role for the application. When the instances are created the Initialize method of the DbInitializer is called. This can be used to seed your database.

Because I want to know more information about my users than just their email and username, I extended the ApplicationUser object that is used to create a user in the database with the UserManaer.

// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
{

    public ApplicationUser()
        : base()
    {
    }
   
    public ApplicationUser(string userName, string firstName, string lastName, DateTime birthDay)
        : base(userName)
    {
        base.Email = userName;

        this.FirstName = firstName;
        this.LastName = lastName;
        this.BirthDay = birthDay;

    }

    [Required]
    [StringLength(50)]
    public string FirstName { get; set; }

    [Required]
    [StringLength(50)]
    public string LastName { get; set; }

    [Required]
    [DataType(DataType.Date)]
    public DateTime BirthDay { get; set; }

    public string FullName => $"{this.FirstName} {this.LastName}";
}

I like constructors for new objects so I will use the long constructor to create my default user.

DbInitializer

The DbInitializer is the class that is used to seed the database. It is created by the program class. It has a static method Initialize that will be used to pass the external dependencies. The Initialize method will be used to orchestrate all the actions.

public class DbInitializer
{
    public static async Task Initialize(ApplicationDbContext context, UserManager<ApplicationUser> userManager,
        RoleManager<IdentityRole> roleManager, ILogger<DbInitializer> logger)
    {
        context.Database.EnsureCreated();

        // Look for any users.
        if (context.Users.Any())
        {
            return; // DB has been seeded
        }

        await CreateDefaultUserAndRoleForApplication(userManager, roleManager, logger);
    }

    private static async Task CreateDefaultUserAndRoleForApplication(UserManager<ApplicationUser> um, RoleManager<IdentityRole> rm, ILogger<DbInitializer> logger)
    {
        const string administratorRole = "Administrator";
        const string email = "noreply@your-domain.com";

        await CreateDefaultAdministratorRole(rm, logger, administratorRole);
        var user = await CreateDefaultUser(um, logger, email);
        await SetPasswordForDefaultUser(um, logger, email, user);
        await AddDefaultRoleToDefaultUser(um, logger, email, administratorRole, user);
    }

    private static async Task CreateDefaultAdministratorRole(RoleManager<IdentityRole> rm, ILogger<DbInitializer> logger, string administratorRole)
    {
        logger.LogInformation($"Create the role `{administratorRole}` for application");
        var ir = await rm.CreateAsync(new IdentityRole(administratorRole));
        if (ir.Succeeded)
        {
            logger.LogDebug($"Created the role `{administratorRole}` successfully");
        }
        else
        {
            var exception = new ApplicationException($"Default role `{administratorRole}` cannot be created");
            logger.LogError(exception, GetIdentiryErrorsInCommaSeperatedList(ir));
            throw exception;
        }
    }

    private static async Task<ApplicationUser> CreateDefaultUser(UserManager<ApplicationUser> um, ILogger<DbInitializer> logger, string email)
    {
        logger.LogInformation($"Create default user with email `{email}` for application");
        var user = new ApplicationUser(email, "First", "Last", new DateTime(1970, 1, 1));

        var ir = await um.CreateAsync(user);
        if (ir.Succeeded)
        {
            logger.LogDebug($"Created default user `{email}` successfully");
        }
        else
        {
            var exception = new ApplicationException($"Default user `{email}` cannot be created");
            logger.LogError(exception, GetIdentiryErrorsInCommaSeperatedList(ir));
            throw exception;
        }

        var createdUser = await um.FindByEmailAsync(email);
        return createdUser;
    }

    private static async Task SetPasswordForDefaultUser(UserManager<ApplicationUser> um, ILogger<DbInitializer> logger, string email, ApplicationUser user)
    {
        logger.LogInformation($"Set password for default user `{email}`");
        const string password = "YourPassword01!";
        var ir = await um.AddPasswordAsync(user, password);
        if (ir.Succeeded)
        {
            logger.LogTrace($"Set password `{password}` for default user `{email}` successfully");
        }
        else
        {
            var exception = new ApplicationException($"Password for the user `{email}` cannot be set");
            logger.LogError(exception, GetIdentiryErrorsInCommaSeperatedList(ir));
            throw exception;
        }
    }

    private static async Task AddDefaultRoleToDefaultUser(UserManager<ApplicationUser> um, ILogger<DbInitializer> logger, string email, string administratorRole, ApplicationUser user)
    {
        logger.LogInformation($"Add default user `{email}` to role '{administratorRole}'");
        var ir = await um.AddToRoleAsync(user, administratorRole);
        if (ir.Succeeded)
        {
            logger.LogDebug($"Added the role '{administratorRole}' to default user `{email}` successfully");
        }
        else
        {
            var exception = new ApplicationException($"The role `{administratorRole}` cannot be set for the user `{email}`");
            logger.LogError(exception, GetIdentiryErrorsInCommaSeperatedList(ir));
            throw exception;
        }
    }

    private static string GetIdentiryErrorsInCommaSeperatedList(IdentityResult ir)
    {
        string errors = null;
        foreach (var identityError in ir.Errors)
        {
            errors += identityError.Description;
            errors += ", ";
        }
        return errors;
    }
}

First a new Administrator role is created. In my application this is the highest role a user can have. When the role is created, a new default user is created with the earlier provided constructor. After the user is created, the user needs a password to sign into the application. So a password is set. The last step is to connect the user to the Administrator role so he has all the permissions that he needs.

For logging, I use the default logging extension framework provided by dotnet core. I connected Serilog to the logging extension for better control.

9 thoughts to “Seed database with users and roles in dotnet core 2.0”

    1. Nope. This is not async, and it’s actually a good way to end up deadlocking your application. You should never call `Wait()`. Here it doesn’t do anything for you, anyways, since whether you block here or with each EF operation, this thread is still blocked. You can either use an async Main (C# 7.1) or you should use `ConfigureAwait(false).GetAwaiter().GetResult()` (instead of Wait()). This is still blocking, but safer in terms of deadlocking the thread. To be completely safe, you should actually run the async operation via `Task.Run`. That will use another thread pool thread, but will not block the active thread at all, and won’t deadlock.

  1. I found this post very helpful. I wanted to extend it a bit by requiring the application to have a verified Email address before the user can login. I tried to duplicate the code found in the Register method that was created by the Sample app for authentication, however, I am getting stuck.

    First, I am having trouble getting the CallbackURL. An exception is generated from the URLHelper / Request.Scheme. I never received the Email to confirm the Admin email address. Then, I tried to manually / dynamically generate the individual components of the CallBackURL and successfully made it through without generating an exceptions and received the email confirmation link from the app. Nevertheless, the email link, when launched, generated a null reference exception. If I register a User, the Email link works fine.

    What would you recommend in my approach to require Email Confirmation from the Database Seeder Process?

    public class EmailController : Controller
    {
    public async Task EmailRegister(ILogger logger, IEmailSender _emailSender, UserManager um, ApplicationUser user)
    {

    var myUser = new ApplicationUser
    {
    UserName = user.Email,
    Email = user.Email,
    Id = user.Id
    };

    if (ModelState.IsValid)
    {

    if (myUser != null)
    {

    var code = await um.GenerateEmailConfirmationTokenAsync(user);

    try
    {
    var callbackUrl = Url.EmailConfirmationLink(myUser.Id, code, Request.Scheme);
    await _emailSender.SendEmailConfirmationAsync(myUser.Email, callbackUrl);
    }
    catch (Exception)
    {

    var exception = new ApplicationException($”Email Sender `{user.Email}` is not verified”);
    logger.LogError(exception, “”);
    throw exception;
    }

    //if (code != null)
    //{

    // string url = “http://localhost:49877/Account/ConfirmEmail?userId=”;
    // var callbackUrl = $”{url}{myUser.Id}&code={code}”;
    // await _emailSender.SendEmailConfirmationAsync(myUser.Email, callbackUrl);
    //}
    //else
    //{
    // var exception = new ApplicationException($”Email Sender `{user.Email}` is not verified”);
    // logger.LogError(exception, “”);
    // throw exception;
    //}
    }
    }
    }
    }

  2. Bedankt! I was wondering why you decided to split out the following line: BuildWebHost(args).Run(); into

    var host = BuildWebHost(args);

    // initilization code here

    host.Run()

    As you don’t reference the host variable in the initialization code, does it serve a logical purpose to split out the code like that?

    Greetings
    Tom

    1. Nevermind, you can delete my question. I somehow did not notice the use of the host variable in the code!

      Related though: would the Main() method in Program.cs be be best practive to run the initialization code instead of Startup.cs?

      1. You need to use the main method because it is the start of the application. If you need extra services in startup, you can add them here.

Leave a Reply