Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save sadukie/6ca0a8c3f1b935988d9dba5e8827b883 to your computer and use it in GitHub Desktop.
Save sadukie/6ca0a8c3f1b935988d9dba5e8827b883 to your computer and use it in GitHub Desktop.
Role Membership Code
using System.ComponentModel.DataAnnotations;
namespace BlazorAdmin.Models;
public class GetRoleMembershipRequest
{
public GetRoleMembershipRequest(string name)
{
Name = name;
}
[Required(ErrorMessage = "The Name field is required")]
public string Name { get; init; }
}
using System.Collections.Generic;
namespace BlazorAdmin.Models;
public class GetRoleMembershipResponse
{
public string RoleId { get; set; }
public List<UserForMembership> RoleMembers { get; set; } = new List<UserForMembership>();
}
namespace BlazorAdmin.Models;
public class UserForMembership
{
public string UserName { get; set; }
public string Id { get; set; }
}
@inject ILogger<Delete> Logger
@inject IJSRuntime JSRuntime
@inject IRoleManagementService RoleManagementService
@inherits BlazorAdmin.Helpers.BlazorComponent
@namespace BlazorAdmin.Pages.RolePage
@using BlazorAdmin.Interfaces
@using BlazorAdmin.Models
@using Microsoft.AspNetCore.Identity
<div class="modal @_modalClass" tabindex="-1" role="dialog" style="display:@_modalDisplay">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Role Membership Update</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" @onclick="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container">
<div class="row">
<p>
Are you sure you want to <strong class="text-danger">REMOVE</strong> @_userName from @_roleName?
</p>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" @onclick="Close">Cancel</button>
<button class="btn btn-danger" @onclick="() => DeleteClick()">
Delete
</button>
</div>
</div>
</div>
</div>
@if (_showDeleteModal)
{
<div class="modal-backdrop fade show"></div>
}
@code {
[Parameter]
public EventCallback<string> OnSaveClick { get; set; }
private string _modalDisplay = "none;";
private string _modalClass = "";
private bool _showDeleteModal = false;
private string _roleName = "";
private string _roleId = "";
private string _userId = "";
private string _userName = "";
private async Task DeleteClick()
{
Logger.LogInformation("Removing User {userId} from Role {roleId}", _userId, _roleId);
await RoleManagementService.DeleteUserFromRole(_userId, _roleId);
Logger.LogInformation("Removed User {userId} from {roleId}", _userId, _roleId);
await OnSaveClick.InvokeAsync(null);
await Close();
}
public async Task Open(string userId, string userName, string roleName, string roleId)
{
_roleName = roleName;
_userName = userName;
_roleId = roleId;
_userId = userId;
await new Css(JSRuntime).HideBodyOverflow();
_modalDisplay = "block;";
_modalClass = "Show";
_showDeleteModal = true;
StateHasChanged();
}
private async Task Close()
{
await new Css(JSRuntime).ShowBodyOverflow();
_modalDisplay = "none";
_modalClass = "";
_showDeleteModal = false;
}
}
using System.Threading;
using System.Threading.Tasks;
using FastEndpoints;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Identity;
using Microsoft.eShopWeb.Infrastructure.Identity;
namespace Microsoft.eShopWeb.PublicApi.RoleMembershipEndpoints;
public class DeleteUserFromRoleEndpoint(RoleManager<IdentityRole> roleManager, UserManager<ApplicationUser> userManager) : Endpoint<DeleteUserFromRoleRequest, Results<NoContent, NotFound>>
{
public override void Configure()
{
Delete("api/roles/{RoleId}/members/{UserId}");
Roles(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS);
AuthSchemes(JwtBearerDefaults.AuthenticationScheme);
Description(d =>
{
d.Produces(StatusCodes.Status204NoContent);
d.Produces(StatusCodes.Status404NotFound);
d.WithTags("RoleManagementEndpoints");
}
);
}
public override async Task<Results<NoContent, NotFound>> ExecuteAsync(DeleteUserFromRoleRequest request, CancellationToken ct)
{
var userToUpdate = await userManager.FindByIdAsync(request.UserId);
if (userToUpdate is null)
{
return TypedResults.NotFound();
}
var roleToUpdate = await roleManager.FindByIdAsync(request.RoleId);
if (roleToUpdate is null || roleToUpdate.Name is null)
{
return TypedResults.NotFound();
}
await userManager.RemoveFromRoleAsync(userToUpdate, roleToUpdate.Name);
return TypedResults.NoContent();
}
}
namespace Microsoft.eShopWeb.PublicApi.RoleMembershipEndpoints;
public class DeleteUserFromRoleRequest : BaseRequest
{
public string UserId { get; init; }
public string RoleId { get; init; }
public DeleteUserFromRoleRequest(string userId, string roleId)
{
UserId = userId;
RoleId = roleId;
}
}
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using BlazorShared.Authorization;
using Microsoft.eShopWeb;
using Microsoft.eShopWeb.PublicApi.RoleManagementEndpoints;
using Microsoft.eShopWeb.PublicApi.RoleMembershipEndpoints;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PublicApiIntegrationTests.Helpers;
namespace PublicApiIntegrationTests.RoleMembershipEndpoints;
[TestClass]
public class DeleteUserFromRoleEndpointTest
{
[TestMethod]
public async Task ReturnsNotFoundGivenValidRoleIdAndInvalidUserIdAndAdminUserToken()
{
var client = HttpClientHelper.GetAdminClient();
var validRoleId = await GetValidRoleId(client, Constants.Roles.ADMINISTRATORS);
var response = await client.DeleteAsync($"api/roles/{validRoleId}/members/0");
Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode);
}
[TestMethod]
public async Task ReturnsNotFoundGivenInvalidRoleIdAndValidUserIdAndAdminUserToken()
{
var client = HttpClientHelper.GetAdminClient();
var roleList = await client.GetAsync($"/api/roles/{Constants.Roles.ADMINISTRATORS}/members");
var roleMembersResponse = await roleList.Content.ReadAsStringAsync();
var validUsers = roleMembersResponse.FromJson<GetRoleMembershipResponse>();
var validUserId = validUsers!.RoleMembers.FirstOrDefault()!.Id;
var response = await client.DeleteAsync($"api/roles/0/members/{validUserId}");
Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode);
}
[TestMethod]
public async Task ReturnsNoContentWhenDeletingUserFromRoleSuccessfully()
{
var client = HttpClientHelper.GetAdminClient();
var roleName = Constants.Roles.PRODUCT_MANAGERS;
var validRoleId = await GetValidRoleId(client, Constants.Roles.PRODUCT_MANAGERS);
var roleList = await client.GetAsync($"/api/roles/{roleName}/members");
var roleMembersResponse = await roleList.Content.ReadAsStringAsync();
var validUsers = roleMembersResponse.FromJson<GetRoleMembershipResponse>();
var validUserId = validUsers!.RoleMembers.FirstOrDefault()!.Id;
var response = await client.DeleteAsync($"api/roles/{validRoleId}/members/{validUserId}");
Assert.AreEqual(HttpStatusCode.NoContent, response.StatusCode);
}
private async Task<string> GetValidRoleId(HttpClient client, string roleName)
{
var validRoles = await client.GetAsync("/api/roles");
validRoles.EnsureSuccessStatusCode();
var validRolesCollection = await validRoles.Content.ReadAsStringAsync();
var model = validRolesCollection.FromJson<RoleListResponse>();
var selectedRole = model!.Roles.Where(x => x.Name == roleName).First();
return selectedRole.Id;
}
}
using System.Net.Http;
using System.Net.Http.Headers;
namespace PublicApiIntegrationTests.Helpers;
internal static class HttpClientHelper
{
public static HttpClient GetAdminClient()
{
return CreateClient(ApiTokenHelper.GetAdminUserToken());
}
public static HttpClient GetProductManagerClient()
{
return CreateClient(ApiTokenHelper.GetProductManagerUserToken());
}
public static HttpClient GetNormalUserClient()
{
return CreateClient(ApiTokenHelper.GetNormalUserToken());
}
private static HttpClient CreateClient(string token)
{
var client = ProgramTest.NewClient;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
return client;
}
}
public async Task<T> HttpDelete<T>(string uri)
where T : class
{
var result = await _httpClient.DeleteAsync($"{_apiUrl}{uri}");
if (!result.IsSuccessStatusCode)
{
var exception = JsonSerializer.Deserialize<ErrorDetails>(await result.Content.ReadAsStringAsync(), new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
_toastService.ShowToast($"Error : {exception.Message}", ToastLevel.Error);
return null;
}
_toastService.ShowToast($"Deleted successfully!", ToastLevel.Success);
return await FromHttpResponseMessage<T>(result);
}
using System.Threading.Tasks;
using BlazorAdmin.Models;
using Microsoft.AspNetCore.Identity;
namespace BlazorAdmin.Interfaces;
public interface IRoleManagementService
{
Task<CreateRoleResponse> Create(CreateRoleRequest role);
Task<IdentityRole> Edit(IdentityRole role);
Task Delete(string id);
Task DeleteUserFromRole(string userId, string roleId);
Task<GetByIdRoleResponse> GetById(string id);
Task<GetRoleMembershipResponse> GetMembershipByName(string name);
Task<RoleListResponse> List();
}
using System.Threading.Tasks;
using BlazorAdmin.Interfaces;
using BlazorAdmin.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
namespace BlazorAdmin.Services;
public class RoleManagementService(HttpService httpService, ILogger<RoleManagementService> logger) : IRoleManagementService
{
public async Task<RoleListResponse> List(){
logger.LogInformation("Fetching roles");
var response = await httpService.HttpGet<RoleListResponse>($"roles");
return response;
}
public async Task<CreateRoleResponse> Create(CreateRoleRequest newRole)
{
var response = await httpService.HttpPost<CreateRoleResponse>($"roles", newRole);
return response;
}
public async Task<IdentityRole> Edit(IdentityRole role)
{
return await httpService.HttpPut<IdentityRole>($"roles", role);
}
public async Task Delete(string id)
{
await httpService.HttpDelete($"roles/{id}");
}
public async Task<GetByIdRoleResponse> GetById(string id)
{
var roleById = await httpService.HttpGet<GetByIdRoleResponse>($"roles/{id}");
return roleById;
}
public async Task<GetRoleMembershipResponse> GetMembershipByName(string name)
{
var roleMembershipByName = await httpService.HttpGet<GetRoleMembershipResponse>($"roles/{name}/members");
return roleMembershipByName;
}
public async Task DeleteUserFromRole(string userId, string roleId)
{
await httpService.HttpDelete($"roles/{roleId}/members/{userId}");
}
}
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FastEndpoints;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Identity;
using Microsoft.eShopWeb.Infrastructure.Identity;
namespace Microsoft.eShopWeb.PublicApi.RoleMembershipEndpoints;
public class RoleMembershipGetByNameEndpoint(UserManager<ApplicationUser> userManager) : Endpoint<GetRoleMembershipRequest, Results<Ok<GetRoleMembershipResponse>, NotFound>>
{
public override void Configure()
{
Get("api/roles/{roleName}/members");
Roles(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS);
AuthSchemes(JwtBearerDefaults.AuthenticationScheme);
Description(d =>
d.Produces<GetRoleMembershipResponse>()
.WithTags("RoleManagementEndpoints"));
}
public override async Task<Results<Ok<GetRoleMembershipResponse>, NotFound>> ExecuteAsync(GetRoleMembershipRequest request, CancellationToken ct)
{
var response = new GetRoleMembershipResponse(request.CorrelationId());
var members = await userManager.GetUsersInRoleAsync(request.RoleName);
if (members is null)
{
return TypedResults.NotFound();
}
response.RoleMembers = [.. members];
return TypedResults.Ok(response);
}
}
namespace Microsoft.eShopWeb.PublicApi.RoleMembershipEndpoints;
public class GetRoleMembershipRequest : BaseRequest
{
public string RoleName { get; init; }
public GetRoleMembershipRequest(string name)
{
RoleName = name;
}
}
using System;
using System.Collections.Generic;
using Microsoft.eShopWeb.Infrastructure.Identity;
namespace Microsoft.eShopWeb.PublicApi.RoleMembershipEndpoints;
public class GetRoleMembershipResponse : BaseResponse
{
public GetRoleMembershipResponse(Guid correlationId) : base(correlationId)
{
}
public GetRoleMembershipResponse()
{
}
public List<ApplicationUser> RoleMembers { get; set; }
}
using System.Threading.Tasks;
using BlazorShared.Authorization;
using Microsoft.eShopWeb;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PublicApiIntegrationTests.Helpers;
namespace PublicApiIntegrationTests.RoleMembershipEndpoints;
[TestClass]
public class RoleMembershipGetByNameEndpointTest
{
[TestMethod]
public async Task ReturnsMembersWithValidRoleName()
{
var client = HttpClientHelper.GetAdminClient();
var roleList = await client.GetAsync($"/api/roles/{Constants.Roles.ADMINISTRATORS}/members");
var roleMembersResponse = await roleList.Content.ReadAsStringAsync();
var model = roleMembersResponse.FromJson<GetRoleMembershipResponse>();
Assert.IsNotNull(model);
Assert.IsTrue(model.RoleMembers.Count > 0);
}
[TestMethod]
public async Task ReturnsEmptyListGivenInvalidName()
{
var client = HttpClientHelper.GetAdminClient();
var invalidName = "invalidName";
var getInvalidRoleNameMembership = await client.GetAsync($"api/roles/{invalidName}/members");
var response = await getInvalidRoleNameMembership.Content.ReadAsStringAsync();
var model = response.FromJson<GetRoleMembershipResponse>();
Assert.IsNotNull(model);
Assert.AreEqual(0, model.RoleMembers.Count);
}
}
@using BlazorAdmin.Interfaces
@using Microsoft.eShopWeb.Infrastructure.Identity
@inject ILogger<Details> Logger
@inject IJSRuntime JSRuntime
@inject IRoleManagementService RoleManagementService
@namespace BlazorAdmin.Pages.RolePage
<div class="modal @_modalClass" tabindex="-1" role="dialog" style="display:@_modalDisplay">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">@_roleName Role - Members</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" @onclick="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
@if (_members == null)
{
<Spinner></Spinner>
}
else
{
@if (_members.Count == 0)
{
<p>No members found.</p>
}
else
{
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody class="cursor-pointer">
@foreach (var member in _members)
{
<tr>
<td>@member.UserName</td>
<td>
@if (member.UserName != "[email protected]")
{
<button @onclick="@(() => DeleteClick(member.Id,member.UserName))" @onclick:stopPropagation="true" class="btn btn-danger">
Remove from Role
</button>
}
</td>
</tr>
}
</tbody>
</table>
}
}
</div>
</div>
</div>
</div>
<DeleteUserFromRole OnSaveClick="RefreshMembers" @ref="DeleteConfirmationComponent"></DeleteUserFromRole>
@if (_showDetailsModal)
{
<div class="modal-backdrop fade show"></div>
}
@code {
private string _modalDisplay = "none;";
private string _modalClass = "";
private bool _showDetailsModal = false;
private string _roleName = "";
private string _roleId = "";
private List<ApplicationUser> _members = new List<ApplicationUser>();
private DeleteUserFromRole DeleteConfirmationComponent { get; set; }
private async Task DeleteClick(string userId, string userName)
{
Logger.LogInformation("Confirming to remove User {userId} from {roleId}",userId, _roleId);
await DeleteConfirmationComponent.Open(userId, userName, _roleName, _roleId);
}
public async Task Open(string roleId, string roleName)
{
_roleName = roleName;
_roleId = roleId;
Logger.LogInformation("Now loading details... /Role/Details/{Id}", roleName, roleId);
_members = (await RoleManagementService.GetMembershipByName(roleName)).RoleMembers;
await ShowModal();
StateHasChanged();
}
public async Task Close()
{
await CloseModal();
}
private async Task ShowModal(){
await new Css(JSRuntime).HideBodyOverflow();
_modalDisplay = "block;";
_modalClass = "Show";
_showDetailsModal = true;
}
private async Task CloseModal(){
await new Css(JSRuntime).ShowBodyOverflow();
_modalDisplay = "none";
_modalClass = "";
_showDetailsModal = false;
}
private async Task RefreshMembers()
{
Logger.LogInformation("Refreshing members after removing a user from a role");
_members = (await RoleManagementService.GetMembershipByName(_roleName)).RoleMembers;
StateHasChanged();
}
}
@page "/roles"
@attribute [Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)]
@inherits BlazorAdmin.Helpers.BlazorComponent
@namespace BlazorAdmin.Pages.RolePage
<PageTitle>eShopOnWeb Admin: Manage Roles</PageTitle>
<h1>Manage Roles</h1>
@if (_roles == null)
{
<Spinner></Spinner>
}
else
{
<p class="esh-link-wrapper">
<button class="btn btn-primary" @onclick="@(CreateClick)">
Create New
</button>
</p>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody class="cursor-pointer">
@foreach (var role in _roles){
<tr>
<td>@role.Name</td>
<td>
<button @onclick="@(() => EditClick(role.Id))" @onclick:stopPropagation="true" class="btn btn-primary">
Edit
</button>
<button @onclick="@(() => DetailsClick(role.Id,role.Name))" @onclick:stopPropagation="true" class="btn btn-success">
Manage Members
</button>
<button @onclick="@(() => DeleteClick(role.Id))" @onclick:stopPropagation="true" class="btn btn-danger">
Delete
</button>
</td>
</tr>
}
</tbody>
</table>
<Create OnSaveClick="ReloadRoles" @ref="CreateComponent"></Create>
<Delete OnSaveClick="ReloadRoles" @ref="DeleteComponent"></Delete>
<Edit OnSaveClick="ReloadRoles" @ref="EditComponent"></Edit>
<Details @ref="DetailsComponent"></Details>
}
using System.Collections.Generic;
using System.Threading.Tasks;
using BlazorAdmin.Helpers;
using BlazorAdmin.Interfaces;
using Microsoft.AspNetCore.Identity;
namespace BlazorAdmin.Pages.RolePage;
public partial class List : BlazorComponent
{
[Microsoft.AspNetCore.Components.Inject]
public IRoleManagementService RoleManagementService { get; set; }
private List<IdentityRole> _roles = [];
private Create CreateComponent { get; set; }
private Delete DeleteComponent { get; set; }
private Edit EditComponent { get; set; }
private Details DetailsComponent { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var response = await RoleManagementService.List();
_roles = response.Roles;
CallRequestRefresh();
}
await base.OnAfterRenderAsync(firstRender);
}
private async void DetailsClick(string id, string name)
{
await DetailsComponent.Open(id, name);
}
private async Task CreateClick()
{
await CreateComponent.Open();
}
private async Task EditClick(string id)
{
await EditComponent.Open(id);
}
private async Task DeleteClick(string id)
{
await DeleteComponent.Open(id);
}
private async Task ReloadRoles()
{
var roleCall = await RoleManagementService.List();
_roles = roleCall.Roles;
StateHasChanged();
}
}

Comments are disabled for this gist.