Customizing AspNetRoles Table in ASP.NET Core Identity

Customizing AspNetRoles Table in ASP.NET Core Identity

In this article, I will discuss How to Customize the AspNetRoles Table in ASP.NET Core Identity to add additional columns as per our requirements. Please read our previous article discussing Roles Management, i.e., How to Add, Update, Retrieve, and Delete Roles in ASP.NET Core Identity.

Customizing AspNetRoles Table in ASP.NET Core Identity

Customizing the AspNetRoles table in ASP.NET Core Identity involves extending the default IdentityRole class to include additional properties relevant to your application’s needs. This allows us to add more detailed information about each role, such as descriptions. If you check the ASP.NET Core Identity database, by default, the AspNetRoles table is created with the following columns:

Customizing AspNetRoles Table in ASP.NET Core Identity

The AspNetRoles Table is created from the following IdentityRole model of ASP.NET Core Identity Framework.

How to Customize the AspNetRoles Table in ASP.NET Core Identity

Now, you might want to store more information about roles than what is provided by default. For example, adding a description field to each role can help understand the purpose of each role within the application. To store custom role data like Description, we need to extend the IdentityRole class.

Extend IdentityRole Class in ASP.NET Core

So, we need to create a custom class inheriting from the IdentityRole class. You can give the class that extends the IdentityRole class any name, but it is recommended that you name it ApplicationRole.

So, create a class file named ApplicationRole.cs within the Models folder and copy and paste the following code. As you can see, the ApplicationRole class extends the IdentityRole class, and we have included one property in this class. But you can add any number of properties as per your requirements. These properties will become new columns in the AspNetRoles table.

using Microsoft.AspNetCore.Identity;

namespace ASPNETCoreIdentityDemo.Models
{
    public class ApplicationRole : IdentityRole
    {
        // Add custom properties here
        public string? Description { get; set; }
    }
}
Replacing IdentityRole with ApplicationRole:

Anywhere in your code that you were using IdentityRole, please update it to ApplicationRole.

Modify the DbContext:

In your ApplicationDbContext class, ensure that it uses your new ApplicationRole class instead of IdentityRole. Specify the ApplicationRole class as the generic argument for the IdentityDbContext class. This is how the IdentityDbContext class knows it has to work with our custom role class (in this case, the ApplicationRole class) instead of the default built-in IdentityRole class.

So, open the ApplicationDbContext.cs class file and copy and paste the following code. As you can see, we are specifying the custom ApplicationRole class to the IdentityDbContext generic class. Here, the string parameter in the generic IdentityDbContext type specifies the primary key type for the user and role type.

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace ASPNETCoreIdentityDemo.Models
{
    public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
    }
}
Update Identity Services Configuration

In Program.cs class file, update the Identity services to use ApplicationRole instead of IdentityRole. So, modify the Program class as follows:

using ASPNETCoreIdentityDemo.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using System;

namespace ASPNETCoreIdentityDemo
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.
            builder.Services.AddControllersWithViews();

            //Configure Entity Framework Core
            var connectionString = builder.Configuration.GetConnectionString("SQLServerIdentityConnection") ?? throw new InvalidOperationException("Connection string 'SQLServerIdentityConnection' not found.");
            builder.Services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(connectionString));

            //Configuration Identity Services
            //Configurre the Custom User and Role Class
            builder.Services.AddIdentity<ApplicationUser, ApplicationRole>(
                options =>
                {
                    // Password settings
                    options.Password.RequireDigit = true;
                    options.Password.RequiredLength = 8;
                    options.Password.RequireNonAlphanumeric = true;
                    options.Password.RequireUppercase = true;
                    options.Password.RequireLowercase = true;
                    options.Password.RequiredUniqueChars = 4;

                    // Other settings can be configured here
                })
                .AddEntityFrameworkStores<ApplicationDbContext>();

            // Configure the Application Cookie settings
            builder.Services.ConfigureApplicationCookie(options =>
            {
                // If the LoginPath isn't set, ASP.NET Core defaults the path to /Account/Login.
                options.LoginPath = "/Account/Login"; // Set your login path here
            });

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (!app.Environment.IsDevelopment())
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            //Configuring Authentication Middleware to the Request Pipeline
            app.UseAuthentication();
            app.UseAuthorization();
            app.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");

            app.Run();
        }
    }
}
Modifying the Models to Include Description Property:

Modify the CreateRoleViewModel.cs class file as follows to include the Description property, which we will use while creating a new role:

using System.ComponentModel.DataAnnotations;
namespace ASPNETCoreIdentityDemo.Models
{
    public class CreateRoleViewModel
    {
        [Required]
        [Display(Name = "Role")]
        public string RoleName { get; set; }

        public string? Description { get; set; }
    }
}

Modify the EditRoleViewModel.cs class file as follows to include the Description property, which we will use while updating an existing role:

using System.ComponentModel.DataAnnotations;

namespace ASPNETCoreIdentityDemo.Models
{
    public class EditRoleViewModel
    {
        [Required]
        public string Id { get; set; }
        [Required(ErrorMessage = "Role Name is Required")]
        public string RoleName { get; set; }

