Search code examples
asp.netauthentication

How to scaffold AspNetUserRoles table?


I have tried to scaffold a AspNetUserRoles table with AspNetUser, AspNetRoles and others tables that I need. The scaffolding has a problem with many to many relationship of AspNetUserRoles and there is an error when application runs

System.InvalidOperationException: 'Cannot use table 'AspNetRoles' for entity type 'IdentityRole' since it is being used for entity type 'AspNetRole' and potentially other entity types, but there is no linking relationship. Add a foreign key to 'IdentityRole' on the primary key properties and pointing to the primary key on another entity type mapped to 'AspNetRoles'.'

Or other similar errors. I tried to handle AspNetRoles for a day.

I could scaffold a ASPNetUserRoles table only and copy paste it's fragment to my DbContext. Is it the right way to add ASPNetUserRoles CRUD pages?


Solution

  • To manage AspNetUsers you can use UserManager. Example description:

    • Razor pages
    • I scaffoldeddb the AspNetUser table, then reverted everything except AspNetUser model, then new Scaffolded item > Razor pages using Entity Framework (CRUD) then deleted AspNetUser, then replaced AspNetUser with IdentityUser. Commented most of the code to just display IdentityUser fields. It works with no complications. Then added the code to use UserManager & RolesManager
    • Index page to display Users with their Role
    • Edit page for UserRoles Management.

    To display User with roles on one page create IdentityUserWithRole.cs

    using Microsoft.AspNetCore.Identity;
    using System.ComponentModel.DataAnnotations;
    
    namespace YourApp.Models
    {
        public class IdentityUserWithRole
        {
            public IdentityUser User { get; }
            public List<string> Roles { get; }
            [Display(Name = "Roles")]
            public string RolesJoined { get; }
    
            public IdentityUserWithRole(IdentityUser user, List<string> roles)
            {
                User = user;
                Roles = roles;
                RolesJoined = String.Join("; ", roles);
            }
    
            public IdentityUserWithRole()
            {
                User = new();
                Roles = new List<string>();
                RolesJoined = "";            
            }
        }
    }
    

    Index.cshtml

    @page
    @model YourApp.Pages.AspNetUsers.IndexModel
    
    @{
        ViewData["Title"] = "Index";
    }
    
    <h1>Index</h1>
    
    <p>
        <a asp-page="Create">Create New</a>
    </p>
    <table class="table">
        <thead>
            <tr>
                <th>
                    @Html.DisplayNameFor(model => model.UserWithRole[0].User.Email)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.UserWithRole[0].RolesJoined)
                </th>
                <th></th>
            </tr>
        </thead>
        <tbody>
    @foreach (var item in Model.UserWithRole) {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.User.Email)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.RolesJoined)
                </td>
                <td>
                    <a asp-page="./Edit" asp-route-id="@item.User.Id">Edit</a> |
                    <a asp-page="./Details" asp-route-id="@item.User.Id">Details</a> |
                    <a asp-page="./Delete" asp-route-id="@item.User.Id">Delete</a>
                </td>
            </tr>
    }
        </tbody>
    </table>
    

    Index.cshtml.cs

    using Microsoft.AspNetCore.Mvc.RazorPages;
    using Microsoft.EntityFrameworkCore;
    using YourApp.Models;
    using Microsoft.AspNetCore.Identity;
    
    namespace YourApp.Pages.AspNetUsers
    {
        public class IndexModel : PageModel
        {
            private readonly IServiceProvider _serviceProvider;
    
            public IndexModel(IServiceProvider serviceProvider)
            {
                _serviceProvider = serviceProvider;
            }
    
            public IList<IdentityUserWithRole> UserWithRole { get; set; } = default!;
    
            public async Task OnGetAsync()
            {
                var userManager = _serviceProvider.GetService<UserManager<IdentityUser>>();
                if (userManager != null)
                {
                    UserWithRole = new List<IdentityUserWithRole>();
                    var aspNetUsers = await userManager.Users.ToListAsync();
                    foreach (var aspNetUser in aspNetUsers)
                    {
                        List<string> userRoles = (List<string>)await userManager.GetRolesAsync(aspNetUser);
                        IdentityUserWithRole userWithRole = new IdentityUserWithRole(aspNetUser, userRoles);
                        UserWithRole.Add(userWithRole);
                    }
                }
            }
        }
    }
    

    Edit.cshtml

    @page
    @model YourApp.Pages.AspNetUsers.EditModel
    
    @{
        ViewData["Title"] = "Edit";
    }
    
    <h1>Edit</h1>
    
    <h4>AspNetUser</h4>
    <hr />
    <div class="row">
        <div class="col-md-4">
            <form method="post">
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                <input type="hidden" asp-for="UserWithRole.User.Id" />
                <input type="hidden" asp-for="UserWithRole.User.UserName" />
                <input type="hidden" asp-for="UserWithRole.User.NormalizedUserName" />
                <input type="hidden" asp-for="UserWithRole.User.NormalizedEmail" />
                <input type="hidden" asp-for="UserWithRole.User.EmailConfirmed" />
                <input type="hidden" asp-for="UserWithRole.User.PasswordHash" />
                <input type="hidden" asp-for="UserWithRole.User.SecurityStamp" />
                <input type="hidden" asp-for="UserWithRole.User.ConcurrencyStamp" />
                <input type="hidden" asp-for="UserWithRole.User.PhoneNumber" />
                <input type="hidden" asp-for="UserWithRole.User.PhoneNumberConfirmed" />
                <input type="hidden" asp-for="UserWithRole.User.TwoFactorEnabled" />
                <input type="hidden" asp-for="UserWithRole.User.LockoutEnabled" />
                <input type="hidden" asp-for="UserWithRole.User.AccessFailedCount" />
                <input type="hidden" asp-for="UserWithRole.RolesJoined" />
                <input type="hidden" asp-for="UserWithRole.Roles" />
    
                <div class="form-group">
                    <label asp-for="UserWithRole.User.Email" class="control-label"></label>
                    <input asp-for="UserWithRole.User.Email" readonly class="form-control" />
                    <span asp-validation-for="UserWithRole.User.Email" class="text-danger"></span>
                </div>
    
                <div class="form-group">
                    <label asp-for="NewUserRole" class="control-label">Add a new role</label>
                    <select id="selectCustomer" asp-for="NewUserRole" class="form-control" asp-items="ViewBag.Roles">
                        <option selected value="">Select</option>
                    </select>
                    <span asp-validation-for="NewUserRole" class="text-danger"></span>
                </div>
    
                <div class="form-group mt-1">
                    <input type="submit" value="Save" class="btn btn-primary" />
                </div>
            </form>
        </div>
    </div>
    <div>
        <table class="table">
            <thead>
                <tr>
                    <th>
                        @Html.DisplayNameFor(model => model.UserWithRole.Roles)
                    </th>
    
                    <th></th>
                </tr>
            </thead>
            <tbody>
                @foreach (var item in Model.UserWithRole.Roles)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item)
                        </td>
                        <td>
                            <form method="post">
                                <input type="text" name="UserId" value="@Model.UserWithRole.User.Id" hidden />
                                <input type="text" name="Role" value="@item" hidden />
                                <input type="submit" value="Delete" asp-page-handler="DeleteRole"/>
                            </form>
                        </td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
    <div>
        <a asp-page="./Index">Back to List</a>
    </div>
    
    @section Scripts {
        @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
    }
    

    Edit.cshtml.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.RazorPages;
    using Microsoft.AspNetCore.Mvc.Rendering;
    using Microsoft.EntityFrameworkCore;
    using YourApp.Data;
    using YourApp.Models;
    using Microsoft.AspNetCore.Identity;
    using Microsoft.Extensions.DependencyInjection;
    
    namespace YourApp.Pages.AspNetUsers
    {
        public class EditModel : PageModel
        {
            private readonly IServiceProvider _serviceProvider;
    
            public EditModel(IServiceProvider serviceProvider)
            {
                _serviceProvider = serviceProvider;
            }
    
            [BindProperty]
            public IdentityUserWithRole UserWithRole { get; set; }
    
            [BindProperty]
            public string? NewUserRole { get; set; } = null;
    
            public async Task<IActionResult> OnGetAsync(string id)
            {
                if (id == null)
                {
                    return NotFound();
                }
    
                var userManager = _serviceProvider.GetService<UserManager<IdentityUser>>();
                if (userManager == null)
                {
                    return NotFound();
                }
    
                var user = await userManager.FindByIdAsync(id);
                if (user == null)
                {
                    return NotFound();
                }
    
                List<string> userRoles = (List<string>)await userManager.GetRolesAsync(user);
                UserWithRole = new(user, userRoles);
    
    
                var roleManager = _serviceProvider.GetService<RoleManager<IdentityRole>>();
                if (roleManager == null)
                {
                    return NotFound();
                }
    
                var roles = roleManager.Roles.Select(x => x.Name).ToList();
                ViewData["Roles"] = new SelectList(roles);
    
                return Page();
            }
    
            // To protect from overposting attacks, enable the specific properties you want to bind to.
            // For more details, see https://aka.ms/RazorPagesCRUD.
            public async Task<IActionResult> OnPostAsync()
            {
                if (!ModelState.IsValid)
                {
                    return Page();
                }
    
                if(NewUserRole != null)
                    await EnsureRole(UserWithRole.User.Id, NewUserRole);
    
                return await this.OnGetAsync(UserWithRole.User.Id);
            }
    
            public async Task<IActionResult> OnPostDeleteRole(IFormCollection formData)
            {
                var userId = formData["UserId"].ToString();
                var role = formData["Role"].ToString();
    
                var userManager = _serviceProvider.GetService<UserManager<IdentityUser>>();
                if(userManager == null)
                {
                    return Page();
                }
                var user = await userManager.FindByIdAsync(userId);
                await userManager.RemoveFromRoleAsync(user, role);
    
                return await OnGetAsync(userId);
            }
    
    
    
            private async Task<IdentityResult> EnsureRole(string userId, string role)
            {
                var roleManager = _serviceProvider.GetService<RoleManager<IdentityRole>>();
    
                IdentityResult ir;
    
                if (await roleManager.RoleExistsAsync(role) == false)
                {
                    ir = await roleManager.CreateAsync(new IdentityRole(role));
                }
    
                var userManager = _serviceProvider.GetService<UserManager<IdentityUser>>();
    
                var user = await userManager.FindByIdAsync(userId);
    
                if (user == null)
                    throw new Exception("User not existing");
    
                ir = await userManager.AddToRoleAsync(user, role);
    
                return ir;
            }
        }
    }
    

    The EnsureUser & EnsureRole I got from https://www.udemy.com/course/aspnet-6-course/ really good course to make your first website for database management.

    public static async Task<string> EnsureUser(
        IServiceProvider serviceProvider, string userName, string initPassword)
    {
        var userManager = serviceProvider.GetService<UserManager<IdentityUser>>();
    
        var user = await userManager.FindByNameAsync(userName);
    
        if (user == null)
        {
            user = new IdentityUser
            {
                UserName = userName,
                Email = userName,
                EmailConfirmed = true,
            };
    
            var result = await userManager.CreateAsync(user, initPassword);
        }
    
        if (user == null)
            throw new Exception("User did not get created. Password policy problem?");
    
        return user.Id;
    }
    
    public static async Task<IdentityResult> EnsureRole(
        IServiceProvider serviceProvider, string userId, string role)
    {
        var roleManager = serviceProvider.GetService<RoleManager<IdentityRole>>();
    
        IdentityResult ir;
    
        if (await roleManager.RoleExistsAsync(role) == false)
        {
            ir = await roleManager.CreateAsync(new IdentityRole(role));
        }
    
        var userManager = serviceProvider.GetService<UserManager<IdentityUser>>();
    
        var user = await userManager.FindByIdAsync(userId);
    
        if (user == null)
            throw new Exception("User not existing");
    
        ir = await userManager.AddToRoleAsync(user, role);
    
        return ir;
    }