Skip to content

Instantly share code, notes, and snippets.

@sadukie
Last active April 3, 2025 18:43
Show Gist options
  • Save sadukie/1cd42481fa4bfa1df8a4d7c5f04dc7ea to your computer and use it in GitHub Desktop.
Save sadukie/1cd42481fa4bfa1df8a4d7c5f04dc7ea to your computer and use it in GitHub Desktop.
eShopOnWeb-Add-Role-Management
using System.ComponentModel.DataAnnotations;
namespace BlazorAdmin.Models;
public class CreateRoleRequest
{
[Required(ErrorMessage = "The Name field is required")]
public string Name { get; set; }
}
using Microsoft.AspNetCore.Identity;
namespace BlazorAdmin.Models;
public class CreateRoleResponse
{
public IdentityRole Role { get; set; }
}
using Microsoft.AspNetCore.Identity;
namespace BlazorAdmin.Models;
public class GetByIdRoleResponse
{
public IdentityRole Role { get; set; }
}
using System.ComponentModel.DataAnnotations;
namespace BlazorAdmin.Models;
public class DeleteRoleRequest
{
[Required(ErrorMessage = "The RoleId field is required")]
public string RoleId { get; set; }
}
using System.Collections.Generic;
using Microsoft.AspNetCore.Identity;
namespace BlazorAdmin.Models;
public class RoleListResponse
{
public List<IdentityRole> Roles { get; set; } = new List<IdentityRole>();
}
@using BlazorAdmin.Interfaces
@using BlazorAdmin.Models
@inject ILogger<Create> 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">
<EditForm Model="_item" OnValidSubmit="@CreateClick">
<DataAnnotationsValidator />
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Create</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 (_item == null)
{
<Spinner></Spinner>
}
else
{
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label class="control-label col-md-6">Name</label>
<div class="col-md-12">
<InputText class="form-control" @bind-Value="_item.Name" />
<ValidationMessage For="(() => _item.Name)" />
</div>
</div>
</div>
</div>
</div>
}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" @onclick="Close">Cancel</button>
<button type="submit" class="btn btn-primary">
Create
</button>
</div>
</EditForm>
</div>
</div>
</div>
@if (_showCreateModal)
{
<div class="modal-backdrop fade show"></div>
}
@code {
[Parameter]
public EventCallback<string> OnSaveClick { get; set; }
private string _modalDisplay = "none;";
private string _modalClass = "";
private bool _showCreateModal = false;
private CreateRoleRequest _item = new CreateRoleRequest();
private async Task CreateClick()
{
var result = await RoleManagementService.Create(_item);
if (result != null)
{
Logger.LogInformation($"Created {result.Role.Name} Role ({result.Role.Id})");
await OnSaveClick.InvokeAsync(null);
await Close();
}
}
public async Task Open()
{
Logger.LogInformation("Now loading... /Roles/Create");
await new Css(JSRuntime).HideBodyOverflow();
_item = new CreateRoleRequest();
_modalDisplay = "block;";
_modalClass = "Show";
_showCreateModal = true;
StateHasChanged();
}
private async Task Close()
{
await new Css(JSRuntime).ShowBodyOverflow();
_modalDisplay = "none";
_modalClass = "";
_showCreateModal = false;
}
}
namespace Microsoft.eShopWeb.PublicApi.RoleManagementEndpoints;
public class CreateRoleRequest : BaseRequest
{
public string Name { get; set; }
}
using System;
using Microsoft.AspNetCore.Identity;
namespace Microsoft.eShopWeb.PublicApi.RoleManagementEndpoints;
public class CreateRoleResponse : BaseResponse
{
public CreateRoleResponse(Guid correlationId)
{
}
public CreateRoleResponse() { }
public IdentityRole Role { get; set; }
}
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FastEndpoints;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.eShopWeb.ApplicationCore.Exceptions;
namespace Microsoft.eShopWeb.PublicApi.RoleManagementEndpoints;
public class CreateRoleEndpoint(RoleManager<IdentityRole> roleManager) : Endpoint<CreateRoleRequest,CreateRoleResponse>
{
public override void Configure()
{
Post("api/roles");
Roles(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS);
AuthSchemes(JwtBearerDefaults.AuthenticationScheme);
Description(d =>
d.Produces<CreateRoleResponse>()
.WithTags("RoleManagementEndpoints")
);
}
public override async Task HandleAsync(CreateRoleRequest request, CancellationToken ct)
{
var response = new CreateRoleResponse(request.CorrelationId());
var existingRole = await roleManager.FindByNameAsync(request.Name);
if (existingRole != null) {
throw new DuplicateException($"A role with name {request.Name} already exists");
}
var newRole = new IdentityRole(request.Name);
var createRole = await roleManager.CreateAsync(newRole);
if (createRole.Succeeded)
{
var responseRole = await roleManager.FindByNameAsync(request.Name);
response.Role = responseRole!;
await SendCreatedAtAsync<RoleGetByIdEndpoint>(new { RoleId = response.Role.Id }, response, cancellation: ct);
}
}
}
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using BlazorShared.Authorization;
using Microsoft.eShopWeb;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PublicApiIntegrationTests.Helpers;
namespace PublicApiIntegrationTests.RoleManagementEndpoints;
[TestClass]
public class CreateRoleEndpointTest
{
private string _testName = "test role";
[TestMethod]
public async Task ReturnsForbiddenGivenNormalUserToken()
{
var jsonContent = GetValidNewItemJson();
var client = HttpClientHelper.GetNormalUserClient();
var response = await client.PostAsync("api/roles", jsonContent);
Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode);
}
[TestMethod]
public async Task ReturnsSuccessGivenValidNewItemAndAdminUserToken()
{
var jsonContent = GetValidNewItemJson();
var client = HttpClientHelper.GetAdminClient();
var response = await client.PostAsync("api/roles", jsonContent);
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var model = stringResponse.FromJson<CreateRoleResponse>();
Assert.IsNotNull(model);
Assert.IsNotNull(model.Role);
Assert.AreEqual(_testName, model.Role.Name);
}
[TestMethod]
public async Task ReturnsConflictForDuplicateRoleName()
{
var request = new CreateRoleRequest()
{
Name = Constants.Roles.ADMINISTRATORS
};
var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
var client = HttpClientHelper.GetAdminClient();
var response = await client.PostAsync("api/roles", jsonContent);
Assert.AreEqual(HttpStatusCode.Conflict, response.StatusCode);
}
private StringContent GetValidNewItemJson()
{
var request = new CreateRoleRequest()
{
Name = _testName
};
var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
return jsonContent;
}
}
@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">
@if (_item is null || _item.Role is null)
{
<Spinner></Spinner>
}
else
{
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Delete @_item.Role.Name</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">DELETE</strong> the <strong>@_item.Role.Name</strong> role?
</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(_item.Role.Id)">
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 GetByIdRoleResponse _item = new GetByIdRoleResponse();
private async Task DeleteClick(string id)
{
await RoleManagementService.Delete(id);
Logger.LogInformation("Deleted Role Id: {id}", id);
await OnSaveClick.InvokeAsync(null);
await Close();
}
public async Task Open(string id)
{
await new Css(JSRuntime).HideBodyOverflow();
_item = await RoleManagementService.GetById(id);
Logger.LogInformation("Loaded role: {id}", id);
_modalDisplay = "block;";
_modalClass = "Show";
_showDeleteModal = true;
StateHasChanged();
}
private async Task Close()
{
await new Css(JSRuntime).ShowBodyOverflow();
_modalDisplay = "none";
_modalClass = "";
_showDeleteModal = false;
}
}
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using FastEndpoints;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using System.Threading;
using Microsoft.eShopWeb.Infrastructure.Identity;
using System.Linq;
using Microsoft.eShopWeb.ApplicationCore.Exceptions;
namespace Microsoft.eShopWeb.PublicApi.RoleManagementEndpoints;
public class DeleteRoleEndpoint(RoleManager<IdentityRole> roleManager, UserManager<ApplicationUser> userManager) : Endpoint<DeleteRoleRequest, Results<NoContent, NotFound>>
{
public override void Configure()
{
Delete("api/roles/{roleId}");
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(DeleteRoleRequest request, CancellationToken ct)
{
var roleToDelete = await roleManager.FindByIdAsync(request.RoleId);
if (roleToDelete is null)
{
return TypedResults.NotFound();
}
if (string.IsNullOrEmpty(roleToDelete.Name))
{
throw new System.Exception("Unknown role to delete");
}
// Without this, the RoleManager will delete the role and treat it as a cascading delete.
// If we accidentally deleted an important role, that would not be a good day.
var usersWithRole = await userManager.GetUsersInRoleAsync(roleToDelete.Name);
if (usersWithRole.Any())
{
throw new RoleStillAssignedException($"The {roleToDelete.Name} role is in use and cannot be deleted.");
}
await roleManager.DeleteAsync(roleToDelete);
return TypedResults.NoContent();
}
}
using System;
namespace Microsoft.eShopWeb.PublicApi.RoleManagementEndpoints;
public class DeleteRoleRequest : BaseRequest
{
public string RoleId { get; init; }
public DeleteRoleRequest(string roleId)
{
RoleId = roleId;
}
}
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using BlazorShared.Authorization;
using Microsoft.eShopWeb;
using Microsoft.eShopWeb.PublicApi.RoleManagementEndpoints;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PublicApiIntegrationTests.Helpers;
namespace PublicApiIntegrationTests.RoleManagementEndpoints;
[TestClass]
public class DeleteRoleEndpointTest
{
[TestMethod]
public async Task ReturnsNotFoundGivenInvalidIdAndAdminUserToken()
{
var client = HttpClientHelper.GetAdminClient();
var response = await client.DeleteAsync("api/roles/0");
Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode);
}
[TestMethod]
public async Task ReturnsConflictWhenDeletingAnAssignedRole()
{
var client = HttpClientHelper.GetAdminClient();
// Get the role id for Product Manager
var roleList = await client.GetAsync("/api/roles");
var stringResponse = await roleList.Content.ReadAsStringAsync();
var model = stringResponse.FromJson<RoleListResponse>();
Assert.IsNotNull(model);
Assert.IsNotNull(model.Roles);
var administrator = model.Roles.FirstOrDefault(x => x.Name == Constants.Roles.ADMINISTRATORS);
Assert.IsNotNull(administrator);
// Try to delete it with it already assigned
var response = await client.DeleteAsync($"api/roles/{administrator.Id}");
Assert.AreEqual(HttpStatusCode.Conflict, response.StatusCode);
}
}
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<TargetFramework>net9.0</TargetFramework>
<!--<AspNetVersion>8.0.8</AspNetVersion>
<SystemExtensionVersion>8.0.0</SystemExtensionVersion>
<EntityFramworkCoreVersion>8.0.10</EntityFramworkCoreVersion>
<VSCodeGeneratorVersion>8.0.3</VSCodeGeneratorVersion>-->
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Ardalis.ApiEndpoints" Version="4.1.0" />
<PackageVersion Include="Ardalis.GuardClauses" Version="5.0.0" />
<PackageVersion Include="Ardalis.Specification.EntityFrameworkCore" Version="8.0.0" />
<PackageVersion Include="Ardalis.Result" Version="10.1.0" />
<PackageVersion Include="Ardalis.Specification" Version="8.0.0" />
<PackageVersion Include="Ardalis.ListStartupServices" Version="1.1.4" />
<PackageVersion Include="AspNetCore.Security.OAuth.Extensions" Version="1.0.0" />
<PackageVersion Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.3.2" />
<PackageVersion Include="Azure.Identity" Version="1.13.2" />
<PackageVersion Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageVersion Include="BlazorInputFile" Version="0.2.0" />
<PackageVersion Include="Blazored.LocalStorage" Version="4.5.0" />
<PackageVersion Include="BuildBundlerMinifier" Version="3.2.449" PrivateAssets="All" />
<PackageVersion Include="FastEndpoints" Version="5.34.0" />
<PackageVersion Include="FastEndpoints.Swagger" Version="5.34.0" />
<PackageVersion Include="FluentValidation" Version="11.11.0" />
<PackageVersion Include="MediatR" Version="12.4.1" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.2" PrivateAssets="all" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Identity.Stores" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="9.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageVersion Include="Microsoft.Extensions.Identity.Core" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging.Configuration" Version="9.0.1" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
<Package.sVersion Include="Microsoft.Web.LibraryManager.Build" Version="2.1.175" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
<PackageVersion Include="MinimalApi.Endpoint" Version="1.3.0" />
<PackageVersion Include="NSubstitute" Version="5.3.0" />
<PackageVersion Include="NSubstitute.Analyzers.CSharp" Version="1.0.17" />
<PackageVersion Include="System.Net.Http.Json" Version="9.0.1" />
<PackageVersion Include="System.Security.Claims" Version="4.3.0" />
<PackageVersion Include="System.Text.Json" Version="9.0.1" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.3.0" />
<!-- Aspire -->
<PackageVersion Include="Aspire.Hosting.AppHost" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="9.1.0" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="9.0.0" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.11.1" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.11.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.11.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.11.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.11.0" />
<PackageVersion Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.10.0-beta.1" />
<!-- Test -->
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageVersion>
<PackageVersion Include="xunit.runner.console" Version="2.9.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageVersion>
<PackageVersion Include="MSTest.TestAdapter" Version="3.7.3" />
<PackageVersion Include="MSTest.TestFramework" Version="3.7.3" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
</ItemGroup>
</Project>
@inject ILogger<Edit> 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">
<EditForm Model="_item" OnValidSubmit="@SaveClick">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Edit @_item.Name</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 (_item == null)
{
<Spinner></Spinner>
}
else
{
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label class="control-label col-md-6">Name</label>
<div class="col-md-12">
<InputText class="form-control" @bind-Value="_item.Name" />
<ValidationMessage For="(() => _item.Name)" />
</div>
</div>
</div>
</div>
</div>
}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" @onclick="Close">Cancel</button>
<button type="submit" class="btn btn-primary">
Save
</button>
</div>
</EditForm>
</div>
</div>
</div>
@if (_showEditModal)
{
<div class="modal-backdrop fade show"></div>
}
@code {
[Parameter]
public EventCallback<string> OnSaveClick { get; set; }
private string _modalDisplay = "none;";
private string _modalClass = "";
private bool _showEditModal = false;
private IdentityRole _item = new IdentityRole();
private async Task SaveClick()
{
await RoleManagementService.Edit(_item);
Logger.LogInformation($"Updated Role Id: {_item.Id} with name {_item.Name}");
await OnSaveClick.InvokeAsync(null);
await Close();
}
public async Task Open(string id)
{
Logger.LogInformation("Now loading... /Roles/Edit/{Id}", id);
await new Css(JSRuntime).HideBodyOverflow();
_item = (await RoleManagementService.GetById(id)).Role;
_modalDisplay = "block;";
_modalClass = "Show";
_showEditModal = true;
StateHasChanged();
}
private async Task Close()
{
await new Css(JSRuntime).ShowBodyOverflow();
_modalDisplay = "none";
_modalClass = "";
_showEditModal = false;
}
}
if (exception is DuplicateException || exception is RoleStillAssignedException)
{
context.Response.StatusCode = (int)HttpStatusCode.Conflict;
await context.Response.WriteAsync(new ErrorDetails()
{
StatusCode = context.Response.StatusCode,
Message = exception.Message
}.ToString());
}
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<HttpStatusCode> HttpDelete(string uri)
{
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 result.StatusCode;
}
_toastService.ShowToast($"Deleted successfully!", ToastLevel.Success);
return result.StatusCode;
}
using System.Collections.Generic;
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<GetByIdRoleResponse> GetById(string id);
Task<RoleListResponse> List();
}
@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="@(() => 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>
}
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 = new List<IdentityRole>();
private Create CreateComponent { get; set; }
private Delete DeleteComponent { get; set; }
private Edit EditComponent { 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 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();
}
}
<AuthorizeView Context="RolesManagement" [email protected]>
<li class="nav-item px-3">
<NavLink class="nav-link" href="roles">
<span class="oi oi-person" aria-hidden="true"></span> Roles
</NavLink>
</li>
</AuthorizeView>
builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();
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;
namespace Microsoft.eShopWeb.PublicApi.RoleManagementEndpoints;
public class RoleGetByIdEndpoint (RoleManager<IdentityRole> roleManager) : Endpoint <GetByIdRoleRequest, Results<Ok<GetByIdRoleResponse>,NotFound>>
{
public override void Configure()
{
Get("api/roles/{roleId}");
Roles(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS);
AuthSchemes(JwtBearerDefaults.AuthenticationScheme);
Description(d =>
d.Produces<GetByIdRoleResponse>()
.WithTags("RoleManagementEndpoints"));
}
public override async Task<Results<Ok<GetByIdRoleResponse>, NotFound>> ExecuteAsync(GetByIdRoleRequest request, CancellationToken ct)
{
var response = new GetByIdRoleResponse(request.CorrelationId());
var role = await roleManager.FindByIdAsync(request.RoleId);
if (role is null)
{
return TypedResults.NotFound();
}
response.Role = role;
return TypedResults.Ok(response);
}
}
namespace Microsoft.eShopWeb.PublicApi.RoleManagementEndpoints;
public class GetByIdRoleRequest : BaseRequest
{
public string RoleId { get; init; }
public GetByIdRoleRequest(string roleId)
{
RoleId = roleId;
}
}
using System;
using Microsoft.AspNetCore.Identity;
namespace Microsoft.eShopWeb.PublicApi.RoleManagementEndpoints;
public class GetByIdRoleResponse : BaseResponse
{
public GetByIdRoleResponse(Guid correlationId) : base(correlationId)
{
}
public GetByIdRoleResponse()
{
}
public IdentityRole Role { get; set; }
}
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using BlazorShared.Authorization;
using Microsoft.eShopWeb;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PublicApiIntegrationTests.Helpers;
namespace PublicApiIntegrationTests.RoleManagementEndpoints;
[TestClass]
public class RoleGetByIdEndpointTest
{
[TestMethod]
public async Task ReturnsItemGivenValidId()
{
var client = HttpClientHelper.GetAdminClient();
var roleList = await client.GetAsync("/api/roles");
var getAllRolesResponse = await roleList.Content.ReadAsStringAsync();
var model = getAllRolesResponse.FromJson<RoleListResponse>();
Assert.IsNotNull(model);
Assert.IsNotNull(model.Roles);
var adminRole = model.Roles.FirstOrDefault(x => x.Name == Constants.Roles.ADMINISTRATORS);
Assert.IsNotNull(adminRole);
var response = await client.GetAsync($"api/roles/{adminRole.Id}");
response.EnsureSuccessStatusCode();
var adminRoleResponse = await response.Content.ReadAsStringAsync();
var adminModel = adminRoleResponse.FromJson<GetByIdRoleResponse>();
Assert.IsNotNull(adminModel);
Assert.IsNotNull(adminModel.Role);
Assert.AreEqual(Constants.Roles.ADMINISTRATORS, adminModel.Role.Name);
}
[TestMethod]
public async Task ReturnsNotFoundGivenInvalidId()
{
var client = HttpClientHelper.GetAdminClient();
var response = await client.GetAsync("api/roles/0");
Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode);
}
}
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FastEndpoints;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
namespace Microsoft.eShopWeb.PublicApi.RoleManagementEndpoints;
public class RoleListEndpoint(RoleManager<IdentityRole> roleManager):EndpointWithoutRequest<RoleListResponse>
{
public override void Configure()
{
Get("api/roles");
Roles(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS);
AuthSchemes(JwtBearerDefaults.AuthenticationScheme);
Description(d => d.Produces<RoleListResponse>()
.WithTags("RoleManagementEndpoints"));
}
public override async Task<RoleListResponse> ExecuteAsync(CancellationToken ct)
{
await Task.Delay(1000, ct);
var response = new RoleListResponse();
response.Roles = roleManager.Roles.ToList();
return response;
}
}
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Identity;
namespace Microsoft.eShopWeb.PublicApi.RoleManagementEndpoints;
public class RoleListResponse : BaseResponse
{
public RoleListResponse(Guid correlationId) : base(correlationId)
{
}
public RoleListResponse()
{
}
public List<IdentityRole> Roles { get; set; } = new List<IdentityRole>();
}
using System.Net;
using System.Threading.Tasks;
using Microsoft.eShopWeb;
using Microsoft.eShopWeb.PublicApi.RoleManagementEndpoints;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PublicApiIntegrationTests.Helpers;
namespace PublicApiIntegrationTests.RoleManagementEndpoints;
[TestClass]
public class RoleListEndpointTest
{
[TestMethod]
public async Task ReturnsUnauthorizedForAnonymousAccess()
{
var client = ProgramTest.NewClient;
var response = await client.GetAsync("/api/roles");
Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode);
}
[TestMethod]
public async Task ReturnsForbiddenForGeneralAuthorizedAccess()
{
var client = HttpClientHelper.GetNormalUserClient();
var response = await client.GetAsync("/api/roles");
Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode);
}
[TestMethod]
public async Task ReturnsSuccessAndRolesForAdministratorAccess()
{
var client = HttpClientHelper.GetAdminClient();
var response = await client.GetAsync("/api/roles");
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var model = stringResponse.FromJson<RoleListResponse>();
Assert.IsNotNull(model);
Assert.IsNotNull(model.Roles);
Assert.IsTrue(model.Roles.Count > 0);
}
}
using System.Threading.Tasks;
using BlazorAdmin.Interfaces;
using BlazorAdmin.Models;
using BlazorShared.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;
}
}
using System;
namespace Microsoft.eShopWeb.ApplicationCore.Exceptions;
public class RoleStillAssignedException : Exception
{
public RoleStillAssignedException(string message) : base(message)
{
}
}
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;
namespace Microsoft.eShopWeb.PublicApi.RoleManagementEndpoints;
public class UpdateRoleEndpoint(RoleManager<IdentityRole> roleManager) : Endpoint<UpdateRoleRequest,Results<Ok<UpdateRoleResponse>,NotFound>>
{
public override void Configure()
{
Put("api/roles");
Roles(BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS);
AuthSchemes(JwtBearerDefaults.AuthenticationScheme);
Description(d =>
d.Produces<UpdateRoleResponse>()
.WithTags("RoleManagementEndpoints"));
}
public override async Task<Results<Ok<UpdateRoleResponse>, NotFound>> ExecuteAsync(UpdateRoleRequest request, CancellationToken ct)
{
var response = new UpdateRoleResponse(request.CorrelationId());
if (request is null)
{
return TypedResults.NotFound();
}
var existingRole = await roleManager.FindByIdAsync(request.Id);
if (existingRole == null)
{
return TypedResults.NotFound();
}
existingRole.Name = request.Name;
var result = await roleManager.UpdateAsync(existingRole);
response.Role = (await roleManager.FindByIdAsync(existingRole.Id))!;
return TypedResults.Ok(response);
}
}
namespace Microsoft.eShopWeb.PublicApi.RoleManagementEndpoints;
public class UpdateRoleRequest : BaseRequest
{
public string Name { get; set; }
public string Id { get; set; }
}
using System;
using Microsoft.AspNetCore.Identity;
namespace Microsoft.eShopWeb.PublicApi.RoleManagementEndpoints;
public class UpdateRoleResponse : BaseResponse
{
public UpdateRoleResponse(Guid correlationId) : base(correlationId)
{
}
public UpdateRoleResponse()
{
}
public IdentityRole Role { get; set; }
}

Comments are disabled for this gist.