Yesterday, the CRUD functionality for roles was completed. The next step was to assign roles to users. First, we created a ViewModel CustomUserRoleViewModel, which is used to present the users under a role.
namespace BlazorServer.ViewModels;
public class CustomUserRoleViewModel
{
public string? UserId { get; set; }
public string? UserName { get; set; }
public bool IsSelected { get; set; }
}
Then we added the functionality to edit users within a role to IRolesRepository and RolesRepository. Overloading was used here: the first is equivalent to Get to obtain the initial page data, and the second is Post to submit the modified data.
IRolesRepository.cs
…
Task<List<CustomUserRoleViewModel>> EditUsersInRoleAsync(string roleId);
Task<ResultViewModel> EditUsersInRoleAsync(List<CustomUserRoleViewModel> model, string roleId);
RolesRepository.cs
…
public async Task<List<CustomUserRoleViewModel>> EditUsersInRoleAsync(string roleId)
{
var role = await _roleManager.FindByIdAsync(roleId);
var model = new List<CustomUserRoleViewModel>();
// 这里注意,_userManager.Users.ToList()后面一定要加.ToList(),否则会抛出异常:https://stackoverflow.com/questions/60727080/helping-solving-there-is-already-an-open-datareader-associated-with-this-comman
foreach (var user in _userManager.Users.ToList())
{
var userRoleViewModel = new CustomUserRoleViewModel
{
UserId = user.Id,
UserName = user.UserName
};
if (await _userManager.IsInRoleAsync(user, role.Name))
userRoleViewModel.IsSelected = true;
else
userRoleViewModel.IsSelected = false;
model.Add(userRoleViewModel);
}
return model;
}
public async Task<ResultViewModel> EditUsersInRoleAsync(List<CustomUserRoleViewModel> model, string roleId)
{
var role = await _roleManager.FindByIdAsync(roleId);
foreach (var m in model)
{
var user = await _userManager.FindByIdAsync(m.UserId);
IdentityResult result;
if (m.IsSelected && !await _userManager.IsInRoleAsync(user, role.Name))
result = await _userManager.AddToRoleAsync(user, role.Name);
else if (!m.IsSelected && await _userManager.IsInRoleAsync(user, role.Name))
result = await _userManager.RemoveFromRoleAsync(user, role.Name);
else
continue;
if (result.Succeeded)
{
if (model.Count > 0)
continue;
return new ResultViewModel
{
Message = roleId,
IsSuccess = true
};
}
return new ResultViewModel
{
Message = roleId,
IsSuccess = false
};
}
return new ResultViewModel
{
Message = roleId,
IsSuccess = true
};
}
Next, the page was added.
EditUsersInRole.razor.cs
using BlazorServer.Repository;
using BlazorServer.Shared;
using BlazorServer.ViewModels;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorServer.Pages.RolesManagement;
public partial class EditUsersInRole
{
[Inject] protected IRolesRepository? RolesRepository { get; set; }
[Inject] protected NavigationManager? NavigationManager { get; set; }
[Inject] protected IJSRuntime? Js { get; set; }
private JsInteropClasses? _jsClass;
[Parameter] public string? Id { get; set; }
public List<CustomUserRoleViewModel> UserRoleViewModel { get; set; } = new List<CustomUserRoleViewModel>();
protected override async Task OnInitializedAsync()
{
await LoadData();
_jsClass = new JsInteropClasses(Js!);
}
private async Task LoadData()
{
UserRoleViewModel = (await RolesRepository!.EditUsersInRoleAsync(Id!)).ToList();
}
public async Task HandleValidSubmit()
{
var result = await RolesRepository!.EditUsersInRoleAsync(UserRoleViewModel, Id!);
if (result.IsSuccess)
{
NavigationManager!.NavigateTo($"/RolesManagement/EditRole/{Id}");
}
else
{
await _jsClass!.Alert(result.Message!);
}
}
public void Cancel()
{
NavigationManager!.NavigateTo($"/RolesManagement/EditRole/{Id}");
}
}
EditUsersInRole.razor
@page "/RolesManagement/EditUsersInRole/{Id}" @attribute [Authorize]
<EditForm Model="UserRoleViewModel" OnValidSubmit="HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="card">
<div class="card-header">
<h2>Add or remove users from the role</h2>
</div>
<div class="card-body">
@foreach (var user in UserRoleViewModel) {
<div class="form-check m-1">
<label class="form-check-label">
<InputCheckbox @bind-Value="@user.IsSelected"></InputCheckbox>
@user.UserName
</label>
</div>
}
</div>
<div class="card-footer">
<button type="submit" class="btn btn-primary">Update</button>
<button type="button" class="btn btn-danger" @onclick="@Cancel">
Cancel
</button>
</div>
</div>
</EditForm>
EditRole.razor.cs added a method to navigate to EditUsersInRole.razor to edit users under a role.
public void EditUsersInRole()
{
NavigationManager!.NavigateTo($"/RolesManagement/EditUsersInRole/{Id}");
}
EditRole.razor added a button to call this method.
<button type="button" class="btn btn-info" @onclick="EditUsersInRole">
Add or remove users under this role
</button>

Start the web app and go to the Edit Users in Role page. You can see only one user exists. Assign the Admin role to this user, thus completing the assignment between Admin and test@gmail.com. However, to test if it works, you need to add restrictions to the page and also create a new user for testing.

First, change @attribute [Authorize] on each razor component to @attribute [Authorize(Roles = "Admin")], meaning only users with the Admin role can view these pages.
Then go to NavMenu.razor and add the following code above the existing <AuthorizeView>. Similarly, only users with the Admin role can see the navigation link to Roles. Then delete the original Roles link inside the <AuthorizeView>.
<AuthorizeView Roles="Admin">
<Authorized>
<li class="nav-item px-3">
<NavLink
class="nav-link"
href="RolesManagement/RolesList"
Match="NavLinkMatch.All"
>
<span class="bi bi-kanban-fill h4 p-2 mb-0" aria-hidden="true"></span>
Roles
</NavLink>
</li>
</Authorized>
</AuthorizeView>
Finally, create a new user named user@gmail.com. Log in with this user, and you will see that the Roles link on the left side is gone. Even manually entering the URL will prompt that you do not have permission. This is the most basic role-based authorization. If the system is simple and only needs role-based permissions, this is sufficient.


Reference:
Note: The code in this article was refactored using .NET 6 + Visual Studio 2022. You can click the original link to compare with the refactored code. Thank you for reading and supporting the original author.