@namespace OpenArchival.Blazor.AdminPages.Shared
@using OpenArchival.Blazor.AdminPages.Shared;
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.EntityFrameworkCore
@using MudBlazor
@using MudExtensions
@using OpenArchival.Blazor.ArchiveSearch
@using OpenArchival.DataAccess
@using Microsoft.AspNetCore.Identity
New User
Edit
@inject IArtifactGroupingProvider GroupingProvider;
@inject IDialogService DialogService;
@inject IArtifactGroupingProvider GroupingProvider;
@inject IDbContextFactory ContextFactory;
@inject ISnackbar Snackbar;
@inject NavigationManager NavigationManager;
@inject UserManager UserManager;
@inject RoleManager RoleManager;
@inject IDbContextFactory ContextFactory;
@code
{
public class UserRowElementComparer: IEqualityComparer
{
public bool Equals(UserDto? x, UserDto? y)
{
if (ReferenceEquals(x, y)) return true;
if (x is null || y is null) return false;
return x.Id == y.Id;
}
public int GetHashCode(UserDto obj)
{
return obj.Id.GetHashCode();
}
}
private UserRowElementComparer _comparer = new();
public MudDataGrid DataGrid { get; set; } = default!;
private HashSet _selectedItems = new(new UserRowElementComparer());
private string _searchString { get; set; } = "";
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
StateHasChanged();
}
private async Task CreateNewUser(MouseEventArgs args)
{
var options = new DialogOptions { CloseOnEscapeKey = true, BackdropClick = false};
var dialog = await DialogService.ShowAsync("Create a User", options);
var newUserDto = await dialog.Result;
if (newUserDto is not null && !newUserDto.Canceled)
{
var userDto = (UserDto?)newUserDto.Data;
if (userDto is null)
{
Snackbar.Add("The new user dialog did not return a user model.", Severity.Error);
return;
}
if (userDto.Username is null)
{
Snackbar.Add("The user model returned by the dialog did not have a username set.");
return;
}
if (userDto.Password is null)
{
Snackbar.Add("The user model returned by the dialog did not have a password.", Severity.Error);
return;
}
var appUser = UserDto.ToApplicationUser(userDto);
if (appUser.Email is null)
{
Snackbar.Add("The application user converted from the DTO did not have an email");
return;
}
ApplicationUser? existing = await UserManager.FindByEmailAsync(appUser.Email);
if (existing is not null)
{
Snackbar.Add("A user with this email already exists!", Severity.Warning);
return;
}
appUser.EmailConfirmed = true; // Bypass email confirmation
IdentityResult result = await UserManager.CreateAsync(appUser, userDto.Password);
if (result.Succeeded)
{
IdentityResult roleResult = await UserManager.AddToRolesAsync(appUser, userDto.Roles);
if (!roleResult.Succeeded)
{
Snackbar.Add("Failed to set roles on user!", Severity.Error);
foreach (IdentityError error in roleResult.Errors)
{
Snackbar.Add(error.Description, Severity.Error);
}
}
Snackbar.Add("User created!", Severity.Success);
await DataGrid.ReloadServerData();
StateHasChanged();
return;
}
else
{
Snackbar.Add($"Failed to create user with errors, check logs. {result.Errors.ToString()}");
return;
}
}
}
private async Task OnEditClicked(UserDto row)
{
// Save the old user dto for comparison
UserDto oldUserDto = new UserDto { Id = row.Id, Username = row.Username, Roles = new List(row.Roles)};
var options = new DialogOptions { CloseOnEscapeKey = true, BackdropClick = false};
var parameters = new DialogParameters
{
{x=>x.Model, row},
{x=>x.IsUpdate, true}
};
var dialog = await DialogService.ShowAsync("Update User", parameters, options);
var dialogResult = await dialog.Result;
if (dialogResult is not null && !dialogResult.Canceled)
{
var newUserDto = (UserDto?)dialogResult.Data;
if (newUserDto is null)
{
Snackbar.Add("Application error, user dialog returned a missing value.", Severity.Error);
return;
}
ApplicationUser? appUser = await UserManager.FindByIdAsync(row.Id);
if (appUser is null)
{
Snackbar.Add("Could not find edited user in the database!", Severity.Error);
return;
}
if (oldUserDto.Username != newUserDto.Username)
{
await UserManager.SetEmailAsync(appUser, newUserDto.Username);
var emailConfirmationToken = await UserManager.GenerateEmailConfirmationTokenAsync(appUser);
var confirmResult = await UserManager.ConfirmEmailAsync(appUser, emailConfirmationToken);
if (confirmResult is null || confirmResult.Errors.Any())
{
Snackbar.Add("Failed to confirm user's email address internally.", Severity.Error);
return;
}
var usernameResult = await UserManager.SetUserNameAsync(appUser, newUserDto.Username);
if (usernameResult is null || usernameResult.Errors.Any())
{
Snackbar.Add("Failed to update username.", Severity.Error);
return;
}
}
// Validate new password here
if (!string.IsNullOrWhiteSpace(newUserDto.Password)) {
var removeResult = await UserManager.RemovePasswordAsync(appUser);
if (!removeResult.Succeeded)
{
Snackbar.Add("Failed to remove old password for user", Severity.Error);
return;
}
var addResult = await UserManager.AddPasswordAsync(appUser, newUserDto.Password);
if (!addResult.Succeeded)
{
Snackbar.Add("Failed to set user's new password.", Severity.Error);
return;
}
}
// Overwrite roles if they have changed
if (!(new HashSet(oldUserDto.Roles).SetEquals(newUserDto.Roles)))
{
var currentRoles = await UserManager.GetRolesAsync(appUser);
var removeResult = await UserManager.RemoveFromRolesAsync(appUser, currentRoles);
if (!removeResult.Succeeded)
{
Snackbar.Add("Failed to update roles for user", Severity.Error);
return;
}
var addResult = await UserManager.AddToRolesAsync(appUser, newUserDto.Roles);
if (!addResult.Succeeded)
{
Snackbar.Add("Failed to add new roles to user.", Severity.Error);
return;
}
}
}
Snackbar.Add("User updated!", Severity.Success);
}
private async Task OnDeleteClicked(UserDto row)
{
// Check if we are trying to delete the last Admin user
// solution: https://stackoverflow.com/questions/20449008/count-the-number-of-users-in-a-role-in-aspnet-identity-not-membership
var context = await ContextFactory.CreateDbContextAsync();
IdentityRole adminRole = await context.Roles.FirstAsync(r => r.Name == UserRoles.Admin);
Int32 countAdminUsers = await context.Set().CountAsync();
if (countAdminUsers == 1)
{
Snackbar.Add("Cannot delete the only admin user!", Severity.Error);
return;
}
bool? confirmed = await DialogService.ShowMessageBox
(
new MessageBoxOptions(){
Message=$"Are you sure you want to delete this user?",
Title="Delete User?",
CancelText="Cancel",
YesText="Delete"
});
if (confirmed is not null)
{
// Grab the user from the database, ASP.NET identity will only allow you to
// delete the exact object from the database
if (row.Id is null)
{
Snackbar.Add("The user row ID was null, cannot delete", Severity.Error);
return;
}
ApplicationUser? appUser = await UserManager.FindByIdAsync(row.Id);
if (appUser is null)
{
Snackbar.Add("User no longer in database, cannot find by ID.", Severity.Error);
return;
}
IdentityResult result = await UserManager.DeleteAsync(appUser);
if (result.Succeeded)
{
await DataGrid.ReloadServerData();
StateHasChanged();
Snackbar.Add("User deleted!", Severity.Success);
} else
{
Snackbar.Add("Call to DeleteAsync failed.", Severity.Error);
foreach (var error in result.Errors)
{
Snackbar.Add(error.Description, Severity.Error);
}
}
}
}
private async Task> ServerReload(GridState state)
{
var context = await ContextFactory.CreateDbContextAsync();
int totalItems = await context.Users.CountAsync();
IEnumerable users = await context.Users
.Skip(state.Page * state.PageSize)
.Take(state.PageSize)
.Select(user=>new UserDto
{
Id = user.Id,
Username = user.UserName,
Roles = (from userRole in context.UserRoles
join role in context.Roles on userRole.RoleId equals role.Id
where userRole.UserId == user.Id
select role.Name).ToList()
}).ToListAsync();
return new GridData()
{
TotalItems = totalItems,
Items = users
};
}
public async Task> SelectedItems()
{
List selectedUsers = [];
foreach (UserDto row in DataGrid.SelectedItems)
{
await using var context = await ContextFactory.CreateDbContextAsync();
var user = await context.Users.Where(user => user.Id == row.Id).FirstAsync();
if (user is null)
{
Snackbar.Add($"User with id {row.Id} no longer exists in the database.", Severity.Error);
continue;
}
selectedUsers.Add(user);
}
return selectedUsers;
}
public async Task SetSelectedItemsAsync(IEnumerable postsToSelect)
{
var context = await ContextFactory.CreateDbContextAsync();
var rowElementsToSelect = postsToSelect.Select(user=> new UserDto
{
Id = user.Id,
Username = user.UserName,
Roles = (from userRole in context.UserRoles
join role in context.Roles on userRole.RoleId equals role.Id
select role.Name).ToList()
});
_selectedItems = new HashSet(rowElementsToSelect, _comparer);
_selectedItems = new HashSet();
StateHasChanged();
}
public void ClearSelected()
{
_selectedItems = new HashSet(_comparer);
}
}