        public string? Description { get; set; }
    }
}
Update Identity-Related Views and Controllers

If you have any views or controllers that refer to the IdentityRole class, update them to use the ApplicationRole custom class. Also, please include the Description property while creating and updating the role. So, modify the Administration Controller as follows:

using ASPNETCoreIdentityDemo.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace ASPNETCoreIdentityDemo.Controllers
{
    public class AdministrationController : Controller
    {
        private readonly RoleManager<ApplicationRole> _roleManager;

        public AdministrationController(RoleManager<ApplicationRole> roleManager)
        {
            _roleManager = roleManager;
        }

        [HttpGet]
        public async Task<IActionResult> ListRoles()
        {
            List<ApplicationRole> roles = await _roleManager.Roles.ToListAsync();
            return View(roles);
        }

        [HttpGet]
        public IActionResult CreateRole()
        {
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> CreateRole(CreateRoleViewModel roleModel)
        {
            if (ModelState.IsValid)
            {
                // Check if the role already exists
                bool roleExists = await _roleManager.RoleExistsAsync(roleModel?.RoleName);
                if (roleExists)
                {
                    ModelState.AddModelError("", "Role Already Exists");
                }
                else
                {
                    // Create the role
                    // We just need to specify a unique role name to create a new role
                    ApplicationRole identityRole = new ApplicationRole
                    {
                        Name = roleModel?.RoleName,
                        Description = roleModel?.Description
                    };

                    // Saves the role in the underlying AspNetRoles table
                    IdentityResult result = await _roleManager.CreateAsync(identityRole);

                    if (result.Succeeded)
                    {
                        return RedirectToAction("Index", "Home");
                    }

                    foreach (IdentityError error in result.Errors)
                    {
                        ModelState.AddModelError("", error.Description);
                    }
                }
            }

            return View(roleModel);
        }

        [HttpGet]
        public async Task<IActionResult> EditRole(string roleId)
        {
            //First Get the role information from the database
            ApplicationRole role = await _roleManager.FindByIdAsync(roleId);
            if (role == null)
            {
                // Handle the scenario when the role is not found
                return View("Error");
            }

            //Populate the EditRoleViewModel from the data retrived from the database
            var model = new EditRoleViewModel
            {
                Id = role.Id,
                RoleName = role.Name,
                Description= role.Description
                // You can add other properties here if needed
            };

            return View(model);
        }

        [HttpPost]
        public async Task<IActionResult> EditRole(EditRoleViewModel model)
        {
            if (ModelState.IsValid)
            {
                var role = await _roleManager.FindByIdAsync(model.Id);
                if (role == null)
                {
                    // Handle the scenario when the role is not found
                    ViewBag.ErrorMessage = $"Role with Id = {model.Id} cannot be found";
                    return View("NotFound");
                }
                else
                {
                    role.Name = model.RoleName;
                    role.Description = model.Description;
                    // Update other properties if needed

                    var result = await _roleManager.UpdateAsync(role);
                    if (result.Succeeded)
                    {
                        return RedirectToAction("ListRoles"); // Redirect to the roles list
                    }

                    foreach (var error in result.Errors)
                    {
                        ModelState.AddModelError("", error.Description);
                    }

                    return View(model);
                }
            }

            return View(model);
            
        }

        [HttpPost]
        public async Task<IActionResult> DeleteRole(string roleId)
        {
            var role = await _roleManager.FindByIdAsync(roleId);
            if (role == null)
            {
                // Role not found, handle accordingly
                ViewBag.ErrorMessage = $"Role with Id = {roleId} cannot be found";
                return View("NotFound");
            }

            var result = await _roleManager.DeleteAsync(role);
            if (result.Succeeded)
            {
                // Role deletion successful
                return RedirectToAction("ListRoles"); // Redirect to the roles list page
            }

            foreach (var error in result.Errors)
            {
                ModelState.AddModelError("", error.Description);
            }

            // If we reach here, something went wrong, return to the view
            return View("ListRoles", await _roleManager.Roles.ToListAsync());
        }
    }
}
Customizing Role Related Views:

Modify the CreateRole.cshtml view as follows to include the Description field in the form.

@model CreateRoleViewModel

@{
    ViewBag.Title = "Create New Role";
}

<form asp-action="CreateRole" method="post" class="mt-3">
    <div asp-validation-summary="All" class="text-danger">
    </div>
    <div class="form-group row">
        <label asp-for="RoleName" class="col-sm-2 col-form-label"></label>
        <div class="col-sm-10">
            <input asp-for="RoleName" class="form-control" placeholder="Name">
            <span asp-validation-for="RoleName" class="text-danger"></span>
        </div>
        <label asp-for="Description" class="col-sm-2 col-form-label"></label>
        <div class="col-sm-10">
            <input asp-for="Description" class="form-control" placeholder="Description">
        </div>
    </div>

    <div class="form-group row">
        <div class="col-sm-10">
            <button type="submit" class="btn btn-primary" style="width:auto">
                Create Role
            </button>
        </div>
    </div>
</form>

Next, modify the EditRole.cshtml view as follows to include the Description field in the form.

@model EditRoleViewModel

@{
    ViewBag.Title = "Edit Role";
}

<h1>Edit Role</h1>

<form method="post" class="mt-3">
    <div class="form-group row">
        <input type="hidden" asp-for="Id" />
        <label asp-for="Id" class="col-sm-2 col-form-label"></label>
        <div class="col-sm-10">
            <input asp-for="Id" disabled class="form-control">
        </div>
    </div>
    <div class="form-group row">
        <label asp-for="RoleName" class="col-sm-2 col-form-label"></label>
        <div class="col-sm-10">
            <input asp-for="RoleName" class="form-control">
            <span asp-validation-for="RoleName" class="text-danger"></span>
        </div>
    </div>

    <div class="form-group row">
        <label asp-for="Description" class="col-sm-2 col-form-label"></label>
        <div class="col-sm-10">
            <input asp-for="Description" class="form-control">
        </div>
    </div>

    <div asp-validation-summary="All" class="text-danger"></div>

    <div class="form-group row">
        <div class="col-sm-10">
            <button type="submit" class="btn btn-primary">Update</button>
            <a asp-action="ListRoles" class="btn btn-primary">Cancel</a>
        </div>
    </div>

</form>

Modify the ListRole.cshtml view to include the Description field in the form as follows.

@model IEnumerable<ApplicationRole>

@{
    ViewBag.Title = "All Roles";
}

<h1>All Roles</h1>

@if (Model.Any())
{
    <a class="btn btn-primary mb-3" style="width:auto" asp-action="CreateRole"
       asp-controller="Administration">Add New Role</a>

    foreach (var role in Model)
    {
        <div class="card mb-3">
            <div class="card-header">
                Role Id : @role.Id
            </div>
            <div class="card-body">
                <h5 class="card-title">@role.Name</h5>
                <h3 class="card-title">@role.Description</h3>
            </div>
            <div class="card-footer">

                <form asp-action="DeleteRole" asp-route-roleId="@role.Id" method="post">

                    <a asp-controller="Administration" asp-action="EditRole"
                       asp-route-roleId="@role.Id" class="btn btn-primary">
                        Edit
                    </a>

                    <span id="confirmDeleteSpan_@role.Id" style="display:none">
                        <span>Are you sure you want to Delete?</span>
                        <button type="submit" class="btn btn-danger">Yes</button>
                        <a href="#" class="btn btn-primary"
                           onclick="confirmDelete('@role.Id', false)">No</a>
                    </span>

                    <span id="deleteSpan_@role.Id">
                        <a href="#" class="btn btn-danger"
                           onclick="confirmDelete('@role.Id', true)">Delete</a>
                    </span>
                </form>

            </div>
        </div>
    }
}
else
{
    <div class="card">
        <div class="card-header">
            No roles created yet
        </div>
        <div class="card-body">
            <h5 class="card-title">
                Use the button below to create a role
            </h5>
            <a class="btn btn-primary" style="width:auto"
               asp-controller="Administration" asp-action="CreateRole">
                Create Role
            </a>
        </div>
    </div>
}

<script>
    function confirmDelete(uniqueId, isTrue) {

        var deleteSpan = 'deleteSpan_' + uniqueId;
        var confirmDeleteSpan = 'confirmDeleteSpan_' + uniqueId;

        if (isTrue) {
            $('#' + deleteSpan).hide();
            $('#' + confirmDeleteSpan).show();
        } else {
            $('#' + deleteSpan).show();
            $('#' + confirmDeleteSpan).hide();
        }
    }
</script>
Generate the Migration and Update the Database

After making changes to the model, you need to create a new Entity Framework Core Migration and update your database schema to reflect these changes. So, open the Package Manager Console and Execute the following add-migration and update-database commands. You can give your migration any name. Here, I am giving it IdentityMigration3. The name that you are giving it should not be given earlier.

Generate the Migration and Update the Database

Now, if you verify the database and check the AspNetRoles table, then you should see the newly added Description column as shown in the image below:

Generate the Migration and Update the Database

Open the Create Role view, and it should ask you to enter the description as shown in the image below:

How to Customize the AspNetRoles Table in ASP.NET Core Identity

Now, if you open the Edit Role view, it will ask you to update the description of the role, as shown in the image below:

How to Customize the AspNetRoles Table in ASP.NET Core Identity

Now, if you visit the Role List Page, then also it is going to display the Role Description as shown in the below image:

Customizing AspNetRoles Table in ASP.NET Core Identity

Please verify the database, and you will see the Description column with the values as shown in the below image:

Customizing AspNetRoles Table in the ASP.NET Core Identity

In the next article, I will discuss How to Add or Remove Users From Roles in ASP.NET Core Identity. In this article, I explain How to Customize the AspNetRoles Table in ASP.NET Core Identity. I hope you enjoy this Customizing AspNetRoles Table in the ASP.NET Core Identity article.

Leave a Reply

Your email address will not be published. Required fields are marked *