首先建立裝載角色資料的 ViewModel,因為接下來的權限會以角色判斷,ASP.NET Core Identity 乘載角色的 Model 為 IdentityRole,裡面有太多不該讓使用者看到的資訊,通常會自己寫新的 ViewModel 以過濾多於資訊,這邊只呈現 RoleId、RoleName 跟 Role 底下所有使用者的名稱。
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace BlazorServer.ViewModels
{
public class CustomRoleViewModel
{
public CustomRoleViewModel()
{
Users = new();
}
public string RoleId { get; set; }
[Required(ErrorMessage = "角色名稱為必填")]
public string RoleName { get; set; }
public List<string> Users { get; set; }
}
}
建立IRolesRepository.cs
跟RolesRepository.cs
,這是專門處理角色的 Service,把基本的角色 CRUD(Create, Read, Update, Delete) 功能實作,再去Startup.cs
註冊。
介面IRolesRepository.cs
using BlazorServer.ViewModels;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace BlazorServer.Services
{
public interface IRolesRepository
{
Task<CustomRoleViewModel> GetRoleAsync(string RoleId);
Task<List<CustomRoleViewModel>> GetRolesAsync();
Task<ResultViewModel> CreateRoleAsync(CustomRoleViewModel model);
Task<ResultViewModel> EditRoleAsync(CustomRoleViewModel model);
Task<ResultViewModel> DeleteRoleAsync(string roleId);
Task<List<CustomUserRoleViewModel>> EditUsersInRoleAsync(string RoleId);
Task<ResultViewModel> EditUsersInRoleAsync(List<CustomUserRoleViewModel> model, string RoleId);
}
}
實作RolesRepository.cs
,這邊注入的RoleManager
跟UserManager
是 ASP.NET Core Identity 預設處理角色跟使用者的 Service,之前在Startup.cs
寫的services.AddIdentity<IdentityUser, IdentityRole>()…
就註冊了該功能,底下有各式 Role、User 相關API可以呼叫。
using BlazorServer.ViewModels;
using Microsoft.AspNetCore.Identity;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace BlazorServer.Services
{
public class RolesRepository : IRolesRepository
{
private readonly RoleManager<IdentityRole> _roleManager;
private readonly UserManager<IdentityUser> _userManager;
public RolesRepository(RoleManager<IdentityRole> roleManager,
UserManager<IdentityUser> userManager)
{
_roleManager = roleManager;
_userManager = userManager;
}
#region Roles
// 取得單一角色
public async Task<CustomRoleViewModel> GetRoleAsync(string RoleId)
{
var role = await _roleManager.FindByIdAsync(RoleId);
var users = await _userManager.GetUsersInRoleAsync(role.Name);
var result = new CustomRoleViewModel
{
RoleId = role.Id,
RoleName = role.Name,
Users = users.Select(u => u.UserName).ToList()
};
return result;
}
// 取得角色 List
public async Task<List<CustomRoleViewModel>> GetRolesAsync()
{
var roles = _roleManager.Roles;
var customRoles = new List<CustomRoleViewModel>();
foreach (var role in roles)
{
customRoles.Add(new CustomRoleViewModel { RoleId = role.Id, RoleName= role.Name });
}
return await Task.Run(() => customRoles);
}
// 建立角色
public async Task<ResultViewModel> CreateRoleAsync(CustomRoleViewModel model)
{
IdentityRole identityRole = new IdentityRole
{
Name = model.RoleName
};
var result = await _roleManager.CreateAsync(identityRole);
if (result.Succeeded)
{
return new ResultViewModel
{
Message = "角色建立成功!",
IsSuccess = true
};
}
return new ResultViewModel
{
Message = "角色建立失敗!",
IsSuccess = false
};
}
// 編輯角色
public async Task<ResultViewModel> EditRoleAsync(CustomRoleViewModel model)
{
var role = await _roleManager.FindByIdAsync(model. RoleId);
if (role == null)
{
return new ResultViewModel
{
Message = $"找不到 Id 為 {model.RoleId} 的角色",
IsSuccess = false
};
}
role.Name = model.RoleName;
var result = await _roleManager.UpdateAsync(role);
if (result.Succeeded)
{
return new ResultViewModel
{
Message = "角色更新成功!",
IsSuccess = true
};
}
return new ResultViewModel
{
Message = "角色更新失敗!",
IsSuccess = false
};
}
// 刪除角色
public async Task<ResultViewModel> DeleteRoleAsync(string roleId)
{
var role = await _roleManager.FindByIdAsync(roleId);
if (role == null)
{
return new ResultViewModel
{
Message = $"找不到 Id 為 {roleId} 的角色",
IsSuccess = false
};
}
var result = await _roleManager.DeleteAsync(role);
if (result.Succeeded)
{
return new ResultViewModel
{
Message = "角色刪除成功!",
IsSuccess = true
};
}
return new ResultViewModel
{
Message = "角色刪除失敗!",
IsSuccess = false
};
}
#endregion
}
}
現在有處理資料的功能了,接下來要產生畫面。RolesManagement.razor.cs
using BlazorServer.Services;
using BlazorServer.Shared;
using BlazorServer.ViewModels;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
namespace BlazorServer.Pages.RolesManagement
{
public partial class RolesManagement
{
[Inject] protected IRolesRepository RolesRepository { get; set; }
[Inject] protected IJSRuntime js { get; set; }
private JsInteropClasses jsClass;
public List<CustomRoleViewModel> Roles { get; set; } = new();
protected override async Task OnInitializedAsync()
{
await loadData();
jsClass = new(js);
}
private async Task loadData()
{
Roles = await RolesRepository.GetRolesAsync();
}
private async Task editRole(string roleId)
{
NavigationManager.NavigateTo($"RolesManagement/EditRole/{roleId}");
}
private async Task deleteRole(string roleId)
{
SweetConfirmViewModel sweetConfirm = new SweetConfirmViewModel()
{
RequestTitle = $"是否確定刪除角色{roleId}?",
RequestText = "這個動作不可復原",
ResponseTitle = "刪除成功",
ResponseText = "角色被刪除了",
};
string jsonString = JsonSerializer.Serialize(sweetConfirm);
bool result = await jsClass.Confirm(jsonString);
if (result)
{
var deleted = await RolesRepository.DeleteRoleAsync(roleId);
if (deleted.IsSuccess)
{
await loadData();
}
else
{
await jsClass.Alert(deleted.Message);
}
}
}
}
}
RolesManagement.razor
@page "/RolesManagement/RolesList"
@attribute [Authorize]
<h1>所有角色</h1>
@if (Roles.Any())
{
<NavLink class="btn btn-primary mb-3" href="RolesManagement/CreateRole" Match="NavLinkMatch.All">
新增角色
</NavLink>
foreach (var role in Roles)
{
<div class="card mb-3 w-25">
<div class="card-header">
Role Id : @role.RoleId
</div>
<div class="card-body">
<h5 class="card-title">@role.RoleName</h5>
</div>
<div class="card-footer">
<button type="button" class="btn btn-primary" @onclick="()=>editRole(role.RoleId)">
編輯角色
</button>
<button type="button" class="btn btn-danger" @onclick="()=>deleteRole(role.RoleId)">
刪除角色
</button>
</div>
</div>
}
}
else
{
<div class="card w-25">
<div class="card-header">
還沒有角色
</div>
<div class="card-body">
<h5 class="card-title">
按底下的按鈕建立角色
</h5>
<NavLink class="btn btn-primary mb-3" href="RolesManagement/CreateRole" Match="NavLinkMatch.All">
新增角色
</NavLink>
</div>
</div>
}
然後去NavMenu.razor
加入 NavLink 通往角色管理。
<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>
這時候開啟網站可以看到這樣的畫面,我們來加上新增角色的畫面並新增一個角色 Admin。
CreateRole.razor.cs
using BlazorServer.Services;
using BlazorServer.ViewModels;
using Microsoft.AspNetCore.Components;
using System.Threading.Tasks;
namespace BlazorServer.Pages.RolesManagement
{
public partial class CreateRole
{
[Inject] protected IRolesRepository RolesRepository { get; set; }
[Inject] protected NavigationManager NavigationManager { get; set; }
public CustomRoleViewModel Role { get; set; } = new();
private async Task createRole()
{
await RolesRepository.CreateRoleAsync(Role);
NavigationManager.NavigateTo("/RolesManagement/RolesList");
}
}
}
CreateRole.razor
@page "/RolesManagement/CreateRole"
@attribute [Authorize]
<EditForm class="mt-3" Model="Role" OnValidSubmit="createRole">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="form-group row">
<label for="RoleName" class="col-sm-1 col-form-label">角色名稱</label>
<div class="col-sm-3">
<InputText @bind-Value="Role.RoleName" id="RoleName" class="form-control" placeholder="角色名稱"></InputText>
</div>
</div>
<div class="form-group row">
<div class="col-sm-10">
<button type="submit" class="btn btn-primary">
建立角色
</button>
</div>
</div>
</EditForm>
有了建立功能就要有編輯功能,編輯完成或取消都直接跳轉回角色列表。EditRole.razor.cs
using BlazorServer.Services;
using BlazorServer.ViewModels;
using Microsoft.AspNetCore.Components;
using System.Threading.Tasks;
namespace BlazorServer.Pages.RolesManagement
{
public partial class EditRole
{
[Inject] protected IRolesRepository RolesRepository { get; set; }
[Inject] protected NavigationManager NavigationManager { get; set; }
public CustomRoleViewModel Role { get; set; } = new();
[Parameter]
public string RoleId { get; set; }
protected override async Task OnInitializedAsync()
{
var result = await RolesRepository.GetRoleAsync(RoleId);
Role = new CustomRoleViewModel
{
RoleId = result.RoleId,
RoleName = result.RoleName,
Users = result.Users
};
}
private async Task editRole()
{
await RolesRepository.EditRoleAsync(Role);
NavigationManager.NavigateTo("/RolesManagement/RolesList");
}
public void Cancel()
{
NavigationManager.NavigateTo($"/RolesManagement/RolesList");
}
}
}
EditRole.razor
@page "/RolesManagement/EditRole/{RoleId}"
@attribute [Authorize]
<EditForm class="mt-3" Model="Role" OnValidSubmit="editRole">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="form-group row">
<label for="RoleName" class="col-sm-1 col-form-label">角色名稱</label>
<div class="col-sm-3">
<InputText @bind-Value="Role.RoleName" id="RoleName" class="form-control" placeholder="角色名稱"></InputText>
</div>
</div>
<div class="card mb-3 w-50">
<div class="card-header">
<h3>角色底下的使用者</h3>
</div>
<div class="card-body">
@if (Role.Users.Any())
{
foreach (var user in Role.Users)
{
<h5 class="card-title">@user</h5>
}
}
else
{
<h5 class="card-title">目前該角色沒有指派給任何使用者</h5>
}
</div>
<div class="card-footer">
<button type="submit" class="btn btn-primary">更新角色</button>
<button type="button" class="btn btn-danger" @onclick="Cancel">取消</button>
</div>
</div>
</EditForm>
角色 CRUD 功能大概就是這些,筆者只是用最簡單的方式處理,不過專案通常不會這麼簡單,還有其他細微功能要調整,明天來說明如何管理角色底下的使用者,以及如何套用角色授權。
Ref:Creating roles in asp net core
Ref:Get list of roles in asp net core