Friday, November 19, 2021

Seed Users And Roles Data In ASP.NET Core Identity

 ASP.NET Core identity allows you to implement authentication and authorization for your web applications. While working with ASP.NET Core Identity at times you need to create default user accounts and roles in the system. In ASP.NET MVC you could have easily done this in Global.asax and Application_Start event handler. In ASP.NET Core the process is bit different since the application startup process is different. To that end this article explains a way to seed such user and roles data in your applications.

Before you go any further ensure that you have a web application configured to use ASP.NET Core Identity. I am going to assume that you have followed my earlier article here to implement ASP.NET Core Identity in your web application. The article just mentioned is for ASP.NET Core 1.x but most of the part is same under ASP.NET Core 2.0. So, it should be an easy upgrade for you. Of course, you can use your own implementation also.

If you followed my article mentioned above, you already have MyIdentityDbContext, MyIdentityUser and MyIdentityRole classes ready with you. Our aim is to create a few user accounts and a few roles when the application runs for the first time. Obviously, we do this only if those users and roles doesn't exists in the database yet.

In the following sections we discuss two ways of seeding the data :

  • Using Configure() method
  • Using Main() method

The former method is simple and straightforward with minimal coding. Just like you inject objects in other parts of your application, you can inject them into the Configure() method also. The later approach is bit complex and requires you to shift the seeding operation to the Main() from Program.cs.

Ok. Let's get going!

Modify your Configure() method signature as shown below :

public void Configure(IApplicationBuilder app, 
IHostingEnvironment env, 
UserManager<MyIdentityUser> userManager, 
RoleManager<MyIdentityRole> roleManager)
{
  ...
  ...
}

As you can see we now inject UserManager and RoleManager into the Configure() method. This way you can use UserManager to create user accounts and RoleManager to create roles at the application startup. Instead of writing code directly inside the Configure() method, we will isolate it in a separate class and then call it from here.

So, add a new class called MyIdentityDataInitializer into the project. The following code shows the skeleton of this class :

public static class MyIdentityDataInitializer
{
    public static void SeedData
(UserManager<MyIdentityUser> userManager, 
RoleManager<MyIdentityRole> roleManager)
    {
    }

    public static void SeedUsers
(UserManager<MyIdentityUser> userManager)
    {
    }

    public static void SeedRoles
(RoleManager<MyIdentityRole> roleManager)
    {
    }
}

The MyIdentityDataInitializer class has three static methods - SeedRoles(), SeedUsers() and SeedData(). If you wish to create both - users and roles - then you would call SeedData(). You can also create only users or only roles using the respective methods.

Now, let's see the code that goes inside these methods. This code should be familiar to you because you already wrote something similar in the AccountController (see my earlier article here for example).

The SeedRoles() creates desired default roles in the system and looks like this :

public static void SeedRoles
(RoleManager<MyIdentityRole> roleManager)
{
    if (!roleManager.RoleExistsAsync
("NormalUser").Result)
    {
        MyIdentityRole role = new MyIdentityRole();
        role.Name = "NormalUser";
        role.Description = "Perform normal operations.";
        IdentityResult roleResult = roleManager.
        CreateAsync(role).Result;
    }


    if (!roleManager.RoleExistsAsync
("Administrator").Result)
    {
        MyIdentityRole role = new MyIdentityRole();
        role.Name = "Administrator";
        role.Description = "Perform all the operations.";
        IdentityResult roleResult = roleManager.
        CreateAsync(role).Result;
    }
}

The SeedRoles() method accepts RoleManager as its parameter. Inside, it creates two roles in the system - NormalUser and Administrator. You should change the role names and their description as per your need. Note that we first check whether a role already exists or not. If it doesn't exist only then we create it.

The SeedUsers() creates desired default user accounts and looks like this :

