Monday, October 12, 2026

Extend Identity Users and Roles in ASP.NET with Scaffold tool

In this tutorial, we will learn how to add more data fields to the standard ASP.NET users & roles schema using the scaffold tool. 

Prerequisites

To proceed with this tutorial, you need the following:

  • VS Code
  • .NET: The current version of .NET is 10.0
  • the dotnet-ef tool
  • Tthe Microsoft.dotnet-scaffold tool

Getting Started

Create a new razor pages application with the following terminal window command:

dotnet new razor --auth individual -o XtendUsersRoles
cd XtendUsersRoles
mkdir Models

This creates an ASP.NET app that uses SQLite database for authentication.

Open the application folder in VS Code.

Suppose we want to capture more data about the user, in addition to email and password. Keeping it simple, lwe will extend user data with FirstName & LastName.

An easy way to do this is to create a new class that extends IdentityUser and adds FirstName and LastName properties. In the Models folder, add a new class named XUser to it with the following class code: 

public class XUser : IdentityUser {
  public XUser() : base() { }

  public string? FirstName { get; set; }
  public string? LastName { get; set; }
}

We may also wish to extend the standard roles table with a CreatedDate property.

Just as we did with users, we will create another class for roles that inherits from IdentityRole. In the Models folder, add another class named XRole and add to it the following code:

public class XRole: IdentityRole {

  public XRole() : base() { }

  public XRole(string roleName) : base(roleName) { }

  public DateTime CreatedDate { get; set; }
}

Add the following to Pages/_ViewImports.cshtml:

@using XtendUsersRoles.Models

Edit Data/ApplicationDbContext.cs file and make ApplicationDbContext  inherit from IdentityDbContext<XUser, XRole, string>(options). The ApplicationDbContext class code will look like this:

public class ApplicationDbContext(
    DbContextOptions<ApplicationDbContext> options)
    : IdentityDbContext<XUser, XRole, string>(options)
{

}

Add these passwords to the appsettings.Development.json file:

"Passwords": {
  "Admin": "P@$$w0rd",
  "Member": "P@$$w0rd"
}

We will seed the database with two roles and two users. Add Data/IdentitySeeder.cs with the following code:

public static class IdentitySeeder {
  public static async Task SeedAsync(
      UserManager<XUser> userManager,
      RoleManager<XRole> roleManager,
      IConfiguration configuration) {

      ArgumentNullException.ThrowIfNull(userManager);
      ArgumentNullException.ThrowIfNull(roleManager);
      ArgumentNullException.ThrowIfNull(configuration);

      await EnsureRoleAsync(roleManager, "Admin");
      await EnsureRoleAsync(roleManager, "Member");

      await EnsureUserAsync(
          userManager, configuration["Passwords:Admin"]!, "aa@aa.aa", "Adam", "Atkins", "Admin");

      await EnsureUserAsync(

          userManager, configuration["Passwords:Member"]!, "mm@mm.mm", "Mike", "Moore", "Member");
  }

  private static async Task EnsureRoleAsync(RoleManager<XRole> roleManager, string roleName) {
      if (!await roleManager.RoleExistsAsync(roleName)) {
          var result = await roleManager.CreateAsync(new XRole(roleName) {
              CreatedDate = DateTime.UtcNow
          });

          EnsureSucceeded(result, $"creating role '{roleName}'");
      }
  }

  private static async Task EnsureUserAsync(
      UserManager<XUser> userManager,
      string password,
      string email,
      string firstName,
      string lastName,
      string roleName) {

      var user = await userManager.FindByEmailAsync(email);

      if (user is null) {
          user = new XUser {
              UserName = email,
              Email = email,
              EmailConfirmed = true,
              FirstName = firstName,
              LastName = lastName
          };

          var result = await userManager.CreateAsync(user, password);
          EnsureSucceeded(result, $"creating user '{email}'");
      }

      if (!await userManager.IsInRoleAsync(user, roleName)) {
          var result = await userManager.AddToRoleAsync(user, roleName);
          EnsureSucceeded(result, $"adding user '{email}' to role '{roleName}'");
      }

  }

  private static void EnsureSucceeded(IdentityResult result, string operation) {
      if (!result.Succeeded) {
          var errors = string.Join("; ", result.Errors.Select(error => $"{error.Code}: {error.Description}"));

          throw new InvalidOperationException($"Identity seeding failed while {operation}: {errors}");
      }
  }
}

In the Program.cs, replace the builder.Services.AddDefaultIdentity. . .  statement with this:

