Add new user creation

This commit is contained in:
2025-10-19 19:09:44 +09:00
parent 4b6f626f29
commit 42c0509978
14 changed files with 710 additions and 157 deletions

View File

@ -17,9 +17,16 @@
<RadzenStack Style="height: 100%" JustifyContent="JustifyContent.SpaceBetween"> <RadzenStack Style="height: 100%" JustifyContent="JustifyContent.SpaceBetween">
<RadzenPanelMenu> <RadzenPanelMenu>
<RadzenPanelMenuItem Path="/" Text="Главная" Icon="home" /> <RadzenPanelMenuItem Path="/" Text="Главная" Icon="home" />
<RadzenPanelMenuItem Text="Тестирование" Icon="simulation"> <AuthorizeView Roles="@AppRole.ADMINISTRATOR_TYPE">
<RadzenPanelMenuItem Path="/test/export" Text="Экспорт" Icon="arrow_outward" /> <Authorized>
</RadzenPanelMenuItem> <RadzenPanelMenuItem Text="Тестирование" Icon="simulation">
<RadzenPanelMenuItem Path="/test/export" Text="Экспорт" Icon="arrow_outward" />
</RadzenPanelMenuItem>
<RadzenPanelMenuItem Text="Администрирование" Icon="admin_panel_settings">
<RadzenPanelMenuItem Path="/management/users" Text="Пользователи" Icon="groups" />
</RadzenPanelMenuItem>
</Authorized>
</AuthorizeView>
</RadzenPanelMenu> </RadzenPanelMenu>
<RadzenPanelMenu> <RadzenPanelMenu>
<AuthorizeView> <AuthorizeView>
@ -28,7 +35,6 @@
<RadzenPanelMenuItem Path="/identity/logout" Text="Выйти" Icon="logout" /> <RadzenPanelMenuItem Path="/identity/logout" Text="Выйти" Icon="logout" />
</Authorized> </Authorized>
<NotAuthorized> <NotAuthorized>
<RadzenPanelMenuItem Path="/account/register" Text="Регистрация" Icon="person_add" />
<RadzenPanelMenuItem Path="/account/login" Text="Вход" Icon="login" /> <RadzenPanelMenuItem Path="/account/login" Text="Вход" Icon="login" />
</NotAuthorized> </NotAuthorized>
</AuthorizeView> </AuthorizeView>

View File

@ -1,82 +0,0 @@
@page "/account/register"
@inject NotificationService NotificationService
<PageTitle>Регистрация аккаунта</PageTitle>
<RadzenCard class="rz-mx-auto" Style="max-width: 420px">
<RadzenTemplateForm TItem="InputModel" Data=@Input Method="post" Action="@($"identity/register?returnUrl={ReturnUrl}")">
<RadzenStack Gap="1rem" class="rz-p-sm-12">
<RadzenText TextStyle="TextStyle.H5" TextAlign="TextAlign.Center">Регистрация</RadzenText>
<RadzenFormField Text="Логин" Variant="Variant.Outlined">
<ChildContent>
<RadzenTextBox Name="UserName" @bind-Value=@Input.UserName AutoCompleteType="AutoCompleteType.Username" />
</ChildContent>
<Helper>
<RadzenRequiredValidator Component="UserName" Text="Поле 'Логин' обязательно к заполнению" />
<RadzenLengthValidator Component="UserName" Min="5" Text="Длина поля 'Логин' должна быть не меньше 5" />
<RadzenLengthValidator Component="UserName" Max="30" Text="Длина поля 'Логин' должна быть не больше 30" />
</Helper>
</RadzenFormField>
<RadzenFormField Text="Пароль" Variant="Variant.Outlined">
<ChildContent>
<RadzenPassword Name="Password" @bind-Value=@Input.Password AutoCompleteType="AutoCompleteType.NewPassword" />
</ChildContent>
<Helper>
<RadzenRequiredValidator Component="Password" Text="Поле 'Пароль' обязательно к заполнению" />
<RadzenLengthValidator Component="Password" Min="6" Text="Длина поля 'Пароль' должна быть не меньше 6" />
<RadzenLengthValidator Component="Password" Max="100" Text="Длина поля 'Пароль' должна быть не больше 100" />
</Helper>
</RadzenFormField>
<RadzenFormField Text="Повторите пароль" Variant="Variant.Outlined">
<ChildContent>
<RadzenPassword Name="ConfirmPassword" @bind-Value=@Input.ConfirmPassword AutoCompleteType="AutoCompleteType.NewPassword" />
</ChildContent>
<Helper>
<RadzenRequiredValidator Component="ConfirmPassword" Text="Поле 'Повторите пароль' обязательно к заполнению" />
<RadzenCompareValidator Value=@Input.Password Component="ConfirmPassword" Text="Пароли должны совпадать" />
</Helper>
</RadzenFormField>
<RadzenButton ButtonType="ButtonType.Submit" Text="Зарегистрировать"></RadzenButton>
</RadzenStack>
</RadzenTemplateForm>
</RadzenCard>
@code {
sealed class InputModel
{
public string UserName { get; set; } = "";
public string Password { get; set; } = "";
public string ConfirmPassword { get; set; } = "";
}
[SupplyParameterFromForm]
InputModel Input { get; set; } = new();
[SupplyParameterFromQuery]
string? Error { get; set; }
[SupplyParameterFromQuery]
string? ReturnUrl { get; set; }
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
if (firstRender)
{
if (!string.IsNullOrEmpty(Error))
{
NotificationService.Notify(new NotificationMessage()
{
Severity = NotificationSeverity.Error,
Summary = "Ошибка",
Detail = Error,
Duration = -1d
});
}
}
}
}