public static void SeedUsers
(UserManager<MyIdentityUser> userManager)
{
    if (userManager.FindByNameAsync
("user1").Result == null)
    {
        MyIdentityUser user = new MyIdentityUser();
        user.UserName = "user1";
        user.Email = "user1@localhost";
        user.FullName = "Nancy Davolio";
        user.BirthDate = new DateTime(1960, 1, 1);

        IdentityResult result = userManager.CreateAsync
        (user, "password_goes_here").Result;

        if (result.Succeeded)
        {
            userManager.AddToRoleAsync(user,
                                "NormalUser").Wait();
        }
    }


    if (userManager.FindByNameAsync
("user2").Result == null)
    {
        MyIdentityUser user = new MyIdentityUser();
        user.UserName = "user2";
        user.Email = "user2@localhost";
        user.FullName = "Mark Smith";
        user.BirthDate = new DateTime(1965, 1, 1);

        IdentityResult result = userManager.CreateAsync
        (user, "password_goes_here").Result;

        if (result.Succeeded)
        {
            userManager.AddToRoleAsync(user,
                                "Administrator").Wait();
        }
    }
}

The SeedUsers() method accepts a UserManager and creates two users - user1 and user2. Notice that we first check whether a user with the same name exists or not. If it doesn't exist then we create it with default values of l, full name and birth date. Change these values as per your requirement. Also notice that the user accounts have been assigned certain default roles.

The SeedUsers() and SeedRoles() don't return any value. But you can change the signature to return some success or failure status if you so wish.

The SeedData() basically calls SeedRoles() and SeedUsers() and looks like this : 

public static void SeedData
(UserManager<MyIdentityUser> userManager, 
RoleManager<MyIdentityRole> roleManager)
{
    SeedRoles(roleManager);
    SeedUsers(userManager);
}

The SeedData() method accepts a UserManager and a RoleManager. Inside, it calls SeedRoles() and SeedUsers(). Note that SeedRoles() is called first because SeedUsers() assigns certain roles to the users being added and those roles must exist in the system prior to adding the users.

Seeding data in Configure()

Finally, it's time to use the MyIdentityDataInitializer class. Open Startup class, go to Configure() method and add this line of code :

public void Configure(IApplicationBuilder app, 
IHostingEnvironment env, 
UserManager<MyIdentityUser> userManager, 
RoleManager<MyIdentityRole> roleManager)
{
    ...
    app.UseAuthentication();

    MyIdentityDataInitializer.SeedData(userManager, roleManager);

    app.UseStaticFiles();

    app.UseMvc(routes =>
    {
      routes.MapRoute(
      name: "default",
      template: "{controller=Home}/{action=Index}/{id?}");
    });
}

As you can see after calling UseAuthentication() we call SeedData() on MyIdentityDataInitializer.

Run the application and see whether the users get created in the database or not. The following figure shows these sample user accounts getting created in the database :

Seeding data in Main()

In the preceding code you created default users and roles in the Configure() method. You can also do that work in Main() method. You might want to use this later approach if you are also seeding data for EF core (won't go into those details here). The following code from Program.cs shows how the Main() code looks like :

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

    using (var scope = host.Services.CreateScope())
    {
        var serviceProvider = scope.ServiceProvider;
        try
        {
            var userManager = serviceProvider.
GetRequiredService<UserManager<MyIdentityUser>>();

            var roleManager = serviceProvider.
GetRequiredService<RoleManager<MyIdentityRole>>();

            MyIdentityDataInitializer.SeedData
(userManager, roleManager);
        }
        catch
        {
                   
        }
    }
    host.Run();
}

Here, instead of injecting the UserManager and RoleManager into Configure() you  grab them from GetRequiredService() method. The userManager and roleManager objects are then passed to SeedData() as before.

No comments:

Post a Comment

No String Argument Constructor/Factory Method to Deserialize From String Value

  In this short article, we will cover in-depth the   JsonMappingException: no String-argument constructor/factory method to deserialize fro...