builder.Services.AddIdentity<XUser, XRole>(
options => {
    options.Stores.MaxLengthForKeys = 128;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultUI()
.AddDefaultTokenProviders()
.AddRoles<XRole>();

We need to call the identity seed logic from somewhere. In Program.cs, Add the following code to the “if (app.Environment.IsDevelopment())” block, right before “app.UseMigrationsEndPoint()”:

await using var scope = app.Services.CreateAsyncScope();
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
await dbContext.Database.MigrateAsync();

await IdentitySeeder.SeedAsync(
    scope.ServiceProvider.GetRequiredService<UserManager<XUser>>(),
    scope.ServiceProvider.GetRequiredService<RoleManager<XRole>>(),
    app.Configuration);

Edit Pages/Shared/_LoginPartial.cshtml and change:

@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager

TO

@inject SignInManager<XUser> SignInManager
@inject UserManager<XUser> UserManager

Let us start with a clean database and migration. Therefore, delete app.db and the Data/Migrations folder. 

If you do not already have the .NET entity framework tool, execute the following command from within a terminal window:

dotnet tool install -g dotnet-ef

Then, execute the following entity framework migration commands from within a terminal window in the root folder of the application:

dotnet ef migrations add M1 -o Data/Migrations
dotnet ef database update

To seed the database with sample roles and users, start the application with:

dotnet watch

To prove that user and role data are successfully seeded, login with one of the below credentials that were previously seeded:

EmailPassword
aa@aa.aa P@$$w0rd
mm@mm.mm P@$$w0rd

The next task we need to accomplish is to modify the registration page so that the application can capture extended data such as FirstName & LastName. ASP.NET provides ASP.NET Core Identity as a Razor Class Library. This means that the registration UI is baked into the assemblies and is surfaced with the .AddDefaultUI() option with the services.AddIdentity() command in Program.cs.

If you do not already have the .NET scaffolding tool, execute the following command from within a terminal window:

dotnet tool install -g Microsoft.dotnet-scaffold

To expose the identity pages (like LOgin, Register, Logout, etc) and change them, we will use the Scaffold tool. Run the following command from within a terminal window at the root of your project:

dotnet scaffold

Follow these steps to generate the Identity razor pages:


The result of the scaffolding process is that we now have a new folder /Areas/Identity/Pages/Account with many pages that pertains to Identity:

There are, however, some things that the scaffold tool did incorrectly:

  1. It added an ApplicationDbContext connection string in appsettings.json file. Go ahead and delete it.
  2. The scaffold tool also added this line of code into Program.cs, that also needs to be deleted:

builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true).AddEntityFrameworkStores<ApplicationDbContext>();

Edit the code-behind file Areas/Identity/Pages/Account/Register.cshtml.cs. Add the following properties to the InputModel class: 

[Required]
[DataType(DataType.Text)]
[StringLength(50, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 2)]
[Display(Name = "First Name")]
public string? FirstName { get; set; }

[Required]
[DataType(DataType.Text)]
[StringLength(50, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 2)]
[Display(Name = "Last Name")]
public string? LastName { get; set; }

In the same file, delete the CreateUser() method.

In the OnPostAsync() method at around line 128, replace:

var user = CreateUser();

with: 

var user = new XUser {
  UserName = Input.Email,
  Email = Input.Email,
  FirstName = Input.FirstName,
  LastName = Input.LastName
};

In the same Register.cshtml.cs file, replace all instances of ApplicationUser with XUser. Do this also in files Login.cshtml.cs and Logout.cshtml.cs.

Next, let us update the UI. Edit razor page Areas\Identity\Pages\Account\Register.cshtml. Add the following markup to the form right before the email/username block: 

<div class="form-floating mb-3">
    <input asp-for="Input.FirstName" class="form-control" aria-required="true" placeholder="first name" />
    <label asp-for="Input.FirstName">First Name</label>
    <span asp-validation-for="Input.FirstName" class="text-danger"></span>
</div>

<div class="form-floating mb-3">
    <input asp-for="Input.LastName" class="form-control" aria-required="true" placeholder="last name" />
    <label asp-for="Input.LastName">Last Name</label>
    <span asp-validation-for="Input.LastName" class="text-danger"></span>
</div>

Run the web application and click on the Register button on the top-right side.


After you click on Register, all user data will be saved in the database. We can capture first name and last name as expected. 

We have succeeded in updating the registration page so that additional user data can be captured. Mission accomplished.

No comments:

Post a Comment