View File

@ -0,0 +1,132 @@
@page "/management/add-user"
@using Hcs.WebApp.Services
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Identity
@using Microsoft.EntityFrameworkCore
@attribute [Authorize]
@inject RoleManager<AppRole> RoleManager
@inject UsersService UsersService
@inject DialogService DialogService
<PageTitle>Создание пользователя</PageTitle>
<AuthorizedContent Roles="@AppRole.ADMINISTRATOR_TYPE">
<Content>
<RadzenTemplateForm TItem="InputModel" Data=@Input Submit="@DoAddUser">
<RadzenAlert Visible="@hasError" AlertStyle="AlertStyle.Danger" Variant="Variant.Flat" Shade="Shade.Lighter" AllowClose="false">
@errorMessage
</RadzenAlert>
<RadzenStack Gap="1rem" class="rz-p-sm-4">
<RadzenFormField Text="Логин" Variant="Variant.Outlined">
<ChildContent>
<RadzenTextBox Name="UserName" @bind-Value=@Input.UserName AutoCompleteType="AutoCompleteType.Username" />
</ChildContent>
<Helper>
<RadzenRequiredValidator Component="UserName" Text="Поле 'Логин' обязательно к заполнению" />
<RadzenLengthValidator Component="UserName" Min="5" Text="Длина поля 'Логин' должна быть не меньше 5" />
<RadzenLengthValidator Component="UserName" Max="30" Text="Длина поля 'Логин' должна быть не больше 30" />
</Helper>
</RadzenFormField>
<RadzenFormField Text="Роль" Variant="Variant.Outlined">
<ChildContent>
<RadzenDropDown Data="@roles" TextProperty="Name" @bind-Value="@Input.Role" Name="Role" style="width: 100%" />
</ChildContent>
</RadzenFormField>
<RadzenFormField Text="Пароль" Variant="Variant.Outlined">
<ChildContent>
<RadzenPassword Name="Password" @bind-Value=@Input.Password AutoCompleteType="AutoCompleteType.NewPassword" />
</ChildContent>
<Helper>
<RadzenRequiredValidator Component="Password" Text="Поле 'Пароль' обязательно к заполнению" />
<RadzenLengthValidator Component="Password" Min="6" Text="Длина поля 'Пароль' должна быть не меньше 6" />
<RadzenLengthValidator Component="Password" Max="100" Text="Длина поля 'Пароль' должна быть не больше 100" />
</Helper>
</RadzenFormField>
<RadzenFormField Text="Повторите пароль" Variant="Variant.Outlined">
<ChildContent>
<RadzenPassword Name="ConfirmPassword" @bind-Value=@Input.ConfirmPassword AutoCompleteType="AutoCompleteType.NewPassword" />
</ChildContent>
<Helper>
<RadzenRequiredValidator Component="ConfirmPassword" Text="Поле 'Повторите пароль' обязательно к заполнению" />
<RadzenCompareValidator Value=@Input.Password Component="ConfirmPassword" Text="Пароли должны совпадать" />
</Helper>
</RadzenFormField>
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" JustifyContent="JustifyContent.End" Gap="0.5rem">
<RadzenButton ButtonType="ButtonType.Submit" Disabled="@inProgress" Text="Создать"></RadzenButton>
<RadzenButton Click="@DoClose" ButtonStyle="ButtonStyle.Light" Disabled="@inProgress" Text="Отмена"></RadzenButton>
</RadzenStack>
</RadzenStack>
</RadzenTemplateForm>
</Content>
</AuthorizedContent>
@code {
sealed class InputModel
{
public string UserName { get; set; } = "";
public AppRole Role { get; set; }
public string Password { get; set; } = "";
public string ConfirmPassword { get; set; } = "";
}
IEnumerable<AppRole> roles;
bool inProgress;
bool hasError;
string errorMessage;
[SupplyParameterFromForm]
InputModel Input { get; set; } = new();
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
roles = await RoleManager.Roles.OrderBy(x => x.Priority).ToListAsync();
Input.Role = roles.First();
}
async Task DoAddUser(InputModel input)
{
if (inProgress) return;
inProgress = true;
hasError = false;
try
{
var result = await UsersService.CreateUser(input.UserName, input.Role.Name, input.Password);
if (result.Succeeded)
{
DialogService.Close(true);
}
else
{
hasError = true;
errorMessage = string.Join(", ", result.Errors.Select(x => x.Description));
}
}
catch (Exception e)
{
hasError = true;
errorMessage = e.Message;
}
finally
{
inProgress = false;
}
}
void DoClose()
{
if (inProgress) return;
DialogService.Close(false);
}
}

