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?
To manage AspNetUsers you can use UserManager. Example description:
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;
}