Day25 建立角色功能

首先建立装载角色资料的 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.csRolesRepository.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,这边注入的RoleManagerUserManager是 ASP.NET Core Identity 预设处理角色跟使用者的 Service,之前在Startup.cs写的services.AddIdentity<IdentityUser, IdentityRole>()…就注册了该功能,底下有各式 Role、User 相关API可以呼叫。
https://ithelp.ithome.com.tw/upload/images/20210925/20140893Gc7hOcTKyc.png

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。
https://ithelp.ithome.com.tw/upload/images/20210925/20140893xezKUo4Pob.png
https://ithelp.ithome.com.tw/upload/images/20210925/20140893p1d6VhAB7R.png

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>

有了建立功能就要有编辑功能,编辑完成或取消都直接跳转回角色列表。
https://ithelp.ithome.com.tw/upload/images/20210925/20140893hQblxSloQo.png
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

Ref:Edit role in asp net core


<<:  #11 Web Crawler 4

>>:  Day11 TailwindCSS 介绍,在 Next.js 专案安装 TailwindCSS

< 关於 React: 开始打地基| LifeCycle 生命圈>

09-10-2021 Mounting 当组件被初始化,第一次被放入DOM时。 ComponentD...

[Day29] HTB Netmon

URL : https://app.hackthebox.eu/machines/Netmon I...

如何衡量万事万物 (1) 衡量的定义

其实在开赛前,我有规划一些软性书单,想说在忙碌或想要休息时,可以拿来挡一下。但我今天早上真正 rev...

DAY 20 制作 Nav Bar - Scrollbar

预设时会是使用浏览器定义的 Scrollbar,不过 vogue 有在修改过样式,所以我们也要跟着改...

Day28 NodeJS实作 II

前端新增的基本页面设计完,今天的内容是以後端为主,建立新增待办事项的API,以将前端传递的资料写入资...