View File

@ -0,0 +1,112 @@
@page "/management/users"
@using Hcs.WebApp.Services
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Identity
@using Microsoft.EntityFrameworkCore
@attribute [Authorize]
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject UsersService UsersService
@inject DialogService DialogService
<PageTitle>Пользователи</PageTitle>
<AuthorizedContent Roles="@AppRole.ADMINISTRATOR_TYPE">
<Content>
<RadzenStack>
<RadzenRow AlignItems="AlignItems.Center">
<RadzenColumn Size="12" SizeMD="6">
<RadzenText Text="Пользователи" TextStyle="TextStyle.H5" class="rz-m-0" />
</RadzenColumn>
<RadzenColumn Size="12" SizeMD="6">
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" JustifyContent="JustifyContent.End" Gap="0.5rem">
<RadzenButton Icon="add_circle_outline" Text="Создать" Click="@AddUser" ButtonStyle="ButtonStyle.Primary" />
</RadzenStack>
</RadzenColumn>
</RadzenRow>
<RadzenRow>
<RadzenColumn SizeMD="12">
<!--<RadzenAlert AlertStyle="AlertStyle.Danger" Variant="Variant.Flat" Shade="Shade.Lighter" Title="" Visible="@errorVisible">@error</RadzenAlert>-->
<RadzenDataGrid TItem="AppUserWithRole" Data="@usersWithRoles" RowSelect="@EditUser" IsLoading="@isLoading" AllowFiltering="true" AllowPaging="true" ShowPagingSummary="true" PageSizeOptions=@(new int[] { 5, 10, 20, 30 }) AllowSorting="true">
<Columns>
<RadzenDataGridColumn TItem="AppUserWithRole" Property="User.UserName" Title="Логин" />
<RadzenDataGridColumn TItem="AppUserWithRole" Property="Role.Name" Title="Роль" />
<RadzenDataGridColumn TItem="AppUserWithRole" Filterable="false" Sortable="false" TextAlign="TextAlign.Center" Width="70px">
<Template Context="userWithRole">
<RadzenButton Click="@(() => DeleteUser(userWithRole))" @onclick:stopPropagation="true" ButtonStyle="ButtonStyle.Danger" Icon="close" Size="ButtonSize.Small" />
</Template>
</RadzenDataGridColumn>
</Columns>
</RadzenDataGrid>
</RadzenColumn>
</RadzenRow>
</RadzenStack>
</Content>
</AuthorizedContent>
@code {
IEnumerable<AppUserWithRole> usersWithRoles;
bool isLoading;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
isLoading = true;
StateHasChanged();
var state = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (state.User.Identity?.IsAuthenticated ?? false)
{
usersWithRoles = await UsersService.GetUsersWithRole();
}
isLoading = false;
StateHasChanged();
}
}
async Task AddUser()
{
var success = await DialogService.OpenAsync<AddUser>(
"Создание пользователя",
null,
new DialogOptions()
{
Width = "420px",
ShowClose = false,
CloseDialogOnEsc = false,
CloseDialogOnOverlayClick = false
});
if (success)
{
await UpdateGrid();
}
}
async Task EditUser(AppUserWithRole userWithRole)
{
// TODO: Implement method
}
async Task DeleteUser(AppUserWithRole userWithRole)
{
// TODO: Implement method
}
async Task UpdateGrid()
{
isLoading = true;
usersWithRoles = await UsersService.GetUsersWithRole();
isLoading = false;
}
}

