Application data
.NET Core ASP.NET Core Identity C# Entity Framework Core Visual Studio

How to Seed Data within your .NET Core Applications

Hi there and welcome to today’s post.

In today’s post I will be discussing how you can seed data into a .NET Core application.

Before I get around to showing how to seed data within a .NET Core application, I will explain what is seeding, and when we may want to seed applications.

What is Seeding?

Seeding is an initialization task within the application that involves pre-populating your application with data that allows your application to minimally function when initially started.

The data that is used for seeding can be obtained from flat files or even a table within a data store such as an SQL database. In some cases, if the data is generic enough and does not change, then default data can be used to populate application tables.

When to use Seeding

We could use seeding in applications when the application we have deployed to server or cloud environment has not been configured with any backend data store. The initial run of the application could be run by an administrator to ensure that the backend data store has been created, and the application data is initialized with pre-populated data. This data can include populated lookups, user accounts, and configuration tables.

Another use case is when the application is a software as a service application that would run with a different backend database for each registered end user that starts their copy of the application. In the data setup and population would include backend database creation, user account creation, data lookup population, and configuration table population.

There are four categories of data that can be seeded in the application:

  1. User account data.
  2. Sample records.
  3. General lookup data.
  4. Configuration data.

I will show examples of each of the above in this post. In the next section I will show how to configure an application for seeding.

Configuring .NET Core Applications for Seeding

There are a few areas that would be helpful for you to understand for the remainder of this post:

  1. Creating a .NET Core application from the Visual Studio project templates.
  2. Entity Framework Core.
  3. ASP.NET Core Identity Framework.

With the population of data lookups within the application, you will need to configure the data context for your backend database.

In the Startup.cs source file, ensure that the data context is configured to your SQL data in ConfigureServices() as shown:

services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(Configuration.GetConnectionString(connStr)));

With the population of user account application data, you will need to configure the data context for your backend database.

Ensure that the identity roles are configured in ConfigureServices() as shown:

services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

More details of configuring your application for identity management can be found in the previous posts where I showed how to create identity applications in .NET Core applications, and how to customize user identity in .NET Core applications.

Next, add a method. Call it Initialize().

public void Initialize(ApplicationDbContext context)
{
	.. 
}

Leave this empty for now.

Seeding User Account Data

To seed user account data, we start with the creation of a class.

Call it SeedAccounts. Include a constructor initially.

public class SeedAccounts
{
        private ApplicationDbContext db;
        private AppConfiguration appConfiguration;

        public SeedAccounts(ApplicationDbContext _db, AppConfiguration _appConfiguration)
        {
            db = _db;
            appConfiguration = _appConfiguration;
        }
	..
}

Note: We can use a custom class to read in the application settings. In a previous post, I showed how to read and uses application settings in .NET Core applications.

Next, add a method. Call it GenerateUserAccounts():

Inside this method, we can access the user store and role manager with:

var userManager = new UserStore<ApplicationUser>(db);
var roleManager = new RoleStore<IdentityRole>(db);

Creating a new role can be done as follows:

// Create Admin Role and Admin User    
if (!roleManager.Roles.Where(a => a.Name == "Admin").Any())
{
      // first we create Admin role 
      var role = new Microsoft.AspNetCore.Identity.IdentityRole();
      role.Name = "Admin";
      role.NormalizedName = role.Name.ToUpper();
      await roleManager.CreateAsync(role);
}

Creating a new user account can be done as follows:

// create Admin user
if (!userManager.Users.Where(a => a.UserName == appConfiguration.AdminEmail).Any())
{
      try
      {
            // Create a Admin super user who will maintain the website
            var user = new ApplicationUser();
            user.UserName = appConfiguration.AdminEmail;
            user.Email = appConfiguration.AdminEmail;
            user.NormalizedEmail = appConfiguration.AdminEmail.ToUpper();
            user.NormalizedUserName = appConfiguration.AdminEmail.ToUpper();
            user.SecurityStamp = Guid.NewGuid().ToString("D");
            var password = new PasswordHasher<ApplicationUser>();
            var hashed = password.HashPassword(user, appConfiguration.AdminPwd);
            user.PasswordHash = hashed;
            var chkUser = await userManager.CreateAsync(user);

            //Add default User to Role Admin   
            if (chkUser.Succeeded)
            {
              	  var adminRole = roleManager.FindByNameAsync("Admin");
                  var adminUser = userManager.FindByEmailAsync(appConfiguration.AdminEmail);
                  if (adminRole != null && adminUser != null)
                  {
                        db.UserRoles.Add(new IdentityUserRole<string>()
                        {
                              RoleId = adminRole.Result.Id.ToString(),
                              UserId = adminUser.Result.Id.ToString()
                        });
                        await db.SaveChangesAsync();
 			      }
 		    }
      }
      catch (Exception ex)
      {
            Console.Write("Error " + ex.Message.ToString());
      }
}

This method uses the CreateAsync() and FindByEmailAsync() async methods of the UserManager class and FindByNameAsync() async methods of the RoleManager class. We also make use of the HashPassword() method of the PasswordHasher class.

To persist changes we use the async SaveChangesAsync() method.

Seeding Sample Data and Lookup Data

To seed sample data, I will create a class SeedData that uses the data context.

An outline of the class is shown below:

public class SeedData
{
   private ApplicationDbContext db;

   public SeedData(ApplicationDbContext _db)
   {
      db = _db;
   }
   ..
   ..
}

Include a constructor parameter for the application data context.

Create a method that will be used to generate some sample data.

Call it GenerateSampleData().

public void GenerateSampleData()
{
   if (db.Books.Count() > 0)
      return;

   try
   {
      BookViewModel bvm = new BookViewModel()
      {

         Title = "The Lord of the Rings",
         Author = "J. R. R. Tolkien",
         Genre = "fantasy",
         YearPublished = 1954,
         Edition = "0",
         ISBN = "",
         Location = "sydney",
         DateCreated = DateTime.Today,
         DateUpdated = DateTime.Today
      };
      db.Add(bvm);
      db.SaveChanges();
   }
   catch (Exception ex)
   {
      Console.WriteLine("Error: " + ex.Message.ToString());
   }
}

Ensure that you have SaveChanges() applied to your data context to ensure changes are persisted.

Sample data could also be read in from a template SQL database, flat files, or from API services.

To run the seed data classes from start up, add the following calls to Initialize():

SeedData seed_data = new SeedData(context);
seed_data.GenerateSampleData();

var appOptions = new AppConfiguration();
            Configuration.GetSection("AppSettings").Bind(appOptions);

SeedAccounts seed_acc = new SeedAccounts(context, appOptions);
var task = Task.Run(async() => await
   seed_acc.GenerateUserAccounts());
var result = task.Wait(5000);

The third type of seeded data, lookup data can be loaded similarly to sample data.

The fourth type of seeded data, configuration data has similarities to sample data population, however the data used for population would be dependent on the application host environment or software registration in a software as a service context.

Note: if there are a number of asynchronous methods used to seed your data, and the application is dependent on the seeded data, then it will make sense to include a delay before returning the result from the method and leaving the start up of the application. Without a delay, the application may try to run without dependent data and give an error on initially starting.

That’s all for this post.

I hope you found it informative and useful.

Social media & sharing icons powered by UltimatelySocial