(26/30) Let's Learn Blazor Together: Assign Roles to Users

(26/30) Let's Learn Blazor Together: Assign Roles to Users

Yesterday, the `CRUD` functions for roles were completed. Next, we need to assign roles to users.

Last updated 12/24/2021 11:20 PM
StrayaWorker
6 min read
Category
Blazor
Topic
Let's Learn Blazor Series
Tags
.NET C# ASP.NET Core Blazor

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:

  1. Add or remove users from role in asp net core

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.

Keep Exploring

Related Reading

More Articles
Same category / Same tag 12/25/2021

(29/30)Learn Blazor Together: Blazor Unit Testing

The most boring part of developing a system is probably fixing bugs, especially errors like trying to access a null object (`Object reference not set to an instance of an object.`), which is the most common problem most people encounter when they first step into programming. To break free from the tedious bug-fixing process, this article introduces `unit testing`.

Continue Reading
Same category / Same tag 12/25/2021

(28/30) Learning Blazor Together: Policy-based Authorization

It was mentioned earlier that `ASP.NET Core Identity` uses `Claim`-based authentication. In fact, `ASP.NET Core Identity` has different types of authorization methods, the simplest being `Login Authorization`, `Role Authorization`, and `Claim Authorization`. However, all of the above are implemented in one way: Policy-based Authorization.

Continue Reading