View File

@ -0,0 +1,49 @@
using Radzen.Blazor;
namespace Hcs.WebApp.Components.Shared
{
public class LocalizedRadzenDataGrid<T> : RadzenDataGrid<T>
{
public LocalizedRadzenDataGrid()
{
FilterText = "Фильтр";
EnumFilterSelectText = "Выбрать...";
EnumNullFilterText = "Нет значения";
AndOperatorText = "И";
OrOperatorText = "ИЛИ";
ApplyFilterText = "Применить";
ClearFilterText = "Очистить";
EqualsText = "Равен";
NotEqualsText = "Не равен";
LessThanText = "Меньше чем";
LessThanOrEqualsText = "Меньше чем или равен";
GreaterThanText = "Больше чем";
GreaterThanOrEqualsText = "Больше чем или равен";
EndsWithText = "Заканчивается на";
ContainsText = "Содержит";
DoesNotContainText = "Не содержит";
InText = "В";
NotInText = "НЕ В";
StartsWithText = "Начинается с";
IsNotNullText = "Не пустой";
IsNullText = "Пустой";
IsEmptyText = "Пустой текст";
IsNotEmptyText = "Не пустой текст";
CustomText = "Пользовательский";
EmptyText = "Нет записей";
PageSizeText = "зап. на стр.";
PagingSummaryFormat = "Страница {0} из {1} ({2} зап.)";
FirstPageTitle = "Первая страница";
FirstPageAriaLabel = "На первую страницу";
PrevPageTitle = "Предыдущая страница";
PrevPageAriaLabel = "На предыдущую страницу";
LastPageTitle = "Последняя страница";
LastPageAriaLabel = "На последнюю страницу";
NextPageTitle = "Следующая страница";
NextPageAriaLabel = "На следующую страницу";
PageTitleFormat = "Страница {0}";
PageAriaLabelFormat = "На страницу {0}";
}
}
}

View File

