Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save sadukie/edb05737837d5c87c7bdaf25972f23e5 to your computer and use it in GitHub Desktop.
Save sadukie/edb05737837d5c87c7bdaf25972f23e5 to your computer and use it in GitHub Desktop.
eShopOnWeb - User Management - UI
using System.Threading.Tasks;
using BlazorAdmin.Models;
namespace BlazorAdmin.Interfaces;
public interface IUserManagementService
{
Task<CreateUserResponse> Create(CreateUserRequest user);
Task<GetUserResponse> Update(UpdateUserRequest user);
Task Delete(string id);
Task<GetUserResponse> GetById(string id);
Task<GetUserResponse> GetByName(string userName);
Task<GetUserRolesResponse> GetRolesByUserId(string userId);
Task SaveRolesForUser(SaveRolesForUserRequest request);
Task<UserListResponse> List();
}
namespace BlazorAdmin.Models;
public class CreateUserRequest
{
public User User{ get; set; }
public CreateUserRequest()
{
User = new();
}
}
namespace BlazorAdmin.Models;
public class CreateUserResponse
{
public string UserId { get; set; }
}
using System.ComponentModel.DataAnnotations;
namespace BlazorAdmin.Models;
public class DeleteUserRequest
{
[Required(ErrorMessage = "The UserId field is required")]
public string UserId { get; set; }
}
namespace BlazorAdmin.Models;
public class GetUserResponse
{
public User User { get; set; }
public GetUserResponse()
{
User = new();
}
}
using System.Collections.Generic;
namespace BlazorAdmin.Models;
public class GetUserRolesResponse
{
public List<string> Roles { get; set; }
}
using System.Collections.Generic;
namespace BlazorAdmin.Models;
public class SaveRolesForUserRequest
{
public string UserId { get; set; }
public List<string> RolesToAdd { get; set; } = [];
public List<string> RolesToRemove { get; set; } = [];
}
namespace BlazorAdmin.Models;
public class UpdateUserRequest
{
public User User{ get; set; }
public UpdateUserRequest()
{
User = new();
}
}
using System;
using System.ComponentModel.DataAnnotations;
namespace BlazorAdmin.Models;
public class User
{
public string Id { get; set; }
[Required]
[RegularExpression(@"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", ErrorMessage = "Invalid UserName.")]
public string UserName { get; set; }
[Required]
[RegularExpression(@"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", ErrorMessage = "Invalid email address format.")]
public string Email { get; set; }
public bool EmailConfirmed { get; set; }
public string PhoneNumber { get; set; }
public bool PhoneNumberConfirmed { get; set; }
public bool TwoFactorEnabled { get; set; }
public DateTimeOffset? LockoutEnd { get; set; }
}
using System.Collections.Generic;
namespace BlazorAdmin.Models;
public class UserListResponse
{
public List<User> Users { get; set; } = [];
}
builder.Services.AddScoped<IUserManagementService, UserManagementService>();
using System.Threading.Tasks;
using BlazorAdmin.Interfaces;
using BlazorAdmin.Models;
using Microsoft.Extensions.Logging;
namespace BlazorAdmin.Services;
public class UserManagementService(HttpService httpService, ILogger<IUserManagementService> logger) : IUserManagementService
{
public async Task<CreateUserResponse> Create(CreateUserRequest user)
{
var response = await httpService.HttpPost<CreateUserResponse>($"users", user);
return response;
}
public async Task Delete(string userId)
{
await httpService.HttpDelete($"users/{userId}");
}
public async Task<GetUserResponse> Update(UpdateUserRequest user)
{
return await httpService.HttpPut<GetUserResponse>($"users", user);
}
public async Task<GetUserResponse> GetById(string userId)
{
return await httpService.HttpGet<GetUserResponse>($"users/{userId}");
}
public async Task<GetUserRolesResponse> GetRolesByUserId(string userId)
{
return await httpService.HttpGet<GetUserRolesResponse>($"users/{userId}/roles");
}
public async Task<GetUserResponse> GetByName(string userName) {
return await httpService.HttpGet<GetUserResponse>($"users/name/{userName}");
}
public async Task<UserListResponse> List()
{
logger.LogInformation("Fetching users");
return await httpService.HttpGet<UserListResponse>($"users");
}
public async Task SaveRolesForUser(SaveRolesForUserRequest request)
{
await httpService.HttpPut($"users/{request.UserId}/roles",request);
}
}
<AuthorizeView Context="IdentityManagement" [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>
<li class="nav-item px-3">
<NavLink class="nav-link" href="users">
<span class="oi oi-person" aria-hidden="true"></span> Users
</NavLink>
</li>
</AuthorizeView>
public async Task<HttpStatusCode> HttpPut(string uri, object dataToSend)
{
var content = ToJson(dataToSend);
var result = await _httpClient.PutAsync($"{_apiUrl}{uri}", content);
if (!result.IsSuccessStatusCode)
{
_toastService.ShowToast("Error", ToastLevel.Error);
return result.StatusCode;
}
_toastService.ShowToast($"Updated successfully!", ToastLevel.Success);
return result.StatusCode;
}
@using BlazorAdmin.Interfaces
@using BlazorAdmin.Models
@using Microsoft.AspNetCore.Identity
@inject ILogger<Create> Logger
@inject IJSRuntime JSRuntime
@inject IUserManagementService UserManagementService
@inject IRoleManagementService RoleManagementService
@namespace BlazorAdmin.Pages.UserPage
<div class="modal @_modalClass" tabindex="-1" role="dialog" style="display:@_modalDisplay">
<div class="modal-dialog" role="document">
<div class="modal-content">
@if (_item == null)
{
<Spinner></Spinner>
}
else
{
<EditForm Model="_item.User" 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">
<div class="container">
<div class="row">
<div class="col-md-12">
<!-- User Information -->
<div class="form-group row">
<label for="UserName" class="control-label col-md-6">Username</label>
<div class="col-md-6">
<InputText id="UserName" class="form-control" @bind-Value="_item.User.UserName" />
<ValidationMessage For="@(() => _item.User.UserName)" class="text-danger" />
</div>
</div>
<div class="form-group row">
<label for="Email" class="control-label col-md-6">Email</label>
<div class="col-md-6">
<InputText id="Email" class="form-control" @bind-Value="_item.User.Email" />
<ValidationMessage For="@(() => _item.User.Email)" class="text-danger" />
</div>
</div>
<div class="form-group row">
<label for="PhoneNumber" class="control-label col-md-6">Phone Number</label>
<div class="col-md-6">
<InputText id="PhoneNumber" class="form-control" @bind-Value="_item.User.PhoneNumber" />
<ValidationMessage For="@(() => _item.User.PhoneNumber)" class="text-danger" />
</div>
</div>
<div class="form-group row form-check">
<div class="col-md-6">
<InputCheckbox id="EmailConfirmed" class="form-check-input" @bind-Value="_item.User.EmailConfirmed" />
<label for="EmailConfirmed" class="form-check-label">Email Confirmed</label>
</div>
</div>
<div class="form-group row form-check">
<div class="col-md-6">
<InputCheckbox id="TwoFactorEnabled" class="form-check-input" @bind-Value="_item.User.TwoFactorEnabled" />
<label for="TwoFactorEnabled" class="form-check-label">Two-Factor Authentication Enabled</label>
</div>
</div>
<div class="form-group row">
<label for="LockoutEnd" class="control-label col-md-6">Lockout Until</label>
<div class="col-md-6">
<InputDate id="LockoutEnd" class="form-control" @bind-Value="_item.User.LockoutEnd" />
<ValidationMessage For="@(() => _item.User.LockoutEnd)" class="text-danger" />
</div>
</div>
</div>
</div>
</div>
<h4>Roles</h4>
@if (_roles == null || _roles.Count == 0)
{
<p>No roles available.</p>
}
else
{
@foreach (var role in _roles)
{
<div class="form-group row form-check">
<div class="col-md-6">
<input type="checkbox" id="@role" class="form-check-input" @onchange="(e) => UpdateRoleSelectedState(role.Name, (bool)((ChangeEventArgs)e).Value)" />
<label class="form-check-label" for="@role">@role</label>
</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 CreateUserRequest _item;
private Dictionary<string, bool> _selectedRoles = new();
private List<IdentityRole> _roles;
private async Task CreateClick()
{
if (_item.User != null)
{
var result = await UserManagementService.Create(_item);
var checkedRoles = _selectedRoles.Where(r => r.Value).Select(r => r.Key).ToList();
if (checkedRoles.Count > 0)
{
SaveRolesForUserRequest rolesForUserRequest = new SaveRolesForUserRequest()
{
UserId = result.UserId,
RolesToAdd = checkedRoles
};
await UserManagementService.SaveRolesForUser(rolesForUserRequest);
}
if (result != null)
{
Logger.LogInformation("Created User {userId}", result.UserId);
await OnSaveClick.InvokeAsync(null);
await Close();
}
}
}
public async Task Open()
{
Logger.LogInformation("Now loading... /Users/Create");
await new Css(JSRuntime).HideBodyOverflow();
_item = new CreateUserRequest();
_roles = (await RoleManagementService.List()).Roles;
_modalDisplay = "block;";
_modalClass = "Show";
_showCreateModal = true;
StateHasChanged();
}
private void UpdateRoleSelectedState(string role, bool isChecked)
{
_selectedRoles[role] = isChecked;
}
private async Task Close()
{
await new Css(JSRuntime).ShowBodyOverflow();
_modalDisplay = "none";
_modalClass = "";
_showCreateModal = false;
}
}
@inject ILogger<Delete> Logger
@inject IJSRuntime JSRuntime
@inject IUserManagementService UserManagementService
@inherits BlazorAdmin.Helpers.BlazorComponent
@namespace BlazorAdmin.Pages.UserPage
@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 (_userId is null || _userName is null)
{
<Spinner></Spinner>
}
else
{
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Delete @_userName</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> this user: <strong>@_userName</strong>?
</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(_userId)">
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 _userId = "";
private string _userName = "";
private async Task DeleteClick(string id)
{
await UserManagementService.Delete(id);
Logger.LogInformation("Deleted user Id: {id}", id);
await OnSaveClick.InvokeAsync(null);
await Close();
}
public async Task Open(string id, string userName)
{
await new Css(JSRuntime).HideBodyOverflow();
_userId = id;
_userName = userName;
_modalDisplay = "block;";
_modalClass = "Show";
_showDeleteModal = true;
StateHasChanged();
}
private async Task Close()
{
await new Css(JSRuntime).ShowBodyOverflow();
_modalDisplay = "none";
_modalClass = "";
_showDeleteModal = false;
}
}
@inject ILogger<Edit> Logger
@inject IJSRuntime JSRuntime
@inject IUserManagementService UserManagementService
@inject IRoleManagementService RoleManagementService
@inherits BlazorAdmin.Helpers.BlazorComponent
@namespace BlazorAdmin.Pages.UserPage
@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 == null)
{
<Spinner></Spinner>
}
else
{
<EditForm Model="_item" OnValidSubmit="@SaveClick">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Edit @_item.UserName</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">
<div class="col-md-12">
<!-- User Information -->
<div class="form-group row">
<label for="UserName" class="control-label col-md-6">Username</label>
<div class="col-md-6">
<InputText id="UserName" class="form-control" @bind-Value="_item.UserName" />
<ValidationMessage For="@(() => _item.UserName)" class="text-danger" />
</div>
</div>
<div class="form-group row">
<label for="Email" class="control-label col-md-6">Email</label>
<div class="col-md-6">
<InputText id="Email" class="form-control" @bind-Value="_item.Email" />
<ValidationMessage For="@(() => _item.Email)" class="text-danger" />
</div>
</div>
<div class="form-group row">
<label for="PhoneNumber" class="control-label col-md-6">Phone Number</label>
<div class="col-md-6">
<InputText id="PhoneNumber" class="form-control" @bind-Value="_item.PhoneNumber" />
<ValidationMessage For="@(() => _item.PhoneNumber)" class="text-danger" />
</div>
</div>
<div class="form-group row form-check">
<div class="col-md-6">
<InputCheckbox id="EmailConfirmed" class="form-check-input" @bind-Value="_item.EmailConfirmed" />
<label for="EmailConfirmed" class="form-check-label">Email Confirmed</label>
</div>
</div>
<div class="form-group row form-check">
<div class="col-md-6">
<InputCheckbox id="TwoFactorEnabled" class="form-check-input" @bind-Value="_item.TwoFactorEnabled" />
<label for="TwoFactorEnabled" class="form-check-label">Two-Factor Authentication Enabled</label>
</div>
</div>
<div class="form-group row">
<label for="LockoutEnd" class="control-label col-md-6">Lockout Until</label>
<div class="col-md-6">
<InputDate id="LockoutEnd" class="form-control" @bind-Value="_item.LockoutEnd" />
<ValidationMessage For="@(() => _item.LockoutEnd)" class="text-danger" />
</div>
</div>
</div>
</div>
</div>
<h4>Roles</h4>
@if (_roles == null || _roles.Count == 0)
{
<p>No roles available.</p>
}
else
{
@foreach (var role in _roles)
{
<div class="form-group row form-check">
<div class="col-md-6">
<input type="checkbox" id="@role" class="form-check-input" checked="@_selectedRoles[role.Name]" @onchange="(e) => UpdateRoleSelectedState(role.Name, (bool)((ChangeEventArgs)e).Value)" />
<label class="form-check-label" for="@role">@role</label>
</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 User _item = new User();
private Dictionary<string, bool> _selectedRoles = new();
private Dictionary<string, bool> _startingRoles = new();
private List<IdentityRole> _roles;
private async Task SaveClick()
{
var updateRequest = new UpdateUserRequest()
{
User = _item
};
await UserManagementService.Update(updateRequest);
var checkedRoles = _selectedRoles.Where(r => r.Value).Select(r => r.Key).ToList();
var startingRoles = _startingRoles.Where(r => r.Value).Select(r => r.Key).ToList();
var rolesToRemove = startingRoles.Except(checkedRoles).ToList();
var rolesToAdd = checkedRoles.Except(startingRoles).ToList();
if (rolesToAdd.Any() || rolesToRemove.Any())
{
SaveRolesForUserRequest rolesForUserRequest = new SaveRolesForUserRequest()
{
UserId = _item.Id,
RolesToAdd = rolesToAdd,
RolesToRemove = rolesToRemove
};
await UserManagementService.SaveRolesForUser(rolesForUserRequest);
}
Logger.LogInformation("Updated User Id: {userId}", _item.Id);
await OnSaveClick.InvokeAsync(null);
await Close();
}
public async Task Open(string id)
{
Logger.LogInformation("Now loading... /Users/Edit/{Id}", id);
await new Css(JSRuntime).HideBodyOverflow();
_item = (await UserManagementService.GetById(id)).User;
_roles = (await RoleManagementService.List()).Roles;
var userRoles = await UserManagementService.GetRolesByUserId(id);
foreach (var role in _roles)
{
var checkedStatus = userRoles.Roles.Contains(role.Name);
_selectedRoles[role.Name] = checkedStatus;
_startingRoles[role.Name] = checkedStatus;
}
Logger.LogInformation("Acquired user {id}", _item.Id);
_modalDisplay = "block;";
_modalClass = "Show";
_showEditModal = true;
StateHasChanged();
}
private void UpdateRoleSelectedState(string role, bool isChecked)
{
_selectedRoles[role] = isChecked;
}
private async Task Close()
{
await new Css(JSRuntime).ShowBodyOverflow();
_modalDisplay = "none";
_modalClass = "";
_showEditModal = false;
}
}
@page "/users"
@attribute [Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)]
@inherits BlazorAdmin.Helpers.BlazorComponent
@namespace BlazorAdmin.Pages.UserPage
<PageTitle>eShopOnWeb Admin: Manage Users</PageTitle>
<h1>Manage Users</h1>
@if (_users == 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>Email</th>
<th>Actions</th>
</tr>
</thead>
<tbody class="cursor-pointer">
@foreach (var user in _users)
{
<tr>
<td>@user.Email</td>
<td>
@if (_currentUser is null || _currentUser.Identity is null)
{
<p>Unknown current user</p>
} else if (user.UserName != _currentUser.Identity.Name && user.UserName != "[email protected]")
{
<button @onclick="@(() => EditClick(user.Id))" @onclick:stopPropagation="true" class="btn btn-primary">
Edit
</button>
<button @onclick="@(() => DeleteClick(user.Id,user.UserName))" @onclick:stopPropagation="true" class="btn btn-danger">
Delete
</button>
}
</td>
</tr>
}
</tbody>
</table>
<Create OnSaveClick="ReloadUsers" @ref="CreateComponent"></Create>
<Delete OnSaveClick="ReloadUsers" @ref="DeleteComponent"></Delete>
<Edit OnSaveClick="ReloadUsers" @ref="EditComponent"></Edit>
}
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using BlazorAdmin.Helpers;
using BlazorAdmin.Interfaces;
using BlazorAdmin.Models;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Logging;
namespace BlazorAdmin.Pages.UserPage;
public partial class List : BlazorComponent
{
[Microsoft.AspNetCore.Components.Inject]
public IUserManagementService UserManagementService{ get; set; }
[Microsoft.AspNetCore.Components.Inject]
ILogger<List> Logger { get; set; }
[Microsoft.AspNetCore.Components.Inject]
public AuthenticationStateProvider AuthProvider { get; set; }
private List<User> _users = [];
private ClaimsPrincipal _currentUser = null;
private Create CreateComponent { get; set; }
private Delete DeleteComponent { get; set; }
private Edit EditComponent { get; set; }
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
var getCurrentClaim = await AuthProvider.GetAuthenticationStateAsync();
_currentUser = getCurrentClaim.User;
Logger.LogInformation("Current User: {0}", _currentUser.Identity.Name);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var response = await UserManagementService.List();
_users = response.Users;
CallRequestRefresh();
}
await base.OnAfterRenderAsync(firstRender);
}
private async Task CreateClick()
{
await CreateComponent.Open();
}
private async Task EditClick(string id)
{
Logger.LogInformation("Edit User {id}", id);
await EditComponent.Open(id);
}
private async Task DeleteClick(string id, string userName)
{
Logger.LogInformation("Displaying delete confirmation for User {id}", id);
await DeleteComponent.Open(id, userName);
}
private async Task ReloadUsers()
{
var usersCall = await UserManagementService.List();
_users = usersCall.Users;
StateHasChanged();
}
}

Comments are disabled for this gist.