@ -10,46 +10,12 @@ namespace Hcs.WebApp.Controllers
[Route("identity/")] [Route("identity/")]
[Authorize] [Authorize]
public class IdentityController( public class IdentityController(
IUserStore<AppUser> userStore,
UserManager<AppUser> userManager, UserManager<AppUser> userManager,
SignInManager<AppUser> signInManager) : Controller SignInManager<AppUser> signInManager) : Controller
{ {
private readonly IUserStore<AppUser> userStore = userStore;
private readonly UserManager<AppUser> userManager = userManager; private readonly UserManager<AppUser> userManager = userManager;
private readonly SignInManager<AppUser> signInManager = signInManager; private readonly SignInManager<AppUser> signInManager = signInManager;
[HttpPost]
[Route("register")]
[AllowAnonymous]
public async Task<IActionResult> Register(string userName, string password, string returnUrl)
{
var user = Activator.CreateInstance<AppUser>();
await userStore.SetUserNameAsync(user, userName, CancellationToken.None);
var result = await userManager.CreateAsync(user, password);
if (!result.Succeeded)
{
var error = string.Join(", ", result.Errors.Select(x => x.Description));
if (!string.IsNullOrEmpty(returnUrl))
{
return Redirect($"/account/register?error={error}&returnUrl={Uri.EscapeDataString(returnUrl)}");
}
else
{
return Redirect($"/account/register?error={error}");
}
}
await signInManager.SignInAsync(user, isPersistent: false);
if (string.IsNullOrEmpty(returnUrl))
{
return Redirect("/");
}
return Redirect(returnUrl);
}
[HttpPost] [HttpPost]
[Route("login")] [Route("login")]
[AllowAnonymous] [AllowAnonymous]

View File

@ -18,7 +18,8 @@ namespace Hcs.WebApp.Data
adminRole = new AppRole() adminRole = new AppRole()
{ {
Name = AppRole.ADMINISTRATOR_TYPE, Name = AppRole.ADMINISTRATOR_TYPE,
NormalizedName = AppRole.ADMINISTRATOR_TYPE.Normalize() NormalizedName = AppRole.ADMINISTRATOR_TYPE.Normalize(),
Priority = 0
}; };
context.Set<AppRole>().Add(adminRole); context.Set<AppRole>().Add(adminRole);
} }
@ -29,7 +30,8 @@ namespace Hcs.WebApp.Data
context.Set<AppRole>().Add(new AppRole() context.Set<AppRole>().Add(new AppRole()
{ {
Name = AppRole.OPERATOR_TYPE, Name = AppRole.OPERATOR_TYPE,
NormalizedName = AppRole.OPERATOR_TYPE.Normalize() NormalizedName = AppRole.OPERATOR_TYPE.Normalize(),
Priority = 10
}); });
} }
@ -39,7 +41,8 @@ namespace Hcs.WebApp.Data
context.Set<AppRole>().Add(new AppRole() context.Set<AppRole>().Add(new AppRole()
{ {
Name = AppRole.OBSERVER_TYPE, Name = AppRole.OBSERVER_TYPE,
NormalizedName = AppRole.OBSERVER_TYPE.Normalize() NormalizedName = AppRole.OBSERVER_TYPE.Normalize(),
Priority = 100
}); });
} }

View File

@ -7,5 +7,7 @@ namespace Hcs.WebApp.Data
public const string ADMINISTRATOR_TYPE = "Administrator"; public const string ADMINISTRATOR_TYPE = "Administrator";
public const string OPERATOR_TYPE = "Operator"; public const string OPERATOR_TYPE = "Operator";
public const string OBSERVER_TYPE = "Observer"; public const string OBSERVER_TYPE = "Observer";
public int Priority { get; set; }
} }
} }

View File

@ -0,0 +1,9 @@
namespace Hcs.WebApp.Data
{
public class AppUserWithRole
{
public AppUser User { get; set; }
public AppRole Role { get; set; }
}
}

View File

@ -0,0 +1,277 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Hcs.WebApp.Data.Migrations
{
[DbContext(typeof(AppIdentityDbContext))]
[Migration("20251019083804_AddRolePriority")]
partial class AddRolePriority
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Hcs.WebApp.Data.AppRole", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<int>("Priority")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Hcs.WebApp.Data.AppUser", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("RoleId")
.HasColumnType("nvarchar(450)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("Value")
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Hcs.WebApp.Data.AppRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Hcs.WebApp.Data.AppUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Hcs.WebApp.Data.AppUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Hcs.WebApp.Data.AppRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Hcs.WebApp.Data.AppUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Hcs.WebApp.Data.AppUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Hcs.WebApp.Data.Migrations
{
/// <inheritdoc />
public partial class AddRolePriority : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Priority",
table: "AspNetRoles",
type: "int",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Priority",
table: "AspNetRoles");
}
}
}

View File

@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore; // <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
#nullable disable #nullable disable
@ -11,11 +12,41 @@ namespace Hcs.WebApp.Data.Migrations
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "8.0.0") .HasAnnotation("ProductVersion", "9.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 128); .HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Hcs.WebApp.Data.AppRole", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<int>("Priority")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Hcs.WebApp.Data.AppUser", b => modelBuilder.Entity("Hcs.WebApp.Data.AppUser", b =>
{ {
b.Property<string>("Id") b.Property<string>("Id")
@ -81,33 +112,6 @@ namespace Hcs.WebApp.Data.Migrations
b.ToTable("AspNetUsers", (string)null); b.ToTable("AspNetUsers", (string)null);
}); });
modelBuilder.Entity("Hcs.WebApp.Data.AppRole", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -216,7 +220,7 @@ namespace Hcs.WebApp.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{ {
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) b.HasOne("Hcs.WebApp.Data.AppRole", null)
.WithMany() .WithMany()
.HasForeignKey("RoleId") .HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -243,7 +247,7 @@ namespace Hcs.WebApp.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{ {
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) b.HasOne("Hcs.WebApp.Data.AppRole", null)
.WithMany() .WithMany()
.HasForeignKey("RoleId") .HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)

View File

@ -1,9 +1,12 @@
using Hcs.WebApp.Components; using Hcs.WebApp.Components;
using Hcs.WebApp.Components.Shared;
using Hcs.WebApp.Data; using Hcs.WebApp.Data;
using Hcs.WebApp.Services; using Hcs.WebApp.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Radzen; using Radzen;
using Radzen.Blazor;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@ -19,10 +22,8 @@ builder.Services.AddHeaderPropagation(x => x.Headers.Add("Cookie"));
builder.Services.AddAuthentication(); builder.Services.AddAuthentication();
builder.Services.AddAuthorization(); builder.Services.AddAuthorization();
builder.Services.AddScoped<IdentityService>();
var connectionString = builder.Configuration.GetConnectionString("IdentityConnection") ?? throw new InvalidOperationException("<22><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> 'IdentityConnection'"); var connectionString = builder.Configuration.GetConnectionString("IdentityConnection") ?? throw new InvalidOperationException("<22><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> 'IdentityConnection'");
builder.Services.AddDbContext<AppIdentityDbContext>(options => options.UseSqlServer(connectionString)); builder.Services.AddDbContextFactory<AppIdentityDbContext>(options => options.UseSqlServer(connectionString));
builder.Services builder.Services
.AddIdentity<AppUser, AppRole>(options => .AddIdentity<AppUser, AppRole>(options =>
@ -42,6 +43,12 @@ builder.Services.AddTransient<IClientProvider, MockClientProvider>();
#else #else
builder.Services.AddTransient<IClientProvider, ClientProvider>(); builder.Services.AddTransient<IClientProvider, ClientProvider>();
#endif #endif
builder.Services.AddScoped<IdentityService>();
builder.Services.AddScoped<UsersService>();
var activator = new RadzenComponentActivator();
activator.Override(typeof(RadzenDataGrid<>), typeof(LocalizedRadzenDataGrid<>));
builder.Services.AddSingleton<IComponentActivator>(activator);
var app = builder.Build(); var app = builder.Build();

View File

@ -0,0 +1,40 @@
using Hcs.WebApp.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
namespace Hcs.WebApp.Services
{
public class UsersService(IDbContextFactory<AppIdentityDbContext> factory, UserManager<AppUser> userManager)
{
private readonly IDbContextFactory<AppIdentityDbContext> factory = factory;
private readonly UserManager<AppUser> userManager = userManager;
public async Task<IEnumerable<AppUserWithRole>> GetUsersWithRole()
{
using var context = factory.CreateDbContext();
return await (from user in context.Users
join userRole in context.UserRoles on user.Id equals userRole.UserId
join role in context.Roles on userRole.RoleId equals role.Id
select new AppUserWithRole()
{
User = user,
Role = role
}).ToListAsync();
}
public async Task<IdentityResult> CreateUser(string userName, string roleName, string password)
{
var user = new AppUser()
{
UserName = userName,
NormalizedUserName = userName.Normalize()
};
var result = await userManager.CreateAsync(user, password);
if (result.Succeeded)
{
result = await userManager.AddToRolesAsync(user, [roleName]);
}
return result;
}
}
}