From 32e199848b044a03d4bc1ca9562e8d8e60e4683b Mon Sep 17 00:00:00 2001 From: neozhu Date: Mon, 7 Feb 2022 14:38:08 +0800 Subject: [PATCH 01/23] add roles page --- src/Blazor.Server.UI/Blazor.Server.UI.csproj | 2 +- .../Pages/Identity/Roles/Roles.razor | 168 ++++++++++++++++++ .../Pages/Identity/Users/Users.razor | 5 +- src/Blazor.Server.UI/Shared/MainLayout.razor | 4 +- src/Blazor.Server.UI/nav.json | 4 +- src/Domain/Entities/Logger/Logger.cs | 20 +++ 6 files changed, 194 insertions(+), 9 deletions(-) create mode 100644 src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor create mode 100644 src/Domain/Entities/Logger/Logger.cs diff --git a/src/Blazor.Server.UI/Blazor.Server.UI.csproj b/src/Blazor.Server.UI/Blazor.Server.UI.csproj index 240d86cda..f5fca61aa 100644 --- a/src/Blazor.Server.UI/Blazor.Server.UI.csproj +++ b/src/Blazor.Server.UI/Blazor.Server.UI.csproj @@ -52,7 +52,7 @@ - + diff --git a/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor b/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor new file mode 100644 index 000000000..20bfed48d --- /dev/null +++ b/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor @@ -0,0 +1,168 @@ +@page "/indentity/roles" +@using Microsoft.AspNetCore.Identity + +@attribute [Authorize(Policy = Permissions.Roles.View)] +@inject IStringLocalizer L +@Title + + + + + + +
+ Roles + @L["Refresh"] + @if (_canCreate) + { + @L["Create"] + } + @if (_canDelete) + { + @L["Delete"] + } + @if (_canImport) + { + @L["Import Data"] + } + @if (_canExport) + { + @L["Export Data"] + } +
+ + @if (_canSearch) + { + + + } +
+ + @L["Actions"] + @L["Name"] + @L["Description"] + + + + @if(_canEdit || _canManagePermissions ) { + + @if (_canEdit) + { + @L["Edit"] + } + @if (_canManagePermissions) + { + @L["Set Permissions"] + } + + } else { + + @L["No Allowed"] + + } + + @context.Name + @context.Description + + + + + +
+ + +
+ + Oopsie !! 😔 @context.GetBaseException().Message + +
+ +@code { + + + private IEnumerable RoleList = new List(); + private HashSet SelectItems = new HashSet(); + private string _searchString; + private bool _sortNameByLength; + public string? Title { get; private set; } + [CascadingParameter] + private Task AuthState { get; set; } = default!; + [Inject] + private IAuthorizationService AuthService { get; set; } = default!; + [Inject] + private RoleManager _roleManager { get; set; } = default!; + + private bool _canCreate; + private bool _canSearch; + private bool _canEdit; + private bool _canDelete; + private bool _canManagePermissions; + private bool _canImport; + private bool _canExport; + protected override async Task OnInitializedAsync() + { + Title = L["Roles"]; + var state = await AuthState; + _canCreate = (await AuthService.AuthorizeAsync(state.User, Permissions.Roles.Create)).Succeeded; + _canSearch = (await AuthService.AuthorizeAsync(state.User, Permissions.Roles.Search)).Succeeded; + _canEdit = (await AuthService.AuthorizeAsync(state.User, Permissions.Roles.Edit)).Succeeded; + _canDelete = (await AuthService.AuthorizeAsync(state.User, Permissions.Roles.Delete)).Succeeded; + _canManagePermissions = (await AuthService.AuthorizeAsync(state.User, Permissions.Roles.ManagePermissions)).Succeeded; + _canImport = (await AuthService.AuthorizeAsync(state.User, Permissions.Users.Import)).Succeeded; + _canExport = (await AuthService.AuthorizeAsync(state.User, Permissions.Users.Export)).Succeeded; + RoleList = await _roleManager.Roles.ToListAsync(); + + } + private Func _quickFilter => x => + { + if (string.IsNullOrWhiteSpace(_searchString)) + return true; + + if (x.Name.Contains(_searchString, StringComparison.OrdinalIgnoreCase)) + return true; + + if (x.Description.Contains(_searchString, StringComparison.OrdinalIgnoreCase)) + return true; + + return false; + }; + +} diff --git a/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor b/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor index 19648f417..f638a04cb 100644 --- a/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor +++ b/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor @@ -196,8 +196,5 @@ return false; }; - private void click() - { - throw new Exception("test"); - } + } diff --git a/src/Blazor.Server.UI/Shared/MainLayout.razor b/src/Blazor.Server.UI/Shared/MainLayout.razor index 2571ac14a..7afb24572 100644 --- a/src/Blazor.Server.UI/Shared/MainLayout.razor +++ b/src/Blazor.Server.UI/Shared/MainLayout.razor @@ -1,5 +1,5 @@ @inherits LayoutComponentBase; - +@inject IStringLocalizer L @@ -14,7 +14,7 @@ @Body - No authentication is required, click sign in. + @L["authentication is required, click sign in."] diff --git a/src/Blazor.Server.UI/nav.json b/src/Blazor.Server.UI/nav.json index 078af7fdb..42c3fe267 100644 --- a/src/Blazor.Server.UI/nav.json +++ b/src/Blazor.Server.UI/nav.json @@ -62,8 +62,8 @@ }, { "title": "Roles", - "href": "/authentication/roles", - "pageStatus": "comingSoon" + "href": "/indentity/roles", + "pageStatus": "completed" } ] }, diff --git a/src/Domain/Entities/Logger/Logger.cs b/src/Domain/Entities/Logger/Logger.cs new file mode 100644 index 000000000..4c3fec95e --- /dev/null +++ b/src/Domain/Entities/Logger/Logger.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace CleanArchitecture.Blazor.Domain.Entities.Log; + +public class Logger : IEntity +{ + public int Id { get; set; } + public string Message { get; set; } + public string MessageTemplate { get; set; } + public string Level { get; set; } + public DateTime TimeStamp { get; set; } + public string Exception { get; set; } + public string UserName { get; set; } + public string ClientIP { get; set; } + public string ClientAgent { get; set; } + public string Properties { get; set; } + public string LogEvent { get; set; } + +} From ed2eaa20a8776e27af46f1f4164b9dcc62aff59e Mon Sep 17 00:00:00 2001 From: neozhu Date: Mon, 7 Feb 2022 15:33:13 +0800 Subject: [PATCH 02/23] add string localizer --- .../Components/Shared/SideMenu.razor | 8 +++-- .../Pages/Authentication/Login.razor | 33 ++++++++++--------- .../Pages/Authentication/Register.razor | 29 ++++++++-------- .../Pages/Authentication/Reset.razor | 7 ++-- .../Pages/Identity/Roles/Roles.razor | 2 +- .../Pages/Identity/Users/Users.razor | 2 +- 6 files changed, 43 insertions(+), 38 deletions(-) diff --git a/src/Blazor.Server.UI/Components/Shared/SideMenu.razor b/src/Blazor.Server.UI/Components/Shared/SideMenu.razor index 54f0d4db6..158570281 100644 --- a/src/Blazor.Server.UI/Components/Shared/SideMenu.razor +++ b/src/Blazor.Server.UI/Components/Shared/SideMenu.razor @@ -1,5 +1,7 @@ @using MudBlazor.Extensions @using Blazor.Server.UI.Models.SideMenu +@inject IStringLocalizer L + - @(section.Title) + @(L[section.Title]) @@ -75,7 +77,7 @@ Href="@(menuItem.Href)" Match="NavLinkMatch.All">
- @menuItem.Title + @L[menuItem.Title] @if (menuItem.PageStatus != PageStatus.Completed) { @@ -99,7 +101,7 @@ Icon="@(sectionItem.Icon)" Match="NavLinkMatch.All">
- @sectionItem.Title + @L[sectionItem.Title] @if (sectionItem.PageStatus != PageStatus.Completed) { diff --git a/src/Blazor.Server.UI/Pages/Authentication/Login.razor b/src/Blazor.Server.UI/Pages/Authentication/Login.razor index 94952d2d9..def4c2514 100644 --- a/src/Blazor.Server.UI/Pages/Authentication/Login.razor +++ b/src/Blazor.Server.UI/Pages/Authentication/Login.razor @@ -5,35 +5,36 @@ @using Blazor.Server.UI.Models.Authentication @using System.ComponentModel.DataAnnotations @using System.Security.Claims +@inject IStringLocalizer L - Sign In + @L["Sign In"] - Don't have an account? Sign Up + @L["Don't have an account?"] @L["Sign Up"]
- - Forgot pwd? + + @L["Forgot pwd?"]
@@ -44,9 +45,9 @@ FullWidth="true">Sign In
@code { - [Inject] private UserManager userManager { get; set; } - [Inject] private NavigationManager _navigation { get; set; } - [Inject] private IDataProtectionProvider dataProtectionProvider { get; set; } + [Inject] private UserManager userManager { get; set; } = default!; + [Inject] private NavigationManager _navigation { get; set; }= default!; + [Inject] private IDataProtectionProvider dataProtectionProvider { get; set; }= default!; LoginFormModel model = new LoginFormModel() { UserName = "administrator", @@ -62,17 +63,17 @@ { if (string.IsNullOrWhiteSpace(pw)) { - yield return "Password is required!"; + yield return L["Password is required!"]; yield break; } if (pw.Length < 6) - yield return "Password must be at least of length 6"; + yield return L["Password must be at least of length 6"]; if (!Regex.IsMatch(pw, @"[A-Z]")) - yield return "Password must contain at least one capital letter"; + yield return L["Password must contain at least one capital letter"]; if (!Regex.IsMatch(pw, @"[a-z]")) - yield return "Password must contain at least one lowercase letter"; + yield return L["Password must contain at least one lowercase letter"]; if (!Regex.IsMatch(pw, @"[0-9]")) - yield return "Password must contain at least one digit"; + yield return L["Password must contain at least one digit"]; } void TogglePasswordVisibility() diff --git a/src/Blazor.Server.UI/Pages/Authentication/Register.razor b/src/Blazor.Server.UI/Pages/Authentication/Register.razor index 319dc3a6e..b272997d0 100644 --- a/src/Blazor.Server.UI/Pages/Authentication/Register.razor +++ b/src/Blazor.Server.UI/Pages/Authentication/Register.razor @@ -1,11 +1,11 @@ -@page "/pages/authentication/register" +@page "/pages/authentication/register" @using System.Text.RegularExpressions @using Microsoft.AspNetCore.Identity @using Blazor.Server.UI.Models.Authentication @using System.ComponentModel.DataAnnotations @using System.Security.Claims @using FluentValidation; - +@inject IStringLocalizer L @attribute [AllowAnonymous] @@ -15,27 +15,28 @@ - + Register + FullWidth="true">@L["Register"] @code { - [Inject] private NavigationManager _navigation { get; set; } - [Inject] ISnackbar Snackbar { get; set; } - [Inject] private UserManager userManager { get; set; } + [Inject] private NavigationManager _navigation { get; set; } = default!; + [Inject] ISnackbar Snackbar { get; set; }= default!; + [Inject] private UserManager userManager { get; set; }= default!; MudForm form; RegisterFormModel model = new (); RegisterFormModelFluentValidator registerValidator = new (); diff --git a/src/Blazor.Server.UI/Pages/Authentication/Reset.razor b/src/Blazor.Server.UI/Pages/Authentication/Reset.razor index 0d130fb21..40ac52e61 100644 --- a/src/Blazor.Server.UI/Pages/Authentication/Reset.razor +++ b/src/Blazor.Server.UI/Pages/Authentication/Reset.razor @@ -1,13 +1,14 @@ -@page "/pages/authentication/reset-password" +@page "/pages/authentication/reset-password" +@inject IStringLocalizer L @attribute [AllowAnonymous] - Set new password + @L["Set new password"] - Set New Password + @L["Set New Password"] @code { string Password { get; set; } diff --git a/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor b/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor index 20bfed48d..a5427157b 100644 --- a/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor +++ b/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor @@ -19,7 +19,7 @@ Hover="true" MultiSelection="true" SelectedItems="@SelectItems" - SortLabel="Sort By" + SortLabel="@L["Sort By"]" Filter="new Func(_quickFilter)">
diff --git a/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor b/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor index f638a04cb..9c7bb79d2 100644 --- a/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor +++ b/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor @@ -19,7 +19,7 @@ Hover="true" MultiSelection="true" SelectedItems="@SelectItems" - SortLabel="Sort By" + SortLabel="@L["Sort By"]" Filter="new Func(_quickFilter)">
From 2d71a4ffc85e4e785ac396f1b68b6ba4a5f9fa4d Mon Sep 17 00:00:00 2001 From: neozhu Date: Mon, 7 Feb 2022 17:37:48 +0800 Subject: [PATCH 03/23] wip: create user form --- .../Components/Shared/NotificationMenu.razor | 10 +-- .../Components/Shared/SideMenu.razor | 10 +-- .../Components/Shared/UserMenu.razor | 9 +- .../Pages/Authentication/Forgot.razor | 9 +- .../Pages/Authentication/Register.razor | 1 - .../Pages/Identity/Roles/Roles.razor | 2 +- .../Pages/Identity/Users/UserFormModel.cs | 14 +++ .../Identity/Users/UserFormModelValidator.cs | 37 ++++++++ .../Pages/Identity/Users/Users.razor | 13 ++- .../Pages/Identity/Users/_UserForm.razor | 90 +++++++++++++++++++ .../Identity/Users/_UserFormDialog.razor | 17 ++++ src/Blazor.Server.UI/Shared/MainLayout.razor | 2 +- src/Blazor.Server.UI/_Imports.razor | 6 +- 13 files changed, 194 insertions(+), 26 deletions(-) create mode 100644 src/Blazor.Server.UI/Pages/Identity/Users/UserFormModel.cs create mode 100644 src/Blazor.Server.UI/Pages/Identity/Users/UserFormModelValidator.cs create mode 100644 src/Blazor.Server.UI/Pages/Identity/Users/_UserForm.razor create mode 100644 src/Blazor.Server.UI/Pages/Identity/Users/_UserFormDialog.razor diff --git a/src/Blazor.Server.UI/Components/Shared/NotificationMenu.razor b/src/Blazor.Server.UI/Components/Shared/NotificationMenu.razor index 4db7a93df..8565d7a34 100644 --- a/src/Blazor.Server.UI/Components/Shared/NotificationMenu.razor +++ b/src/Blazor.Server.UI/Components/Shared/NotificationMenu.razor @@ -1,5 +1,5 @@ @inherits MudComponentBase - +@inject IStringLocalizer L @@ -27,7 +27,7 @@ style="min-width: 330px"> - Notifications + @(L["Notifications"]) @if (Notifications != null && Notifications.Any()) @@ -60,7 +60,7 @@ { - You have no unread notifications. + @(L["You have no unread notifications."]) } @@ -70,7 +70,7 @@ OnClick="OnClickViewAll" Style="text-transform:none" Variant="Variant.Text"> - View All + @(L["View All"])
@@ -85,4 +85,4 @@ margin-bottom: -8px; } - \ No newline at end of file + diff --git a/src/Blazor.Server.UI/Components/Shared/SideMenu.razor b/src/Blazor.Server.UI/Components/Shared/SideMenu.razor index 158570281..654a2330f 100644 --- a/src/Blazor.Server.UI/Components/Shared/SideMenu.razor +++ b/src/Blazor.Server.UI/Components/Shared/SideMenu.razor @@ -15,7 +15,7 @@ Icon="@Icons.Custom.Brands.MudBlazor" Size="Size.Large" /> - Application Name + @L["Application Name"] @@ -77,7 +77,7 @@ Href="@(menuItem.Href)" Match="NavLinkMatch.All">
- @L[menuItem.Title] + @(L[menuItem.Title]) @if (menuItem.PageStatus != PageStatus.Completed) { @@ -85,7 +85,7 @@ Color="@Color.Primary" Size="Size.Small" Variant="Variant.Text"> - @menuItem.PageStatus.ToDescriptionString() + @(L[menuItem.PageStatus.ToDescriptionString()]) }
@@ -101,7 +101,7 @@ Icon="@(sectionItem.Icon)" Match="NavLinkMatch.All">
- @L[sectionItem.Title] + @(L[sectionItem.Title]) @if (sectionItem.PageStatus != PageStatus.Completed) { @@ -109,7 +109,7 @@ Color="@Color.Primary" Size="Size.Small" Variant="Variant.Text"> - @sectionItem.PageStatus.ToDescriptionString() + @(L[sectionItem.PageStatus.ToDescriptionString()]) }
diff --git a/src/Blazor.Server.UI/Components/Shared/UserMenu.razor b/src/Blazor.Server.UI/Components/Shared/UserMenu.razor index 124c13a91..4680bf908 100644 --- a/src/Blazor.Server.UI/Components/Shared/UserMenu.razor +++ b/src/Blazor.Server.UI/Components/Shared/UserMenu.razor @@ -1,3 +1,4 @@ +@inject IStringLocalizer L - Profile + @L["Profile"]
- Settings + @L["Settings"]
@@ -38,9 +39,9 @@ StartIcon="@Icons.Material.Filled.Logout" Link="/account/signout" Variant="Variant.Outlined"> - Logout + @L["Logout"]
- \ No newline at end of file + diff --git a/src/Blazor.Server.UI/Pages/Authentication/Forgot.razor b/src/Blazor.Server.UI/Pages/Authentication/Forgot.razor index 830cddb5a..469908790 100644 --- a/src/Blazor.Server.UI/Pages/Authentication/Forgot.razor +++ b/src/Blazor.Server.UI/Pages/Authentication/Forgot.razor @@ -1,15 +1,16 @@ -@page "/pages/authentication/forgot-password" +@page "/pages/authentication/forgot-password" +@inject IStringLocalizer L @attribute [AllowAnonymous] -Forgot Password? +@L["Forgot Password?"] -Enter the email address linked to your account and you will recieve an email containing a link to reset your password. +@L["Enter the email address linked to your account and you will recieve an email containing a link to reset your password."] -Reset Password +@L["Reset Password"] @code { diff --git a/src/Blazor.Server.UI/Pages/Authentication/Register.razor b/src/Blazor.Server.UI/Pages/Authentication/Register.razor index b272997d0..c6f5c1b93 100644 --- a/src/Blazor.Server.UI/Pages/Authentication/Register.razor +++ b/src/Blazor.Server.UI/Pages/Authentication/Register.razor @@ -71,7 +71,6 @@ @code { [Inject] private NavigationManager _navigation { get; set; } = default!; - [Inject] ISnackbar Snackbar { get; set; }= default!; [Inject] private UserManager userManager { get; set; }= default!; MudForm form; RegisterFormModel model = new (); diff --git a/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor b/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor index a5427157b..08e97708b 100644 --- a/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor +++ b/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor @@ -111,7 +111,7 @@ - Oopsie !! 😔 @context.GetBaseException().Message + Oopsie !! 😔 @context.GetBaseException().Message diff --git a/src/Blazor.Server.UI/Pages/Identity/Users/UserFormModel.cs b/src/Blazor.Server.UI/Pages/Identity/Users/UserFormModel.cs new file mode 100644 index 000000000..e82a3e9fe --- /dev/null +++ b/src/Blazor.Server.UI/Pages/Identity/Users/UserFormModel.cs @@ -0,0 +1,14 @@ +namespace Blazor.Server.UI.Pages.Identity.Users; + +public class UserFormModel +{ + public string? Id { get; set; } + public string? UserName { get; set; } + public string? DisplayName { get; set; } + public string? Site { get; set; } + public string? ProfilePictureDataUrl { get; set; } + public string? Email { get; set; } + public string? Password { get; set; } + public string? ConfirmPassword { get; set; } + public string[]? AssignRoles { get; set; } +} diff --git a/src/Blazor.Server.UI/Pages/Identity/Users/UserFormModelValidator.cs b/src/Blazor.Server.UI/Pages/Identity/Users/UserFormModelValidator.cs new file mode 100644 index 000000000..be81dd50e --- /dev/null +++ b/src/Blazor.Server.UI/Pages/Identity/Users/UserFormModelValidator.cs @@ -0,0 +1,37 @@ +using FluentValidation; +namespace Blazor.Server.UI.Pages.Identity.Users; + +public class UserFormModelValidator: AbstractValidator +{ + public UserFormModelValidator() + { + RuleFor(v => v.Site) + .MaximumLength(256) + .NotEmpty(); + RuleFor(v => v.UserName) + .MaximumLength(256) + .NotEmpty(); + RuleFor(v => v.Email) + .MaximumLength(256) + .NotEmpty() + .EmailAddress(); + + RuleFor(p => p.Password).NotEmpty().WithMessage("Your password cannot be empty") + .MinimumLength(6).WithMessage("Your password length must be at least 6.") + .MaximumLength(16).WithMessage("Your password length must not exceed 16.") + .Matches(@"[A-Z]+").WithMessage("Your password must contain at least one uppercase letter.") + .Matches(@"[a-z]+").WithMessage("Your password must contain at least one lowercase letter.") + .Matches(@"[0-9]+").WithMessage("Your password must contain at least one number.") + .Matches(@"[\!\?\*\.]+").WithMessage("Your password must contain at least one (!? *.)."); + RuleFor(x => x.ConfirmPassword) + .Equal(x => x.Password); + } + + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((UserFormModel)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} diff --git a/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor b/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor index 9c7bb79d2..fc65eadc0 100644 --- a/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor +++ b/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor @@ -1,8 +1,8 @@ @page "/indentity/users" @using Microsoft.AspNetCore.Identity - -@attribute [Authorize(Policy = Permissions.Users.View)] @inject IStringLocalizer L +@attribute [Authorize(Policy = Permissions.Users.View)] + @Title + + + + + @if (_canSearch && (Context.AdvancedSearchEnabled || AdvancedSearchContent is not null)) + { + + + + @if (Context.AdvancedSearchEnabled) + { +
+ + @foreach (var field in Context.Fields) + { + + } +
+ } + @AdvancedSearchContent + +
+ } + + + +
+ @if (_canCreate) + { + @L["Create"] + } + @L["Reload"] +
+ + @if (_canSearch && !_advancedSearchExpanded) + { + + + } +
+ + + @if (Context.Fields is not null) + { + foreach (var field in Context.Fields) + { + + @if (Context.IsClientContext) + { + @field.DisplayName + } + else + { + @field.DisplayName + } + + } + } + @L["Actions"] + + + + @foreach (var field in Context.Fields) + { + + @if (field.Template is not null) + { + @field.Template(context) + } + else if (field.Type == typeof(bool)) + { + + } + else + { + + } + + } + + @if (ActionsContent is not null) + { + @ActionsContent(context) + } + else if (HasActions) + { + + @if (CanUpdateEntity(context)) + { + @L["Edit"] + } + @if (CanDeleteEntity(context)) + { + @L["Delete"] + } + @if (ExtraActions is not null) + { + @ExtraActions(context) + } + + } + else + { + + @L["No Allowed Actions"] + + } + + + + + + + +
+ +
+ + + +
diff --git a/src/Blazor.Server.UI/Components/EntityTable/EntityTable.razor.cs b/src/Blazor.Server.UI/Components/EntityTable/EntityTable.razor.cs new file mode 100644 index 000000000..4e7b0040b --- /dev/null +++ b/src/Blazor.Server.UI/Components/EntityTable/EntityTable.razor.cs @@ -0,0 +1,228 @@ + +using Blazor.Server.UI.Components.Dialogs; +using Blazor.Server.UI.Shared; +using CleanArchitecture.Blazor.Application.Common.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Authorization; +using MudBlazor; + +namespace Blazor.Server.UI.Components.EntityTable; + +public partial class EntityTable + where TRequest : new() +{ + [Parameter] + [EditorRequired] + public EntityTableContext Context { get; set; } = default!; + + [Parameter] + public bool Loading { get; set; } + + [Parameter] + public string? SearchString { get; set; } + [Parameter] + public EventCallback SearchStringChanged { get; set; } + + [Parameter] + public RenderFragment? AdvancedSearchContent { get; set; } + + [Parameter] + public RenderFragment? ActionsContent { get; set; } + [Parameter] + public RenderFragment? ExtraActions { get; set; } + [Parameter] + public RenderFragment? ChildRowContent { get; set; } + + [Parameter] + public RenderFragment? EditFormContent { get; set; } + + [CascadingParameter] + protected Task AuthState { get; set; } = default!; + [Inject] + protected IAuthorizationService AuthService { get; set; } = default!; + + private bool _canSearch; + private bool _canCreate; + private bool _canUpdate; + private bool _canDelete; + + private bool _advancedSearchExpanded; + + private MudTable _table = default!; + private IEnumerable? _entityList; + private int _totalItems; + + protected override async Task OnInitializedAsync() + { + var state = await AuthState; + _canSearch = await CanDoActionAsync(Context.SearchAction, state); + _canCreate = await CanDoActionAsync(Context.CreateAction, state); + _canUpdate = await CanDoActionAsync(Context.UpdateAction, state); + _canDelete = await CanDoActionAsync(Context.DeleteAction, state); + + await LocalLoadDataAsync(); + } + + public Task ReloadDataAsync() => + Context.IsClientContext + ? LocalLoadDataAsync() + : ServerLoadDataAsync(); + + private async Task CanDoActionAsync(string? action, AuthenticationState state) => + !string.IsNullOrWhiteSpace(action) && + ((bool.TryParse(action, out bool isTrue) && isTrue) || // check if action equals "True", then it's allowed + (Context.EntityResource is { } resource && (await AuthService.AuthorizeAsync(state.User, action)).Succeeded)); + + private bool HasActions => _canUpdate || _canDelete || Context.HasExtraActionsFunc is null || Context.HasExtraActionsFunc(); + private bool CanUpdateEntity(TEntity entity) => _canUpdate && (Context.CanUpdateEntityFunc is null || Context.CanUpdateEntityFunc(entity)); + private bool CanDeleteEntity(TEntity entity) => _canDelete && (Context.CanDeleteEntityFunc is null || Context.CanDeleteEntityFunc(entity)); + + // Client side paging/filtering + private bool LocalSearch(TEntity entity) => + Context.ClientContext?.SearchFunc is { } searchFunc + ? searchFunc(SearchString, entity) + : string.IsNullOrWhiteSpace(SearchString); + + private async Task LocalLoadDataAsync() + { + if (Loading || Context.ClientContext is null) + { + return; + } + + Loading = true; + _entityList = await Context.ClientContext.LoadDataFunc(); + Loading = false; + } + + // Server Side paging/filtering + + private async Task OnSearchStringChanged(string? text = null) + { + await SearchStringChanged.InvokeAsync(SearchString); + + await ServerLoadDataAsync(); + } + + private async Task ServerLoadDataAsync() + { + if (Context.IsServerContext) + { + await _table.ReloadServerData(); + } + } + + private Func>>? ServerReloadFunc => + Context.IsServerContext ? ServerReload : null; + + private async Task> ServerReload(TableState state) + { + if (!Loading && Context.ServerContext is not null) + { + Loading = true; + var filter = GetPaginationFilter(state); + await Context.ServerContext.SearchFunc(filter); + Loading = false; + } + + return new TableData { TotalItems = _totalItems, Items = _entityList }; + } + + private PaginationFilter GetPaginationFilter(TableState state) + { + + + var filter = new PaginationFilter + { + PageSize = state.PageSize, + PageNumber = state.Page + 1, + Keyword = SearchString, + OrderBy = state.SortLabel, + SortDirection = state.SortDirection.ToString() + }; + + if (!Context.AllColumnsChecked) + { + filter.AdvancedSearch = new() + { + Fields = Context.SearchFields, + Keyword = filter.Keyword + }; + filter.Keyword = null; + } + + return filter; + } + + private async Task InvokeModal(TEntity? entity = default) + { + bool isCreate = entity is null; + + var parameters = new DialogParameters() + { + { nameof(AddEditModal.EditFormContent), EditFormContent }, + { nameof(AddEditModal.OnInitializedFunc), Context.EditFormInitializedFunc }, + { nameof(AddEditModal.EntityName), Context.EntityName } + }; + + TRequest requestModel; + + if (isCreate) + { + _ = Context.CreateFunc ?? throw new InvalidOperationException("CreateFunc can't be null!"); + parameters.Add(nameof(AddEditModal.SaveFunc), Context.CreateFunc); + + requestModel = (await Context.GetDefaultsFunc())??new TRequest(); + + } + else + { + _ = Context.IdFunc ?? throw new InvalidOperationException("IdFunc can't be null!"); + var id = Context.IdFunc(entity!); + parameters.Add(nameof(AddEditModal.Id), id); + + _ = Context.UpdateFunc ?? throw new InvalidOperationException("UpdateFunc can't be null!"); + Func saveFunc = entity => Context.UpdateFunc(id, entity); + parameters.Add(nameof(AddEditModal.SaveFunc), saveFunc); + + requestModel =(await Context.GetDetailsFunc(id)) ?? new TRequest(); + } + + parameters.Add(nameof(AddEditModal.RequestModel), requestModel); + + var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Medium, FullWidth = true, DisableBackdropClick = true }; + + var dialog = DialogService.Show>(string.Empty, parameters, options); + + Context.SetAddEditModalRef(dialog); + + var result = await dialog.Result; + + if (!result.Cancelled) + { + await ReloadDataAsync(); + } + } + + private async Task Delete(TEntity entity) + { + _ = Context.IdFunc ?? throw new InvalidOperationException("IdFunc can't be null!"); + TId id = Context.IdFunc(entity); + + string deleteContent = L["You're sure you want to delete {0} with id '{1}'?"]; + var parameters = new DialogParameters + { + { nameof(DeleteConfirmation.ContentText), string.Format(deleteContent, Context.EntityName, id) } + }; + var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Small, FullWidth = true, DisableBackdropClick = true }; + var dialog = DialogService.Show(L["Delete"], parameters, options); + var result = await dialog.Result; + if (!result.Cancelled) + { + _ = Context.DeleteFunc ?? throw new InvalidOperationException("DeleteFunc can't be null!"); + await Context.DeleteFunc(id); + await ReloadDataAsync(); + } + } +} \ No newline at end of file diff --git a/src/Blazor.Server.UI/Components/EntityTable/EntityTableContext.cs b/src/Blazor.Server.UI/Components/EntityTable/EntityTableContext.cs new file mode 100644 index 000000000..78a258037 --- /dev/null +++ b/src/Blazor.Server.UI/Components/EntityTable/EntityTableContext.cs @@ -0,0 +1,189 @@ +using MudBlazor; + +namespace Blazor.Server.UI.Components.EntityTable; + +/// +/// Abstract base class for the initialization Context of the EntityTable Component. +/// +/// The type of the entity. +/// The type of the id of the entity. +/// The type of the Request which is used on the AddEditModal and which is sent with the CreateFunc and UpdateFunc. +public abstract class EntityTableContext +{ + /// + /// The columns you want to display on the table. + /// + public List> Fields { get; } + + /// + /// A function that returns the Id of the entity. This is only needed when using the CRUD functionality. + /// + public Func? IdFunc { get; } + + /// + /// A function that executes the GetDefaults method on the api (or supplies defaults locally) and returns + /// a Task of Result of TRequest. When not supplied, a TRequest is simply newed up. + /// No need to check for error messages or api exceptions. These are automatically handled by the component. + /// + public Func>? GetDefaultsFunc { get; } + + /// + /// A function that executes the Create method on the api with the supplied entity and returns a Task of Result. + /// No need to check for error messages or api exceptions. These are automatically handled by the component. + /// + public Func? CreateFunc { get; } + + /// + /// A function that executes the GetDetails method on the api with the supplied Id and returns a Task of Result of TRequest. + /// No need to check for error messages or api exceptions. These are automatically handled by the component. + /// When not supplied, the TEntity out of the _entityList is supplied using the IdFunc and converted using mapster. + /// + public Func>? GetDetailsFunc { get; } + + /// + /// A function that executes the Update method on the api with the supplied entity and returns a Task of Result. + /// When not supplied, the TEntity from the list is mapped to TCreateRequest using mapster. + /// No need to check for error messages or api exceptions. These are automatically handled by the component. + /// + public Func? UpdateFunc { get; } + + /// + /// A function that executes the Delete method on the api with the supplied entity id and returns a Task of Result. + /// No need to check for error messages or api exceptions. These are automatically handled by the component. + /// + public Func? DeleteFunc { get; } + + /// + /// The name of the entity. This is used in the title of the add/edit modal and delete confirmation. + /// + public string? EntityName { get; } + + /// + /// The plural name of the entity. This is used in the "Search for ..." placeholder. + /// + public string? EntityNamePlural { get; } + + /// + /// The FSHResource that is representing this entity. This is used in combination with the xxActions to check for permissions. + /// + public string? EntityResource { get; } + + /// + /// The FSHAction name of the search permission. This is FSHAction.Search by default. + /// When empty, no search functionality will be available. + /// When the string is "true", search funtionality will be enabled, + /// otherwise it will only be enabled if the user has permission for this action on the EntityResource. + /// + public string SearchAction { get; } + + /// + /// The permission name of the create permission. This is FSHAction.Create by default. + /// When empty, no create functionality will be available. + /// When the string "true", create funtionality will be enabled, + /// otherwise it will only be enabled if the user has permission for this action on the EntityResource. + /// + public string CreateAction { get; } + + /// + /// The permission name of the update permission. This is FSHAction.Update by default. + /// When empty, no update functionality will be available. + /// When the string is "true", update funtionality will be enabled, + /// otherwise it will only be enabled if the user has permission for this action on the EntityResource. + /// + public string UpdateAction { get; } + + /// + /// The permission name of the delete permission. This is FSHAction.Delete by default. + /// When empty, no delete functionality will be available. + /// When the string is "true", delete funtionality will be enabled, + /// otherwise it will only be enabled if the user has permission for this action on the EntityResource. + /// + public string DeleteAction { get; } + + /// + /// Use this if you want to run initialization during OnInitialized of the AddEdit form. + /// + public Func? EditFormInitializedFunc { get; } + + /// + /// Use this if you want to check for permissions of content in the ExtraActions RenderFragment. + /// The extra actions won't be available when this returns false. + /// + public Func? HasExtraActionsFunc { get; set; } + + /// + /// Use this if you want to disable the update functionality for specific entities in the table. + /// + public Func? CanUpdateEntityFunc { get; set; } + + /// + /// Use this if you want to disable the delete functionality for specific entities in the table. + /// + public Func? CanDeleteEntityFunc { get; set; } + + public EntityTableContext( + List> fields, + Func? idFunc, + Func>? getDefaultsFunc, + Func? createFunc, + Func>? getDetailsFunc, + Func? updateFunc, + Func? deleteFunc, + string? entityName, + string? entityNamePlural, + string? entityResource, + string? searchAction, + string? createAction, + string? updateAction, + string? deleteAction, + Func? editFormInitializedFunc, + Func? hasExtraActionsFunc, + Func? canUpdateEntityFunc, + Func? canDeleteEntityFunc) + { + EntityResource = entityResource; + Fields = fields; + EntityName = entityName; + EntityNamePlural = entityNamePlural; + IdFunc = idFunc; + GetDefaultsFunc = getDefaultsFunc; + CreateFunc = createFunc; + GetDetailsFunc = getDetailsFunc; + UpdateFunc = updateFunc; + DeleteFunc = deleteFunc; + SearchAction = searchAction ?? "Search"; + CreateAction = createAction ?? "Create"; + UpdateAction = updateAction ?? "Update"; + DeleteAction = deleteAction ?? "Delete"; + EditFormInitializedFunc = editFormInitializedFunc; + HasExtraActionsFunc = hasExtraActionsFunc; + CanUpdateEntityFunc = canUpdateEntityFunc; + CanDeleteEntityFunc = canDeleteEntityFunc; + } + + // AddEdit modal + private IDialogReference? _addEditModalRef; + + internal void SetAddEditModalRef(IDialogReference dialog) => + _addEditModalRef = dialog; + + public IAddEditModal AddEditModal => + _addEditModalRef?.Dialog as IAddEditModal + ?? throw new InvalidOperationException("AddEditModal is only available when the modal is shown."); + + // Shortcuts + public EntityClientTableContext? ClientContext => this as EntityClientTableContext; + public EntityServerTableContext? ServerContext => this as EntityServerTableContext; + public bool IsClientContext => ClientContext is not null; + public bool IsServerContext => ServerContext is not null; + + // Advanced Search + public bool AllColumnsChecked => + Fields.All(f => f.CheckedForSearch); + public void AllColumnsCheckChanged(bool checkAll) => + Fields.ForEach(f => f.CheckedForSearch = checkAll); + public bool AdvancedSearchEnabled => + ServerContext?.EnableAdvancedSearch is true; + public List SearchFields => + Fields.Where(f => f.CheckedForSearch).Select(f => f.SortLabel).ToList(); +} \ No newline at end of file diff --git a/src/Blazor.Server.UI/Components/EntityTable/IAddEditModal.cs b/src/Blazor.Server.UI/Components/EntityTable/IAddEditModal.cs new file mode 100644 index 000000000..7dbdd74b5 --- /dev/null +++ b/src/Blazor.Server.UI/Components/EntityTable/IAddEditModal.cs @@ -0,0 +1,9 @@ +namespace Blazor.Server.UI.Components.EntityTable; + +public interface IAddEditModal +{ + TRequest RequestModel { get; } + bool IsCreate { get; } + void ForceRender(); + bool Validate(object request); +} \ No newline at end of file diff --git a/src/Blazor.Server.UI/Components/EntityTable/PaginationResponse.cs b/src/Blazor.Server.UI/Components/EntityTable/PaginationResponse.cs new file mode 100644 index 000000000..ce9befdb0 --- /dev/null +++ b/src/Blazor.Server.UI/Components/EntityTable/PaginationResponse.cs @@ -0,0 +1,9 @@ +namespace Blazor.Server.UI.Components.EntityTable; + +public class PaginationResponse +{ + public List Data { get; set; } = default!; + public int TotalCount { get; set; } + public int CurrentPage { get; set; } = 1; + public int PageSize { get; set; } = 10; +} diff --git a/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor b/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor index d5751fab4..ef0675c4e 100644 --- a/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor +++ b/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor @@ -9,7 +9,7 @@ @inject IStringLocalizer L @Title diff --git a/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor b/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor index c26eda37d..5f24b4e92 100644 --- a/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor +++ b/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor @@ -5,7 +5,7 @@ @Title @@ -15,7 +15,7 @@ L + + + Products + + + + + @L["Product Name"] + @L["Price"] + @L["Unit"] + + + + + @context.Name + @context.Description + + @context.Price + @context.Unit + + + No matching records found + + + Loading... + + + + + + +@code { + private IEnumerable _pagedData; + private MudTable _table; + private int _totalItems; + private string _searchString = string.Empty; + private bool _loading; + [Inject] + private ISender _mediator { get; set; } = default!; + + private async Task> ServerReload(TableState state) + { + _loading = true; + var request = new ProductsWithPaginationQuery() + { + Keyword = _searchString, + OrderBy = string.IsNullOrEmpty(state.SortLabel)?"Id":state.SortLabel, + SortDirection = (state.SortDirection== SortDirection.None?SortDirection.Descending.ToString():state.SortDirection.ToString()), + PageNumber = state.Page + 1, + PageSize = state.PageSize + }; + var result = await _mediator.Send(request); + _loading = false; + return new TableData() { TotalItems = result.TotalItems, Items = result.Items }; + + } + private async Task OnSearch(string text) + { + _searchString = text; + await _table.ReloadServerData(); + } +} diff --git a/src/Blazor.Server.UI/Shared/SharedResource.cs b/src/Blazor.Server.UI/Shared/SharedResource.cs new file mode 100644 index 000000000..fea0353b3 --- /dev/null +++ b/src/Blazor.Server.UI/Shared/SharedResource.cs @@ -0,0 +1,5 @@ +namespace Blazor.Server.UI.Shared; + +public class SharedResource +{ +} diff --git a/src/Blazor.Server.UI/_Imports.razor b/src/Blazor.Server.UI/_Imports.razor index 4158a2197..3fc89676a 100644 --- a/src/Blazor.Server.UI/_Imports.razor +++ b/src/Blazor.Server.UI/_Imports.razor @@ -8,11 +8,13 @@ @using Microsoft.AspNetCore.Components.Authorization @using Microsoft.Extensions.Localization @using Microsoft.EntityFrameworkCore +@using Microsoft.AspNetCore.Identity @using Blazored.LocalStorage @using Microsoft.JSInterop @using Blazor.Server.UI @using Blazor.Server.UI.Shared @using MudBlazor +@using MediatR @using Blazor.Server.UI.Components.Shared @using Blazor.Server.UI.Components.Shared.Themes @using Blazor.Server.UI.Components.Index diff --git a/src/Blazor.Server.UI/nav.json b/src/Blazor.Server.UI/nav.json index 63813ec07..4b1851b52 100644 --- a/src/Blazor.Server.UI/nav.json +++ b/src/Blazor.Server.UI/nav.json @@ -17,7 +17,7 @@ "isParent": true, "menuItems": [ { - "title": "Product", + "title": "Products", "href": "/pages/products", "pageStatus": "completed" } diff --git a/src/Domain/Entities/Product.cs b/src/Domain/Entities/Product.cs index 06a786e02..4a072cb36 100644 --- a/src/Domain/Entities/Product.cs +++ b/src/Domain/Entities/Product.cs @@ -8,4 +8,8 @@ public class Product : AuditableEntity public int Id { get; set; } public string Name { get; set; } public string Description { get; set; } + public string Unit { get; set; } + public decimal Price { get; set; } + public IList Pictures { get; set; } + } diff --git a/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs b/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs index de4ab3e09..89d7d5257 100644 --- a/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs +++ b/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs @@ -76,8 +76,7 @@ public static async Task SeedSampleDataAsync(ApplicationDbContext context) context.DocumentTypes.Add(new Domain.Entities.DocumentType() { Name = "Image", Description = "Image" }); context.DocumentTypes.Add(new Domain.Entities.DocumentType() { Name = "Other", Description = "Other" }); await context.SaveChangesAsync(); - context.Loggers.Add(new Domain.Entities.Log.Logger() { Message = "Initial add document types", Level = "Information", UserName = "System", TimeStamp = System.DateTime.Now }); - await context.SaveChangesAsync(); + } if (!context.KeyValues.Any()) { @@ -90,21 +89,17 @@ public static async Task SeedSampleDataAsync(ApplicationDbContext context) context.KeyValues.Add(new Domain.Entities.KeyValue() { Name = "Region", Value = "CNS", Text = "CNS", Description = "Region of Customer" }); context.KeyValues.Add(new Domain.Entities.KeyValue() { Name = "Region", Value = "Oversea", Text = "Oversea", Description = "Region of Customer" }); await context.SaveChangesAsync(); - context.Loggers.Add(new Domain.Entities.Log.Logger() { Message = "Initial add key values", Level = "Information", UserName = "System", TimeStamp = System.DateTime.Now }); - await context.SaveChangesAsync(); + } if (!context.Customers.Any()) { context.Customers.Add(new Domain.Entities.Customer() { Name = "SmartAdmin", AddressOfEnglish = "https://wrapbootstrap.com/theme/smartadmin-responsive-webapp-WB0573SK0", GroupName = "SmartAdmin", Address = "https://wrapbootstrap.com/theme/smartadmin-responsive-webapp-WB0573SK0", Sales = "GotBootstrap", RegionSalesDirector = "GotBootstrap", Region = "CNC", NameOfEnglish = "SmartAdmin", PartnerType = Domain.Enums.PartnerType.TP, Contact = "GotBootstrap", Email = "drlantern@gotbootstrap.com" }); await context.SaveChangesAsync(); - - context.Loggers.Add(new Domain.Entities.Log.Logger() { Message = "Initial add customer", Level = "Information", UserName = "System", TimeStamp = System.DateTime.Now }); - - context.Loggers.Add(new Domain.Entities.Log.Logger() { Message = "Debug", Level = "Debug", UserName = "System", TimeStamp = System.DateTime.Now.AddHours(-1) }); - context.Loggers.Add(new Domain.Entities.Log.Logger() { Message = "Error", Level = "Error", UserName = "System", TimeStamp = System.DateTime.Now.AddHours(-1) }); - context.Loggers.Add(new Domain.Entities.Log.Logger() { Message = "Warning", Level = "Warning", UserName = "System", TimeStamp = System.DateTime.Now.AddHours(-2) }); - context.Loggers.Add(new Domain.Entities.Log.Logger() { Message = "Trace", Level = "Trace", UserName = "System", TimeStamp = System.DateTime.Now.AddHours(-4) }); - context.Loggers.Add(new Domain.Entities.Log.Logger() { Message = "Fatal", Level = "Fatal", UserName = "System", TimeStamp = System.DateTime.Now.AddHours(-4) }); + } + if (!context.Products.Any()) + { + context.Products.Add(new Domain.Entities.Product() { Name = "IPhone 13 Pro", Description= "Apple iPhone 13 Pro smartphone. Announced Sep 2021. Features 6.1″ display, Apple A15 Bionic chipset, 3095 mAh battery, 1024 GB storage.", Unit="EA",Price=999.98m }); + context.Products.Add(new Domain.Entities.Product() { Name = "MI 12 Pro", Description = "Xiaomi 12 Pro Android smartphone. Announced Dec 2021. Features 6.73″ display, Snapdragon 8 Gen 1 chipset, 4600 mAh battery, 256 GB storage.", Unit = "EA", Price = 199.00m }); await context.SaveChangesAsync(); } } diff --git a/src/Infrastructure/Persistence/Configurations/ProductConfiguration.cs b/src/Infrastructure/Persistence/Configurations/ProductConfiguration.cs new file mode 100644 index 000000000..52669334b --- /dev/null +++ b/src/Infrastructure/Persistence/Configurations/ProductConfiguration.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json; +using CleanArchitecture.Blazor.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CleanArchitecture.Infrastructure.Persistence.Configurations; + +public class ProductConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.Property(e => e.Pictures) + .HasConversion( + v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null), + v => JsonSerializer.Deserialize>(v, (JsonSerializerOptions)null), + new ValueComparer>( + (c1, c2) => c1.SequenceEqual(c2), + c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())), + c => c.ToList())); + + + } +} From 29fa11be560f64842b2611e4d069fa55ad0ef81b Mon Sep 17 00:00:00 2001 From: neozhu Date: Thu, 10 Feb 2022 20:34:42 +0800 Subject: [PATCH 12/23] wip: product features --- .gitignore | 3 + .../Components/Common/ErrorHandler.razor.cs | 2 - .../EntityTable/EntityTable.razor.cs | 2 - .../14510888-58de-4ac2-946b-7e1aa5e8e459.jpg | Bin 3276 -> 0 bytes .../240a3d4e-dbaf-49ec-ac63-a3005bc1a4e8.jpg | Bin 4130 -> 0 bytes .../3eac5dca-2176-42a1-a5a6-9fdd4b078e68.jpg | Bin 3539 -> 0 bytes .../47bd017e-2c1a-4959-a6f4-e8ef5931e36c.jpg | Bin 4130 -> 0 bytes .../4ba77fd9-b7a2-4c5b-8ac7-d229671681a4.jpg | Bin 3539 -> 0 bytes .../4ea10986-bd32-4049-b052-3b305551ca77.jpg | Bin 3276 -> 0 bytes .../6c690af3-eb07-4812-bc09-3c5ff428f54b.jpg | Bin 3539 -> 0 bytes .../cec33b69-605d-4eb3-b4cf-89ab14f3bb86.png | Bin 12189 -> 0 bytes .../d30fd1a3-3307-4822-98bf-0216977d2186.jpg | Bin 3043 -> 0 bytes .../f64289c5-9b43-4704-900d-aeb54cb1c9d2.jpg | Bin 4130 -> 0 bytes .../fab80e71-176b-4c3b-94b2-8e5fbe2c30cd.jpg | Bin 4232 -> 0 bytes .../fba2f6ed-2446-451f-98a2-5272d039336f.jpg | Bin 54208 -> 0 bytes .../fcd0e588-ce07-4e1b-b144-d58c3ee26812.jpg | Bin 3276 -> 0 bytes .../Pages/Identity/Roles/Roles.razor | 25 +++- .../Pages/Identity/Users/Users.razor | 23 ++- .../Pages/Products/Products.razor | 134 ++++++++++++++++-- .../Shared/MainLayout.razor.cs | 31 ++++ src/Blazor.Server.UI/_Imports.razor | 1 + 21 files changed, 201 insertions(+), 20 deletions(-) delete mode 100644 src/Blazor.Server.UI/Files/ProfilePictures/14510888-58de-4ac2-946b-7e1aa5e8e459.jpg delete mode 100644 src/Blazor.Server.UI/Files/ProfilePictures/240a3d4e-dbaf-49ec-ac63-a3005bc1a4e8.jpg delete mode 100644 src/Blazor.Server.UI/Files/ProfilePictures/3eac5dca-2176-42a1-a5a6-9fdd4b078e68.jpg delete mode 100644 src/Blazor.Server.UI/Files/ProfilePictures/47bd017e-2c1a-4959-a6f4-e8ef5931e36c.jpg delete mode 100644 src/Blazor.Server.UI/Files/ProfilePictures/4ba77fd9-b7a2-4c5b-8ac7-d229671681a4.jpg delete mode 100644 src/Blazor.Server.UI/Files/ProfilePictures/4ea10986-bd32-4049-b052-3b305551ca77.jpg delete mode 100644 src/Blazor.Server.UI/Files/ProfilePictures/6c690af3-eb07-4812-bc09-3c5ff428f54b.jpg delete mode 100644 src/Blazor.Server.UI/Files/ProfilePictures/cec33b69-605d-4eb3-b4cf-89ab14f3bb86.png delete mode 100644 src/Blazor.Server.UI/Files/ProfilePictures/d30fd1a3-3307-4822-98bf-0216977d2186.jpg delete mode 100644 src/Blazor.Server.UI/Files/ProfilePictures/f64289c5-9b43-4704-900d-aeb54cb1c9d2.jpg delete mode 100644 src/Blazor.Server.UI/Files/ProfilePictures/fab80e71-176b-4c3b-94b2-8e5fbe2c30cd.jpg delete mode 100644 src/Blazor.Server.UI/Files/ProfilePictures/fba2f6ed-2446-451f-98a2-5272d039336f.jpg delete mode 100644 src/Blazor.Server.UI/Files/ProfilePictures/fcd0e588-ce07-4e1b-b144-d58c3ee26812.jpg diff --git a/.gitignore b/.gitignore index dfcfd56f4..3947aed3d 100644 --- a/.gitignore +++ b/.gitignore @@ -348,3 +348,6 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ + + +*/Files/ diff --git a/src/Blazor.Server.UI/Components/Common/ErrorHandler.razor.cs b/src/Blazor.Server.UI/Components/Common/ErrorHandler.razor.cs index 3dc9210d7..3a2fce987 100644 --- a/src/Blazor.Server.UI/Components/Common/ErrorHandler.razor.cs +++ b/src/Blazor.Server.UI/Components/Common/ErrorHandler.razor.cs @@ -7,8 +7,6 @@ namespace Blazor.Server.UI.Components.Common; public partial class ErrorHandler { - [Inject] - public IAuthenticationService AuthService { get; set; } = default!; public List _receivedExceptions = new(); diff --git a/src/Blazor.Server.UI/Components/EntityTable/EntityTable.razor.cs b/src/Blazor.Server.UI/Components/EntityTable/EntityTable.razor.cs index 4e7b0040b..8e813013d 100644 --- a/src/Blazor.Server.UI/Components/EntityTable/EntityTable.razor.cs +++ b/src/Blazor.Server.UI/Components/EntityTable/EntityTable.razor.cs @@ -39,8 +39,6 @@ public partial class EntityTable [CascadingParameter] protected Task AuthState { get; set; } = default!; - [Inject] - protected IAuthorizationService AuthService { get; set; } = default!; private bool _canSearch; private bool _canCreate; diff --git a/src/Blazor.Server.UI/Files/ProfilePictures/14510888-58de-4ac2-946b-7e1aa5e8e459.jpg b/src/Blazor.Server.UI/Files/ProfilePictures/14510888-58de-4ac2-946b-7e1aa5e8e459.jpg deleted file mode 100644 index 23713ba9b32ff34c0e1aedbfb218d3c6c61482d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3276 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 diff --git a/src/Blazor.Server.UI/Files/ProfilePictures/240a3d4e-dbaf-49ec-ac63-a3005bc1a4e8.jpg b/src/Blazor.Server.UI/Files/ProfilePictures/240a3d4e-dbaf-49ec-ac63-a3005bc1a4e8.jpg deleted file mode 100644 index 0ffc7b1f97d2cae8ccf8cd84b37c3fa6943e82b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4130 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 diff --git a/src/Blazor.Server.UI/Files/ProfilePictures/47bd017e-2c1a-4959-a6f4-e8ef5931e36c.jpg b/src/Blazor.Server.UI/Files/ProfilePictures/47bd017e-2c1a-4959-a6f4-e8ef5931e36c.jpg deleted file mode 100644 index 0ffc7b1f97d2cae8ccf8cd84b37c3fa6943e82b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4130 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 diff --git a/src/Blazor.Server.UI/Files/ProfilePictures/4ea10986-bd32-4049-b052-3b305551ca77.jpg b/src/Blazor.Server.UI/Files/ProfilePictures/4ea10986-bd32-4049-b052-3b305551ca77.jpg deleted file mode 100644 index 23713ba9b32ff34c0e1aedbfb218d3c6c61482d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3276 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 diff --git a/src/Blazor.Server.UI/Files/ProfilePictures/6c690af3-eb07-4812-bc09-3c5ff428f54b.jpg b/src/Blazor.Server.UI/Files/ProfilePictures/6c690af3-eb07-4812-bc09-3c5ff428f54b.jpg deleted file mode 100644 index 456fd068ab1b85a9e8564cc6b3783b124652ebe4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3539 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 diff --git a/src/Blazor.Server.UI/Files/ProfilePictures/cec33b69-605d-4eb3-b4cf-89ab14f3bb86.png b/src/Blazor.Server.UI/Files/ProfilePictures/cec33b69-605d-4eb3-b4cf-89ab14f3bb86.png deleted file mode 100644 index 8a8745e729c60e52e7b2af582b0cc0cc486ae8bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12189 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 aVAw3L4bnC=gxh~E&@0HONLNdmh5aA=*o&h8 diff --git a/src/Blazor.Server.UI/Files/ProfilePictures/d30fd1a3-3307-4822-98bf-0216977d2186.jpg b/src/Blazor.Server.UI/Files/ProfilePictures/d30fd1a3-3307-4822-98bf-0216977d2186.jpg deleted file mode 100644 index edb1a8e30cd59fd39b287e84c201c20842ea259a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3043 zcmbW$c{tQ-8wc>;7|U2Q7)!R8u}f#{W66>*q%qb=i(O>TT8^!{zAP@k6P7ZK9 z1KbDLAj~YxOl&O7EbMG-98h6yC?_WrE+7aKmX(l~laY{?R#4VgS3v40N=s{8*3dC9 zGBGtdtA@438r$fjO^iTn?Cj7}P;qW3`@bKC_$AwU!m1qO)&bWji&3Oeou z6`#z=0QxJyzXPHJ(=#wKoy^P13DAMSU^;p*0|Pz%NoT~#8$b_b;1WaXF>+%anZ*5h z6rkmfEZ>LUOs+-(-M+W(lWBjDynMgD2)pS7Y&V!(I%Ew);6|R z?ChOgT;1F~Zg~dY2?`Dg4GWLG|KMRn**Q;hi%Uw&%5fEyRWBQx@Xal) zgtj+5y?y-y#KE^?{6(EKlW({AOQR~=cN7(`VSBE z#6w3<52k1O(*vRlIk{jcJ%bpMkxLKDDt&-$dz&HaR;7O4#{YJB9Q zunQ-hbvLAcvbYJ+Glv!ztS3LN+Mm!iP9nkcXMd$vWiL zH%M|K%G_bqr#r=I}lP$s-2;g^uN}+%KI-il!nyuN!GN+E1-IYpCUT-{FO!bDD zm$mh@irfn(UsPv*e$cS(QA|h#@`%vcU%}(5uI#O1V}LsSV!!ZeRICWjf56Rq{(Z@- zZ4rFv11VA+i%3;%ljsmE*J|wMhqppRYSJ;lchQUE+jY4F2+bNa@YO6JJUdZE5I67d zkJmJaWu=AGjuUU{G zD4Iudu65eyn>$rBUln{$2}D&(3`OGJC7^V3)Dj1%VE2FU+Z_N4jXT? z|gf|vi1e&`mbKAO@ zO`XwR?cK(@=lVupwJT~^>jSr!QH)GBAN5|Mt18IdEpC=p8B+bl)?uqwl^ zrIZ6J8SfX%+&hs$UBtZuD~*KBR?=yT{KdU^U7Pcs#{jD&26isL1~P;a&Q6o%VU&{TTV35vYqlG_1Qx<33Xb} zBkt-|nvAz#U4ZGM-rTfXZC8cx`H>GVdX~s}x=g>0%a4e;8S46TwOcakXv;OS9n}{u z^MR5rG#yQG(y6s3Y<;RoSZZ?LwQ_Y%i@~j-X*!^{7H2nJ1DNl}c@UMtZAKYAD1)72fIf54$vEh9%@Nv@yK zHaYo+YfE!^FP-@jLO*-AX;xhJkevQ2Tt$Ak=z>|R!6M_NyH948D!coQR||Jnbx$oV z3;`VKM^;-x6)Y^?D9x=%2e4tW$tzqiE#w!%}G-MRtO56R$r9mXqKq~>vVA#$=s<#qgHoU_MB&QRx=qgbhP zGF9c>d`Cy-C2_MadZUU}qf@BbxFkqp9TGy|`WS5MN@Y_W$7T>ErQYnMsALV;y?R_|NJh-eJ zg=mQv9%#QwDYW19z+~Ne27?dtrnnC)7n@Xet`|^>O@8pCN@5#=Tq}8f!;28O1ljNI z&S7l_q``B_4(!A_~uA(Zqc^RGo(0 zY)Cb7{*a^+cu`K+bbgy$>2Up!jO-+?o1;V0$q#rq%yN~kt!Pci_yo>X3z~@gSww-8t&=1(W-Phb-{BBq!lC^-g)3HeELr^YYS?@(ISRGq=?`th>hSeyv#(e$+Ia)qG#Xj%g~i_j0&P_IR)Npk8l70 diff --git a/src/Blazor.Server.UI/Files/ProfilePictures/f64289c5-9b43-4704-900d-aeb54cb1c9d2.jpg b/src/Blazor.Server.UI/Files/ProfilePictures/f64289c5-9b43-4704-900d-aeb54cb1c9d2.jpg deleted file mode 100644 index 0ffc7b1f97d2cae8ccf8cd84b37c3fa6943e82b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4130 zcmb`IcTiJXyT&&J2-QfJW@w7ifDD;Z|<|#GqYyaUVFd4_nmhw@H0pP4qKU7ngMKV zY`|Zv2LKlUEz_t#KLD__R0g;K0AK@7v9SY!teGUs2Qu^t_41Xq#CZDqUhwtwi3Y$K zAQfO|V>=lC7K9UWATACL2q%;a3Oz744<8RVH!n97$}7Oj%g4_Wl;;pkfdA0J{9xc*z#me#iRj?SK5LSO&D+rf9EW8)K(Q=g}a z3yVw3D_>XF);Fj>es1sV?$LhzW&m(Rfh9SN1{Jm19O=&H z5si)7)@R3izkjaNn*sq+J&x~&x#!XQOScH{e(iR$Aqc#=?}e@mcxJPauWsG3<0eWq zd(8UWIL)cb%FzbWSFLHF7FMZ_g|&{Up-EkM=<3^eg?`?-WK^*nx`>SSMs*JhldDyh zWfYiv7>c+3mEqu}nIkmDQUL5m3bidT#&KwhE_Q1-q*+XGkScT}v_<8!s%&HDnELnC zpU3jOFZ4v^+p}G1YT&KrQOdH-o-&iBZpK@iI(&*Vt)e}*%G$Sn-8BBfU#h&l<-T8* z;f91Il!Cn8bmBx=7I30Yh-yb>Q1*J)9A`eL0r zXI@8BvT7~OmuRHaO!UG-HC<{tKp+uT84c@yxpfOJO^1X(4noOxP1UOR9((n`1?#t- z9kJXpI-`BvHbvsc^gH3iizzPV5c^H*8sRx~;5<#aI1$Ae5K~(6#~0k9<#x}ATab{4 z+LN@Zx34a^j2^L+{lly6vX9I}%+$&N*`HMg6I#*!2uRXWy7yu{vL3aK;G6v@e?J@jj;WX))C#W$d1?!P~BocpoLsR6WAit(&Gnm}g6fF}eB$fy_NGnXgL{-0+njm#Z1&3qu zc<(C)>V}zOsl)d1D0%r}C+qs(f97pxx>dFn_^e797e3UVvTKUZMs5rzB6GAzwYZ>? zbMBg1KcamEqw535-a09T!P`+AiLi2=Gtz!@QP2e>{x?n}ND~A)Dy`7>M1LzSY|#`D z-vghVYkN;M_ZU|^w8G&qJ5)3X;M5@n=IER&uGnr3tdAR-;n=d|@&n!B|3VrB=5;2t z*M`Z8uHTtr34X@@q`!r1h>@9eYrfnZf#)Vzjm1s@som^Jx{~jW5^OI0Qgk54U-3Bm zOtASP^ziEbm9pJ5O$1D-~$Lm-EwICz_4NzV@XYTHf)fLqks@X zc#wn@A4&s()Y!gHAmH<*2?Qc^!dA|OjlIvmxm{O1k@8eF877;%L!YCce~CiY$GTyA zBKPWXq|Q&h-s|`EkPjAMMB@QGhpb?fLqA?T4uO$ObBR^6rD8svc*B1DejxyyYHJA_ zR%4n4OEN|_j_R_~RKA95-0OYrCUvQGjPA5_>s7bsSo2Pskj8R#S-sR@@36NU&T2&zkHx8em{4#@ zx!O?Wof;{=%#|n;OQj;orfSU-7o0JTMY(1+FJ?)5NBJ5iLYTZ8I!cbC;bAS zLKSSnFK zGHt2nr_hpcJuaA{jThLq(Hx>%1}|O?#W<5T^4_{zD&@R3^D`LXk-x%!-`PuQ^&$r^ znJGylt`4}%j4I`*Z-yY$j5F2B0;L;xEdchY)1p(Ub!7F*PR-|s-0Z(&n_v7!+;%2{ zfX!(0+9rloM7bo9w_15~r)3Lik~=US=4dNXCY4=A*Sr<07I~#1%|cIN?RCq-E}Sbt zcBA^}%p3Q!?T*W$BOw}C>buESMrpzcTdw(ZratTB4lGdm$^P=3dhEM|Y2B+J@Vr0| zKWpl-tfIWNTWVr{BY8**L9{q5>wutd_n*QuYG3axKi@wUcQN!=)oW$D-|Lc9KRxXV zcfDv2z2&+@tIY@K&7Dcu2cOP79D&hW zne=Fa5uUwR45gXra2fP|n5c1R>$>xmaW_Fe8cwA2atjCE=UzgBiP`D;R(jM^B1sc;sRHnMGJVOA-#!Ao2GQ7!~$e6Lze=%E3w z%G!0RfEm+U)xjd;I*(tJrfZ%er$k;^#TDpKb_{|r42jCP@Tp7V?8!=(&+Cy9Jh3k} zVoKu62(KmxZRuaQ-w<7+-&OQW8mF<98(9AE&3zDgbM$F#RZ|eUEsO5?$$fX$IbGHM zi@3^4+g1DecVC8vuEtdL?dMqLek)dvSs1K)NVgP@f-J7&^xXsjqse8LlV#!>AGdIM zs>vr6`Q$A$MDMjbo}Vq*!9wXGRMc}w^RBL={ia;P2**1@3`vf0&FyoX(KD%65@{CN z->)5x(p{i?2)E$4*U-Y4F5ilcTBHry1bx;1_?wYufedVHs%-xmt^KM=_nYL;K zOoZPU3PE>m^j+b{P5&sOoA8y4z$pO{g}zUaMYqXFsugiOxlG}SvnJbmVo%pG_0=$Q zl1G0I%#P-wUZLjqx8HbNt5`aQN_e2Fko5Pv@6sVT{ocOegGZ2C==xByB?$+MgEx%P z4XKe9G?(gt=$0JyE;ZC+wI5nq;zBdd=OVI`_WS4Zu4-svZO__(0GA~#mLS}Wr;L0# z9tb<#PASz4qq){x``t!ALi&aFWR!o(?ZI+l)5sT9^j;EdPuAF#E--2n#OKgk=-@iO z!{%q`qAw6X#ssWz&Hg>Ht%}UgU%1;iywQ}5Z>daU>pDL?2&Bl+U9%J3?;FuV3}=QY z6@&Cz8y|OUcOF*Q0qsv)Cs|^@J-HQu8_Wsb>G&1@7Uy$YH8ckfU4=%HYe*EkYa~XL zHwc_;QL7`sQ^Yg*ac$_dN8VFMMW_J$w}DDmJL?|`37UxCxa@g;9Ap2?3ezH}6i@M~ z8Zg{Lsnd^#?iHT*U_1(p52-4iO_^3GNpYnqs9N0GkB+iM8i>GX=5a>ta2mIAuhN^r z#9_J!q*oc{`8X;OOBQqJuKJ#QQ{Wu4jenL|atIaV1 zzvt`_9ni*`W;0ea&NB)7OkSQxh$nL}!dVblN)cVcQ_j1IFPPomR}B^*p@{=M^J2&( zWGC*BxqtO%8*TaXmzQ*b{_ho#`Xa>;*#dT42nb{z2Y~=r?BE3&5(Kb22&PGOF`nY9 zONIlCVFETqm=PK-0s=GocB1yr6B3`92E|>YKSi=?yI|w+z|WJi;(DgQUG|q-hdmHv&ZUz~nc4b@W0a2t+PzOdE3@qp5|57ISTKZuNJt3=jD+B;5+Ys7fPi!( zFr*;eT{GlJ4Sjg`-e+I?<38tJ*S)^1_2KzF>t4^<$k`0QV5n!H2Y^5T06Jg5**NeL zpaxS>Q-P_esi+|kY8og56iQ1AWuc?L$iU9R!NJbL#>T~a8OFse$iv14mw*cji-=tj z#PA_qya*J5K52$K*0#2WCWcxgVN7y zqyqgzz`q2dpggY?LPHC^zyMHyC@CqxlvGq;@cGTa^D_X>Drmdr^cSGO65NU3Kvb3_c zalHTF;iJb+&R$QwpZWOu`G>uH_4-YC1UBydUmrexicd&P&&bTm&dJUDT3qs@w5+_M zva0E4b4x3}t-YhKe_(KEcw}^Jc5Z%QacOylKqURy-r3#TCm$Sw0Lp*jobUex`VSt) za~=vXm=X;6n+HVUb8bpTFcrTnHPiLmkh>nt0&*{CShQl(iW+GJ<;}KO?|J@)vI!~7 zUMBsG^bex{4k+~hMf7i=fAgG;0T(Gj=MP572q1vNY^-Q1#knscc$?po1)CY$^!56e z!7(P1-x^OD?X|OfrJB(1!=Bwte-WoSkH`w7nnO#kOVbnCbd%YZAQ1@~TPP0YW7N)OO{#OnoWXd|ItYg|rc-iHsXvx#OkvI^D1d9v>i z@&4^)tvC%RU90CAJiQ{=9&$;lf80`8{mDLxSO~GpGZ$qt12>0!a_yU z^SeaTvB3YDUNCXHK5-oVAOV_L3Dw7IM+R9H>J&V0Evs`&;+dIggx{MAA*J-N_Jrqj za~HhPIU>=r=>A5@wX*%I<@UA_ z`*Ut)#*hRMY39u$_@6J}@ zsao)@7WQ)lQV4cM{&=QOuJd%ie6S%P% z`99=Y5Nj_Io+ut}hVf&vx^LRH()i)U%5`2{jar>J)Q!mT_tobuix2%*1pkg9MS_BX znl#H-E+r~!lP69)rjWP3>Ca;zz9Bad2^uNzX>QR@ATUrrH=ES@K?qG@+g0sp%Wn%a z#yn~{94xDyTV3ax6pVeEo58HFB4Dk=P_N0J;)VT)H}2~Z1`rsw>Q09Sbl29(~t^ZqQ7{IlGRswyM;prhqMMEbu0l!n2_G6t+ zJnEJo>i0o%Xf9wL%sw>T6nsH(HNNf0u32jWp=JGRg8mLI;+4~D;|#+q?VNM>^RgE< zM(3tW>RUZ#o%(;;UrtEU@)>vaoJc~6q@4kGPLnZf`k4ml1_Iu`6V#T-cMUu6eb$4g zKj~ZiKZOLzl6#u=WC=Eb$DUTHT^^IVO9zg>4HoE~cl_M^hKu%btbeB79_g>jP}w;e z6jkra$m>iGSZ0vj=Qm$^YsC_&R}B;Dm#f@jP7iHQQ_Y?{X6aXdhRw+$e&j06t?b*B z+=QzOg=)@kvwCKxrej5cbnLb}ww_%90W=S71Wo3D=&){`_ENtXbu2huxSc*sz2;Ay z73`F4ebXn`w#600(Ag>e$^wS8zcjx&MmNQ;o)Wg++#Q;J8pHxKH$DT9F#vS*ys^Bt32lmcj!EOms zLysU&(Y-#V(cNzUk?%F-1G+DJH=XEK7H|K#c9^n};z)j2UzaKC+Z)&ZH^Jl+k{(Z|;Jmnst zDgecp9Kwm-tS>wRZ0g)j3e-%C?VRe^%01%oV;fKWW#<$Tujk3weOD1wRi6f|X`~O zwpWgbf6W2kPOOi;kRiIvEpPNWytl1G|8^s9Fqe0}i7M^VZ1AfaCO8{@qH&zurKws% zw-^k08!m51bM&?$kCw5apRs96z5Dj%Ns(s%oY$L7n+5|?E(O=yWK?XA7<*$J9gqPO zal;;U7u&&K`;lYox#pe@PKPNoUI0Foax7Z4$!sdC|EkizF zFl`IU>XuKyL4pF=hKqRZoj9y$zLAO8NqSAN-;4>(cryP>|Bq>6SQULpr>R^QXtNC# z6@U!%_R6)p=@Y2%exj?@Kqxlip<}zy35H-YF6S%s_UF~mTa#jmNLV^=2&{|i;H!Sm zN6j7FogZ>Z1Fpr=3Dw_cj*gJR@6_8G=GCB{i%55zcBDRi+E;#<@o77?QYA^kO>gO@ z4=oWCa6TtHPWysK%f6^37QsR|Hv2Jq=5?VxXp-PW6&~u; z{^S=PWWli^vRy#mEZakZAcuCt7X-%FUA-=MMhm{(D1dls0@7!I?a%}5r3;q-ZrPrMI6MPp?UP6`4yWS-Pr0o)$EJ3hsuN zAot5~9(}=O9>$98<0A54bvWvRuAWfIrLPhA8ANM*kaREWo@-q9V$kbvZ&T{o7{|RlBvC^7E3yy zF4Qaf*wa)`$JE=x(|_&i$aq#W`Czhs#6E2J#l-NtF?~2jHB96$c*PAlM^)-4#_F$) zT%5T&i9Ymjsyh~c?nI_ka;#|78te;KJ#N=k8dz*bdUM%bJXlCv()&W_df&dSnzFUN z^R-Xd>ogczTOR#6W?&HWN<1Xf{hn5!M_e$pF1Dg!iQ#JdL40>jC%G_r`|dK zxUYy(_>;}V)rL3wm|yB9d6~b%qgBdbmD}GBQGrI18}G+srAocT8fh9qB3n4|QaRU0 z$_@Bi9@axT=y_+qPjWEX6OcOlvGX__oN!<6=Cu1H>&<)iOLoL-GZ#U_*io{bBV7&& z_Or~#M-JU0xnp=gGBR&MHU>|ru^SrzQFlHAerD;B!)9AzM;VA$XNq@WH8vm3P5Q3n zYP2JUyr~rBze*EDR7GxZzJheg21!9iWOjBKwEYORe{x|7 zh*#z=g`StY*6+>nEb~^tY}$`36TBokfAMKq>5ceX5IsUo&a$fIECoL^LTL{uI&N znb68h?{MJ_7W>z3<)3{#jC$n@V@=65iQ)^Z%W>B0$;I#Z5mNop03jOFJ-0~hr|x_H zJMb`WG`Xkwu}o{P$m*?v)WRHdc%^G`n`O6>H3%fJ?9w^^5S54nDu}}Qbi`A$XRpf zq^`?7E- z%F`$Fj7fAdG9*xTWFI%xN^3i~^(e%9nkm8ErTN*Ri>IIGM8^m?I_-wpjm1s$srXiY z5RXn`-^R`GD*d<@m4Rdq{GUQM@}mq1KCZqW1kDR97lcBd$|-f3)C&zE)?wdx1r7UZ zRPKgH{o(#r{8>I#Sw~|O96L=>)1gSTz-8>;CZaNoKk!=C^nj1dmGSX4&v#jkXXnwM zK3FjkQ|HGOCA1LAV)j7Hif9kKsUK{4Ed92t zH9KwQ#t?qUD?oS4EjIh$QvMhu3hZ<#JwdIs72wtrj~J*^BNUBI{mihMp0I1=LKl zwlnX3f-qA_iCR?ql(7LKie1?JQ)ir3)PN7_kgKnDRM<_DGoV-Ffe2`L`WPek$Dve< zI}PSXZz^6qgi_x0@eI%Mc!1%NC vS__}uXlO2PJHa8dj=FblYdCBtkdL)#bS(46WXM6xoM$jshszkv+35cOGr9Vf diff --git a/src/Blazor.Server.UI/Files/ProfilePictures/fba2f6ed-2446-451f-98a2-5272d039336f.jpg b/src/Blazor.Server.UI/Files/ProfilePictures/fba2f6ed-2446-451f-98a2-5272d039336f.jpg deleted file mode 100644 index cf14d999c1e900c257cda934c79539abc116d7c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54208 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 diff --git a/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor b/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor index ef0675c4e..03d4ecea6 100644 --- a/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor +++ b/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor @@ -1,4 +1,5 @@ @page "/indentity/roles" +@using Blazor.Server.UI.Components.Dialogs @using CleanArchitecture.Blazor.Infrastructure.Constants.ClaimTypes @using Microsoft.AspNetCore.Identity @using System.ComponentModel @@ -19,7 +20,7 @@ @L["Delete"] @@ -140,8 +142,6 @@ [CascadingParameter] private Task AuthState { get; set; } = default!; [Inject] - private IAuthorizationService AuthService { get; set; } = default!; - [Inject] private RoleManager _roleManager { get; set; } = default!; private List _permissions = new(); @@ -312,6 +312,25 @@ } } + private async Task OnDeleteChecked() + { + string deleteContent = L["You're sure you want to delete selected items:{0}?"]; + var parameters = new DialogParameters + { + { nameof(DeleteConfirmation.ContentText), string.Format(deleteContent, SelectItems.Count) } + }; + var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall, FullWidth = true, DisableBackdropClick = true }; + var dialog = DialogService.Show(L["Delete"], parameters, options); + var result = await dialog.Result; + if (!result.Cancelled) + { + foreach(var item in SelectItems) + { + await _roleManager.DeleteAsync(item); + RoleList.Remove(item); + } + } + } diff --git a/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor b/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor index 5f24b4e92..4289f0372 100644 --- a/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor +++ b/src/Blazor.Server.UI/Pages/Identity/Users/Users.razor @@ -1,4 +1,5 @@ @page "/indentity/users" +@using Blazor.Server.UI.Components.Dialogs @using Microsoft.AspNetCore.Identity @inject IStringLocalizer L @attribute [Authorize(Policy = Permissions.Users.View)] @@ -46,6 +47,7 @@ Disabled="@(!(SelectItems.Count>0))" Size="Size.Small" Style="margin-right: 5px;" + OnClick="OnDeleteChecked" IconColor="Color.Surface">@L["Delete"] } @if (_canImport) @@ -162,8 +164,6 @@ [CascadingParameter] protected Task AuthState { get; set; } = default!; [Inject] - protected IAuthorizationService AuthService { get; set; } = default!; - [Inject] private UserManager _userManager { get; set; } = default!; private bool _canCreate; @@ -289,6 +289,25 @@ } } + private async Task OnDeleteChecked() + { + string deleteContent = L["You're sure you want to delete selected items:{0}?"]; + var parameters = new DialogParameters + { + { nameof(DeleteConfirmation.ContentText), string.Format(deleteContent, SelectItems.Count) } + }; + var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall, FullWidth = true, DisableBackdropClick = true }; + var dialog = DialogService.Show(L["Delete"], parameters, options); + var result = await dialog.Result; + if (!result.Cancelled) + { + foreach(var item in SelectItems) + { + await _userManager.DeleteAsync(item); + UserList.Remove(item); + } + } + } private async Task OnSetActive(ApplicationUser item) { item.IsActive = !item.IsActive; diff --git a/src/Blazor.Server.UI/Pages/Products/Products.razor b/src/Blazor.Server.UI/Pages/Products/Products.razor index 959479ba3..74bfa80e6 100644 --- a/src/Blazor.Server.UI/Pages/Products/Products.razor +++ b/src/Blazor.Server.UI/Pages/Products/Products.razor @@ -1,29 +1,110 @@ @page "/pages/products" @using CleanArchitecture.Blazor.Application.Features.Products.DTOs @using CleanArchitecture.Blazor.Application.Features.Products.Queries.Pagination - @inject IStringLocalizer L +@Title + - Products +

+ Product + @L["Refresh"] + @if (_canCreate) + { + @L["Create"] + } + @if (_canDelete) + { + @L["Delete"] + } + @if (_canImport) + { + @L["Import Data"] + } + @if (_canExport) + { + @L["Export Data"] + } +
+ + + + @L["Actions"] @L["Product Name"] - @L["Price"] + @L["Price"] @L["Unit"] - + + + @if (_canEdit || _canDelete) + { + + @if (_canEdit) + { + @L["Edit"] + } + @if (_canDelete) + { + @L["Delete"] + } + + } + else + { + + @L["No Allowed"] + + } + @context.Name @context.Description - @context.Price + @context.Price @context.Unit @@ -38,33 +119,64 @@
@code { - private IEnumerable _pagedData; - private MudTable _table; + public string? Title { get; private set; } + private HashSet _selectedItems = new HashSet(); + private MudTable _table = default!; private int _totalItems; private string _searchString = string.Empty; private bool _loading; [Inject] private ISender _mediator { get; set; } = default!; + [CascadingParameter] + protected Task AuthState { get; set; } = default!; + + + private bool _canSearch; + private bool _canCreate; + private bool _canEdit; + private bool _canDelete; + private bool _canImport; + private bool _canExport; + protected override async Task OnInitializedAsync() + { + Title = L["Products"]; + var state = await AuthState; + _canCreate = (await AuthService.AuthorizeAsync(state.User, Permissions.Products.Create)).Succeeded; + _canSearch = (await AuthService.AuthorizeAsync(state.User, Permissions.Products.Search)).Succeeded; + _canEdit = (await AuthService.AuthorizeAsync(state.User, Permissions.Products.Edit)).Succeeded; + _canDelete = (await AuthService.AuthorizeAsync(state.User, Permissions.Products.Delete)).Succeeded; + _canImport = (await AuthService.AuthorizeAsync(state.User, Permissions.Products.Import)).Succeeded; + _canExport = (await AuthService.AuthorizeAsync(state.User, Permissions.Products.Export)).Succeeded; + } private async Task> ServerReload(TableState state) { _loading = true; var request = new ProductsWithPaginationQuery() { Keyword = _searchString, - OrderBy = string.IsNullOrEmpty(state.SortLabel)?"Id":state.SortLabel, - SortDirection = (state.SortDirection== SortDirection.None?SortDirection.Descending.ToString():state.SortDirection.ToString()), + OrderBy = string.IsNullOrEmpty(state.SortLabel) ? "Id" : state.SortLabel, + SortDirection = (state.SortDirection == SortDirection.None ? SortDirection.Descending.ToString() : state.SortDirection.ToString()), PageNumber = state.Page + 1, PageSize = state.PageSize }; var result = await _mediator.Send(request); _loading = false; - return new TableData() { TotalItems = result.TotalItems, Items = result.Items }; - + return new TableData() { TotalItems = result.TotalItems, Items = result.Items }; + } private async Task OnSearch(string text) { _searchString = text; await _table.ReloadServerData(); } + private async Task OnRefresh() + { + _searchString = string.Empty; + await _table.ReloadServerData(); + } + private Task OnCreate() + { + return Task.CompletedTask; + } } diff --git a/src/Blazor.Server.UI/Shared/MainLayout.razor.cs b/src/Blazor.Server.UI/Shared/MainLayout.razor.cs index 80d8a4b6b..e8d701d49 100644 --- a/src/Blazor.Server.UI/Shared/MainLayout.razor.cs +++ b/src/Blazor.Server.UI/Shared/MainLayout.razor.cs @@ -44,6 +44,37 @@ public partial class MainLayout : IDisposable }; private readonly MudTheme _theme = new() { + Shadows = new() { + Elevation = new string[] + { + "none", + "0 2px 4px -1px rgba(6, 24, 44, 0.2)", + "0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12)", + "0 30px 60px rgba(0,0,0,0.12)", + "0 6px 12px -2px rgba(50,50,93,0.25),0 3px 7px -3px rgba(0,0,0,0.3)", + "0 50px 100px -20px rgba(50,50,93,0.25),0 30px 60px -30px rgba(0,0,0,0.3)", + "0px 3px 5px -1px rgba(0,0,0,0.2),0px 6px 10px 0px rgba(0,0,0,0.14),0px 1px 18px 0px rgba(0,0,0,0.12)", + "0px 4px 5px -2px rgba(0,0,0,0.2),0px 7px 10px 1px rgba(0,0,0,0.14),0px 2px 16px 1px rgba(0,0,0,0.12)", + "0px 5px 5px -3px rgba(0,0,0,0.2),0px 8px 10px 1px rgba(0,0,0,0.14),0px 3px 14px 2px rgba(0,0,0,0.12)", + "0px 5px 6px -3px rgba(0,0,0,0.2),0px 9px 12px 1px rgba(0,0,0,0.14),0px 3px 16px 2px rgba(0,0,0,0.12)", + "0px 6px 6px -3px rgba(0,0,0,0.2),0px 10px 14px 1px rgba(0,0,0,0.14),0px 4px 18px 3px rgba(0,0,0,0.12)", + "0px 6px 7px -4px rgba(0,0,0,0.2),0px 11px 15px 1px rgba(0,0,0,0.14),0px 4px 20px 3px rgba(0,0,0,0.12)", + "0px 7px 8px -4px rgba(0,0,0,0.2),0px 12px 17px 2px rgba(0,0,0,0.14),0px 5px 22px 4px rgba(0,0,0,0.12)", + "0px 7px 8px -4px rgba(0,0,0,0.2),0px 13px 19px 2px rgba(0,0,0,0.14),0px 5px 24px 4px rgba(0,0,0,0.12)", + "0px 7px 9px -4px rgba(0,0,0,0.2),0px 14px 21px 2px rgba(0,0,0,0.14),0px 5px 26px 4px rgba(0,0,0,0.12)", + "0px 8px 9px -5px rgba(0,0,0,0.2),0px 15px 22px 2px rgba(0,0,0,0.14),0px 6px 28px 5px rgba(0,0,0,0.12)", + "0px 8px 10px -5px rgba(0,0,0,0.2),0px 16px 24px 2px rgba(0,0,0,0.14),0px 6px 30px 5px rgba(0,0,0,0.12)", + "0px 8px 11px -5px rgba(0,0,0,0.2),0px 17px 26px 2px rgba(0,0,0,0.14),0px 6px 32px 5px rgba(0,0,0,0.12)", + "0px 9px 11px -5px rgba(0,0,0,0.2),0px 18px 28px 2px rgba(0,0,0,0.14),0px 7px 34px 6px rgba(0,0,0,0.12)", + "0px 9px 12px -6px rgba(0,0,0,0.2),0px 19px 29px 2px rgba(0,0,0,0.14),0px 7px 36px 6px rgba(0,0,0,0.12)", + "0px 10px 13px -6px rgba(0,0,0,0.2),0px 20px 31px 3px rgba(0,0,0,0.14),0px 8px 38px 7px rgba(0,0,0,0.12)", + "0px 10px 13px -6px rgba(0,0,0,0.2),0px 21px 33px 3px rgba(0,0,0,0.14),0px 8px 40px 7px rgba(0,0,0,0.12)", + "0px 10px 14px -6px rgba(0,0,0,0.2),0px 22px 35px 3px rgba(0,0,0,0.14),0px 8px 42px 7px rgba(0,0,0,0.12)", + "0 50px 100px -20px rgba(50, 50, 93, 0.25), 0 30px 60px -30px rgba(0, 0, 0, 0.30)", + "2.8px 2.8px 2.2px rgba(0, 0, 0, 0.02),6.7px 6.7px 5.3px rgba(0, 0, 0, 0.028),12.5px 12.5px 10px rgba(0, 0, 0, 0.035),22.3px 22.3px 17.9px rgba(0, 0, 0, 0.042),41.8px 41.8px 33.4px rgba(0, 0, 0, 0.05),100px 100px 80px rgba(0, 0, 0, 0.07)", + "0px 0px 20px 0px rgba(0, 0, 0, 0.05)" + } + }, Palette = new Palette { Primary = "#2d4275", diff --git a/src/Blazor.Server.UI/_Imports.razor b/src/Blazor.Server.UI/_Imports.razor index 3fc89676a..8f80fed4b 100644 --- a/src/Blazor.Server.UI/_Imports.razor +++ b/src/Blazor.Server.UI/_Imports.razor @@ -30,4 +30,5 @@ @inject ISnackbar Snackbar @inject IDialogService DialogService @inject IConfiguration Config +@inject IAuthorizationService AuthService @attribute [Authorize] From 0c2b8e983b8addc0991f720832491d680ebe8f3d Mon Sep 17 00:00:00 2001 From: neozhu Date: Thu, 10 Feb 2022 20:35:36 +0800 Subject: [PATCH 13/23] fixed --- .gitignore | 2 +- src/Blazor.Server.UI/Blazor.Server.UI.csproj | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3947aed3d..b02fdef0a 100644 --- a/.gitignore +++ b/.gitignore @@ -350,4 +350,4 @@ MigrationBackup/ .ionide/ -*/Files/ +**/Files/ diff --git a/src/Blazor.Server.UI/Blazor.Server.UI.csproj b/src/Blazor.Server.UI/Blazor.Server.UI.csproj index cffb62f0e..da39f39c2 100644 --- a/src/Blazor.Server.UI/Blazor.Server.UI.csproj +++ b/src/Blazor.Server.UI/Blazor.Server.UI.csproj @@ -54,6 +54,7 @@ + From b56a50fd5a4fb01f6991d97d78ef906499c95328 Mon Sep 17 00:00:00 2001 From: neozhu Date: Thu, 10 Feb 2022 20:43:04 +0800 Subject: [PATCH 14/23] update nuget packages --- src/Application/Application.csproj | 10 +++++----- src/Blazor.Server.UI/Blazor.Server.UI.csproj | 13 ++++++++----- src/Domain/Domain.csproj | 4 ++-- src/Infrastructure/Infrastructure.csproj | 16 ++++++++-------- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/Application/Application.csproj b/src/Application/Application.csproj index f6b2c3d47..e26c287a2 100644 --- a/src/Application/Application.csproj +++ b/src/Application/Application.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -23,12 +23,12 @@ - - + + - + - + diff --git a/src/Blazor.Server.UI/Blazor.Server.UI.csproj b/src/Blazor.Server.UI/Blazor.Server.UI.csproj index da39f39c2..69a36444e 100644 --- a/src/Blazor.Server.UI/Blazor.Server.UI.csproj +++ b/src/Blazor.Server.UI/Blazor.Server.UI.csproj @@ -13,11 +13,14 @@ - - - - - + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Domain/Domain.csproj b/src/Domain/Domain.csproj index 2697a78dc..835e9f9a8 100644 --- a/src/Domain/Domain.csproj +++ b/src/Domain/Domain.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -8,7 +8,7 @@ - + diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index 93689554b..96dc8ef33 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -11,17 +11,17 @@ - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + From 2c2184ccd97e68ac5f1add2194f313d9beb53578 Mon Sep 17 00:00:00 2001 From: neozhu Date: Fri, 11 Feb 2022 08:35:58 +0800 Subject: [PATCH 15/23] enable nullable --- src/Application/Application.csproj | 1 + src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor | 2 +- src/Blazor.Server.UI/appsettings.json | 2 +- src/Domain/Domain.csproj | 1 + src/Infrastructure/Configurations/SmartSettings.cs | 4 ++-- src/Infrastructure/DependencyInjection.cs | 4 ++-- src/Infrastructure/Infrastructure.csproj | 1 + src/Infrastructure/Persistence/ApplicationDbContextSeed.cs | 2 -- 8 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Application/Application.csproj b/src/Application/Application.csproj index f6b2c3d47..6ba4528a0 100644 --- a/src/Application/Application.csproj +++ b/src/Application/Application.csproj @@ -5,6 +5,7 @@ CleanArchitecture.Blazor.Application CleanArchitecture.Blazor.Application enable + enable diff --git a/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor b/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor index ef0675c4e..fa8adebf5 100644 --- a/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor +++ b/src/Blazor.Server.UI/Pages/Identity/Roles/Roles.razor @@ -19,7 +19,7 @@ CleanArchitecture.Blazor.Domain CleanArchitecture.Blazor.Domain enable + enable diff --git a/src/Infrastructure/Configurations/SmartSettings.cs b/src/Infrastructure/Configurations/SmartSettings.cs index 63a48ccf1..61d11955d 100644 --- a/src/Infrastructure/Configurations/SmartSettings.cs +++ b/src/Infrastructure/Configurations/SmartSettings.cs @@ -3,9 +3,9 @@ namespace CleanArchitecture.Blazor.Infrastructure.Configurations; -public class SmartSettings +public class DashbordSettings { - public const string SectionName = nameof(SmartSettings); + public const string SectionName = nameof(DashbordSettings); public string Version { get; set; } public string App { get; set; } diff --git a/src/Infrastructure/DependencyInjection.cs b/src/Infrastructure/DependencyInjection.cs index aa10cf88c..40ffb6e89 100644 --- a/src/Infrastructure/DependencyInjection.cs +++ b/src/Infrastructure/DependencyInjection.cs @@ -44,8 +44,8 @@ public static IServiceCollection AddInfrastructure(this IServiceCollection servi options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); - services.Configure(configuration.GetSection(SmartSettings.SectionName)); - services.AddSingleton(s => s.GetRequiredService>().Value); + services.Configure(configuration.GetSection(DashbordSettings.SectionName)); + services.AddSingleton(s => s.GetRequiredService>().Value); services.AddSingleton(); services.AddScoped(provider => provider.GetService()); services.AddScoped(); diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index 93689554b..3142f5386 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -5,6 +5,7 @@ CleanArchitecture.Blazor.Infrastructure CleanArchitecture.Blazor.Infrastructure enable + enable diff --git a/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs b/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs index 89d7d5257..15c8dce58 100644 --- a/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs +++ b/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs @@ -1,10 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using CleanArchitecture.Blazor.Application.Common.Extensions; using CleanArchitecture.Blazor.Application.Common.Extensions; using CleanArchitecture.Blazor.Infrastructure.Constants.Role; -using Microsoft.AspNetCore.Identity; using System.Reflection; From d444cc4a15661749c6dc4ef4e7fb6cdfa13366af Mon Sep 17 00:00:00 2001 From: neozhu Date: Fri, 11 Feb 2022 09:15:20 +0800 Subject: [PATCH 16/23] code refactoring for nullable --- src/Blazor.Server.UI/Blazor.Server.UI.csproj | 1 + .../Components/Shared/NavMenu.razor | 4 +-- .../Components/Shared/SideMenu.razor | 6 ++--- .../Shared/MainLayout.razor.cs | 25 ++++++++++++------ src/Blazor.Server.UI/_Imports.razor | 4 ++- src/Blazor.Server.UI/appsettings.json | 9 ++++--- src/Domain/Common/AuditableEntity.cs | 10 +++---- src/Domain/Entities/Audit/AuditTrail.cs | 12 ++++----- src/Domain/Entities/Customer.cs | 26 +++++++++---------- src/Domain/Entities/Document.cs | 8 +++--- src/Domain/Entities/DocumentType.cs | 4 +-- src/Domain/Entities/KeyValue.cs | 8 +++--- src/Domain/Entities/Logger/Logger.cs | 21 ++++++++------- src/Domain/Entities/Product.cs | 8 +++--- .../Configurations/DashbordSettings.cs | 20 ++++++++++++++ .../Configurations/SmartSettings.cs | 17 ------------ src/Infrastructure/Configurations/Theme.cs | 15 +++++------ .../Identity/ApplicationRole.cs | 7 ++--- .../Identity/ApplicationRoleClaim.cs | 16 +++--------- .../Identity/ApplicationUser.cs | 8 +++--- .../Identity/ApplicationUserClaim.cs | 14 +++------- .../Identity/ApplicationUserLogin.cs | 2 +- .../Identity/ApplicationUserRole .cs | 5 ++-- .../Identity/ApplicationUserToken.cs | 2 +- .../IdentityUserConfiguration.cs | 12 +-------- 25 files changed, 129 insertions(+), 135 deletions(-) create mode 100644 src/Infrastructure/Configurations/DashbordSettings.cs delete mode 100644 src/Infrastructure/Configurations/SmartSettings.cs diff --git a/src/Blazor.Server.UI/Blazor.Server.UI.csproj b/src/Blazor.Server.UI/Blazor.Server.UI.csproj index 69a36444e..0baa3926a 100644 --- a/src/Blazor.Server.UI/Blazor.Server.UI.csproj +++ b/src/Blazor.Server.UI/Blazor.Server.UI.csproj @@ -58,6 +58,7 @@ + diff --git a/src/Blazor.Server.UI/Components/Shared/NavMenu.razor b/src/Blazor.Server.UI/Components/Shared/NavMenu.razor index e0a91e381..abd9cd019 100644 --- a/src/Blazor.Server.UI/Components/Shared/NavMenu.razor +++ b/src/Blazor.Server.UI/Components/Shared/NavMenu.razor @@ -45,5 +45,5 @@ - COMPANY - \ No newline at end of file + @Settings.Company + diff --git a/src/Blazor.Server.UI/Components/Shared/SideMenu.razor b/src/Blazor.Server.UI/Components/Shared/SideMenu.razor index 654a2330f..a2ee24d37 100644 --- a/src/Blazor.Server.UI/Components/Shared/SideMenu.razor +++ b/src/Blazor.Server.UI/Components/Shared/SideMenu.razor @@ -15,7 +15,7 @@ Icon="@Icons.Custom.Brands.MudBlazor" Size="Size.Large" /> - @L["Application Name"] + @L[Settings.AppName] @@ -121,9 +121,9 @@ - @@2022 Copyright + @Settings.Copyright Privacy Policy - version 1.0 + version @Settings.Version
diff --git a/src/Blazor.Server.UI/Shared/MainLayout.razor.cs b/src/Blazor.Server.UI/Shared/MainLayout.razor.cs index e8d701d49..b225f8ee9 100644 --- a/src/Blazor.Server.UI/Shared/MainLayout.razor.cs +++ b/src/Blazor.Server.UI/Shared/MainLayout.razor.cs @@ -4,6 +4,8 @@ using Blazor.Server.UI.Components.Shared; using Blazor.Server.UI.Models; using Toolbelt.Blazor.HotKeys; +using Microsoft.AspNetCore.Components.Authorization; +using CleanArchitecture.Blazor.Infrastructure.Extensions; namespace Blazor.Server.UI.Shared; @@ -188,7 +190,7 @@ public partial class MainLayout : IDisposable } }; - private readonly UserModel _user = new() + private UserModel _user = new() { Avatar = "./sample-data/avatar.png", DisplayName = "MudDemo", @@ -205,10 +207,11 @@ public partial class MainLayout : IDisposable private bool _themingDrawerOpen; - [Inject] private IDialogService _dialogService { get; set; } - [Inject] private HotKeys _hotKeys { get; set; } - [Inject] private ILocalStorageService _localStorage { get; set; } - + [Inject] private IDialogService _dialogService { get; set; } = default!; + [Inject] private HotKeys _hotKeys { get; set; } = default!; + [Inject] private ILocalStorageService _localStorage { get; set; } = default!; + [CascadingParameter] + protected Task AuthState { get; set; } = default!; public void Dispose() { _hotKeysContext?.Dispose(); @@ -224,12 +227,18 @@ protected override async Task OnAfterRenderAsync(bool firstRender) StateHasChanged(); } } - protected override Task OnInitializedAsync() + protected override async Task OnInitializedAsync() { - + var state = await AuthState; + _user = new UserModel() + { + Avatar = state.User.GetProfilePictureDataUrl(), + DisplayName = state.User.GetDisplayName(), + Email = state.User.GetEmail(), + Role = state.User.GetRoles().FirstOrDefault() + }; _hotKeysContext = _hotKeys.CreateContext() .Add(ModKeys.Meta, Keys.K, OpenCommandPalette, "Open command palette."); - return Task.CompletedTask; } protected void SideMenuDrawerOpenChangedHandler(bool state) { diff --git a/src/Blazor.Server.UI/_Imports.razor b/src/Blazor.Server.UI/_Imports.razor index 8f80fed4b..8e3b643e9 100644 --- a/src/Blazor.Server.UI/_Imports.razor +++ b/src/Blazor.Server.UI/_Imports.razor @@ -1,5 +1,6 @@ @using System.Net.Http @using System.Net.Http.Json +@using CleanArchitecture.Blazor.Infrastructure.Configurations @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @@ -26,7 +27,8 @@ @using CleanArchitecture.Blazor.Application.Common.Interfaces @using CleanArchitecture.Blazor.Application.Common.Models @using CleanArchitecture.Blazor.Domain.Enums - +@using Microsoft.AspNetCore.Components.Authorization +@inject DashbordSettings Settings @inject ISnackbar Snackbar @inject IDialogService DialogService @inject IConfiguration Config diff --git a/src/Blazor.Server.UI/appsettings.json b/src/Blazor.Server.UI/appsettings.json index 22fb79ed3..a46663957 100644 --- a/src/Blazor.Server.UI/appsettings.json +++ b/src/Blazor.Server.UI/appsettings.json @@ -17,13 +17,14 @@ }, "DashbordSettings": { "Version": "4.3.2", - "App": "Razor", - "AppName": "Razor Page WebApp", - "AppFlavor": "ASP.NET Core 6.0", + "App": "Blazor", + "AppName": "Blazor Dashbord", + "AppFlavor": "Blazor .NET 6.0", "AppFlavorSubscript": ".NET 6.0", + "Company": "Company", + "Copyright": "@2022 Copyright", "Theme": { "ThemeVersion": "4.5.1", - "IconPrefix": "fal", "Logo": "logo.png", "User": "hualin,zhu", "Email": "new163@163.com", diff --git a/src/Domain/Common/AuditableEntity.cs b/src/Domain/Common/AuditableEntity.cs index 33e4dd422..5d9e6f6fe 100644 --- a/src/Domain/Common/AuditableEntity.cs +++ b/src/Domain/Common/AuditableEntity.cs @@ -9,23 +9,23 @@ public interface IEntity } public abstract class AuditableEntity : IEntity { - public DateTime Created { get; set; } + public DateTime? Created { get; set; } - public string CreatedBy { get; set; } + public string? CreatedBy { get; set; } public DateTime? LastModified { get; set; } - public string LastModifiedBy { get; set; } + public string? LastModifiedBy { get; set; } } public interface ISoftDelete { DateTime? Deleted { get; set; } - string DeletedBy { get; set; } + string? DeletedBy { get; set; } } public abstract class AuditableSoftDeleteEntity : AuditableEntity, ISoftDelete { public DateTime? Deleted { get; set; } - public string DeletedBy { get; set; } + public string? DeletedBy { get; set; } } diff --git a/src/Domain/Entities/Audit/AuditTrail.cs b/src/Domain/Entities/Audit/AuditTrail.cs index eb0b8c940..e4e24f64a 100644 --- a/src/Domain/Entities/Audit/AuditTrail.cs +++ b/src/Domain/Entities/Audit/AuditTrail.cs @@ -8,14 +8,14 @@ namespace CleanArchitecture.Blazor.Domain.Entities.Audit; public class AuditTrail : IEntity { public int Id { get; set; } - public string UserId { get; set; } + public string? UserId { get; set; } public AuditType AuditType { get; set; } - public string TableName { get; set; } + public string? TableName { get; set; } public DateTime DateTime { get; set; } - public Dictionary OldValues { get; set; } = new(); - public Dictionary NewValues { get; set; } = new(); - public ICollection AffectedColumns { get; set; } - public Dictionary PrimaryKey { get; set; } = new(); + public Dictionary? OldValues { get; set; } = new(); + public Dictionary? NewValues { get; set; } = new(); + public ICollection? AffectedColumns { get; set; } + public Dictionary? PrimaryKey { get; set; } = new(); public List TemporaryProperties { get; } = new(); public bool HasTemporaryProperties => TemporaryProperties.Any(); diff --git a/src/Domain/Entities/Customer.cs b/src/Domain/Entities/Customer.cs index 13b296800..857c3d9e6 100644 --- a/src/Domain/Entities/Customer.cs +++ b/src/Domain/Entities/Customer.cs @@ -6,26 +6,26 @@ namespace CleanArchitecture.Blazor.Domain.Entities; public partial class Customer : AuditableEntity, IHasDomainEvent, IAuditTrial { public int Id { get; set; } - public string Name { get; set; } - public string NameOfEnglish { get; set; } - public string GroupName { get; set; } + public string? Name { get; set; } + public string? NameOfEnglish { get; set; } + public string? GroupName { get; set; } public PartnerType PartnerType { get; set; } - public string Region { get; set; } - public string Sales { get; set; } - public string RegionSalesDirector { get; set; } + public string? Region { get; set; } + public string? Sales { get; set; } + public string? RegionSalesDirector { get; set; } - public string Address { get; set; } + public string? Address { get; set; } - public string AddressOfEnglish { get; set; } + public string? AddressOfEnglish { get; set; } - public string Contact { get; set; } + public string? Contact { get; set; } - public string Email { get; set; } + public string? Email { get; set; } - public string PhoneNumber { get; set; } + public string? PhoneNumber { get; set; } - public string Fax { get; set; } - public string Comments { get; set; } + public string? Fax { get; set; } + public string? Comments { get; set; } public List DomainEvents { get; set; } = new(); } diff --git a/src/Domain/Entities/Document.cs b/src/Domain/Entities/Document.cs index 7672ba8cc..002d67633 100644 --- a/src/Domain/Entities/Document.cs +++ b/src/Domain/Entities/Document.cs @@ -6,11 +6,11 @@ namespace CleanArchitecture.Blazor.Domain.Entities; public class Document : AuditableEntity, IHasDomainEvent { public int Id { get; set; } - public string Title { get; set; } - public string Description { get; set; } + public string? Title { get; set; } + public string? Description { get; set; } public bool IsPublic { get; set; } - public string URL { get; set; } + public string? URL { get; set; } public int DocumentTypeId { get; set; } - public virtual DocumentType DocumentType { get; set; } + public virtual DocumentType DocumentType { get; set; } = default!; public List DomainEvents { get; set; } = new(); } diff --git a/src/Domain/Entities/DocumentType.cs b/src/Domain/Entities/DocumentType.cs index 55eff0d48..49578c583 100644 --- a/src/Domain/Entities/DocumentType.cs +++ b/src/Domain/Entities/DocumentType.cs @@ -6,6 +6,6 @@ namespace CleanArchitecture.Blazor.Domain.Entities; public class DocumentType : AuditableEntity { public int Id { get; set; } - public string Name { get; set; } - public string Description { get; set; } + public string? Name { get; set; } + public string? Description { get; set; } } diff --git a/src/Domain/Entities/KeyValue.cs b/src/Domain/Entities/KeyValue.cs index 6f1145ed7..fccef12e0 100644 --- a/src/Domain/Entities/KeyValue.cs +++ b/src/Domain/Entities/KeyValue.cs @@ -6,9 +6,9 @@ namespace CleanArchitecture.Blazor.Domain.Entities; public class KeyValue : AuditableEntity, IHasDomainEvent { public int Id { get; set; } - public string Name { get; set; } - public string Value { get; set; } - public string Text { get; set; } - public string Description { get; set; } + public string Name { get; set; } = default!; + public string Value { get; set; } = default!; + public string Text { get; set; } = default!; + public string? Description { get; set; } public List DomainEvents { get; set; } = new(); } diff --git a/src/Domain/Entities/Logger/Logger.cs b/src/Domain/Entities/Logger/Logger.cs index 4c3fec95e..57bf6b60e 100644 --- a/src/Domain/Entities/Logger/Logger.cs +++ b/src/Domain/Entities/Logger/Logger.cs @@ -6,15 +6,16 @@ namespace CleanArchitecture.Blazor.Domain.Entities.Log; public class Logger : IEntity { public int Id { get; set; } - public string Message { get; set; } - public string MessageTemplate { get; set; } - public string Level { get; set; } - public DateTime TimeStamp { get; set; } - public string Exception { get; set; } - public string UserName { get; set; } - public string ClientIP { get; set; } - public string ClientAgent { get; set; } - public string Properties { get; set; } - public string LogEvent { get; set; } + public string? Message { get; set; } + public string? MessageTemplate { get; set; } + public string Level { get; set; } = default!; + + public DateTime TimeStamp { get; set; } = DateTime.Now; + public string? Exception { get; set; } + public string? UserName { get; set; } + public string? ClientIP { get; set; } + public string? ClientAgent { get; set; } + public string? Properties { get; set; } + public string? LogEvent { get; set; } } diff --git a/src/Domain/Entities/Product.cs b/src/Domain/Entities/Product.cs index 4a072cb36..dbdd22c4b 100644 --- a/src/Domain/Entities/Product.cs +++ b/src/Domain/Entities/Product.cs @@ -6,10 +6,10 @@ namespace CleanArchitecture.Blazor.Domain.Entities; public class Product : AuditableEntity { public int Id { get; set; } - public string Name { get; set; } - public string Description { get; set; } - public string Unit { get; set; } + public string? Name { get; set; } + public string? Description { get; set; } + public string? Unit { get; set; } public decimal Price { get; set; } - public IList Pictures { get; set; } + public IList? Pictures { get; set; } } diff --git a/src/Infrastructure/Configurations/DashbordSettings.cs b/src/Infrastructure/Configurations/DashbordSettings.cs new file mode 100644 index 000000000..f303fff72 --- /dev/null +++ b/src/Infrastructure/Configurations/DashbordSettings.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace CleanArchitecture.Blazor.Infrastructure.Configurations; + +public class DashbordSettings +{ + public const string SectionName = nameof(DashbordSettings); + + public string Version { get; set; }="6.0.2"; + public string App { get; set; } = "Dashbord"; + public string AppName { get; set; } = "Admin Dashbord"; + public string AppFlavor { get; set; } = String.Empty; + public string AppFlavorSubscript { get; set; } = String.Empty; + + public string Company { get; set; } = "Company"; + public string Copyright { get; set; } = "@2022 Copyright"; + public Theme Theme { get; set; } = default!; + public Features Features { get; set; } = default!; +} diff --git a/src/Infrastructure/Configurations/SmartSettings.cs b/src/Infrastructure/Configurations/SmartSettings.cs deleted file mode 100644 index 61d11955d..000000000 --- a/src/Infrastructure/Configurations/SmartSettings.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace CleanArchitecture.Blazor.Infrastructure.Configurations; - -public class DashbordSettings -{ - public const string SectionName = nameof(DashbordSettings); - - public string Version { get; set; } - public string App { get; set; } - public string AppName { get; set; } - public string AppFlavor { get; set; } - public string AppFlavorSubscript { get; set; } - public Theme Theme { get; set; } - public Features Features { get; set; } -} diff --git a/src/Infrastructure/Configurations/Theme.cs b/src/Infrastructure/Configurations/Theme.cs index 207f0b260..edb08b2be 100644 --- a/src/Infrastructure/Configurations/Theme.cs +++ b/src/Infrastructure/Configurations/Theme.cs @@ -5,12 +5,11 @@ namespace CleanArchitecture.Blazor.Infrastructure.Configurations; public class Theme { - public string ThemeVersion { get; set; } - public string IconPrefix { get; set; } - public string Logo { get; set; } - public string User { get; set; } - public string Role { get; set; } - public string Email { get; set; } - public string Twitter { get; set; } - public string Avatar { get; set; } + public string ThemeVersion { get; set; } = default!; + public string Logo { get; set; } = default!; + public string User { get; set; } = default!; + public string Role { get; set; } = default!; + public string Email { get; set; } = default!; + public string Twitter { get; set; } = default!; + public string Avatar { get; set; } = default!; } diff --git a/src/Infrastructure/Identity/ApplicationRole.cs b/src/Infrastructure/Identity/ApplicationRole.cs index eb044bd58..461073ab6 100644 --- a/src/Infrastructure/Identity/ApplicationRole.cs +++ b/src/Infrastructure/Identity/ApplicationRole.cs @@ -7,17 +7,18 @@ namespace CleanArchitecture.Blazor.Infrastructure.Identity; public class ApplicationRole : IdentityRole { - public string Description { get; set; } + public string? Description { get; set; } public virtual ICollection RoleClaims { get; set; } public virtual ICollection UserRoles { get; set; } public ApplicationRole() : base() { RoleClaims = new HashSet(); + UserRoles = new HashSet(); } - public ApplicationRole(string roleName, string roleDescription = null) : base(roleName) + public ApplicationRole(string roleName) : base(roleName) { RoleClaims = new HashSet(); - Description = roleDescription; + UserRoles = new HashSet(); } } diff --git a/src/Infrastructure/Identity/ApplicationRoleClaim.cs b/src/Infrastructure/Identity/ApplicationRoleClaim.cs index a25b75d75..82d489286 100644 --- a/src/Infrastructure/Identity/ApplicationRoleClaim.cs +++ b/src/Infrastructure/Identity/ApplicationRoleClaim.cs @@ -7,17 +7,9 @@ namespace CleanArchitecture.Blazor.Infrastructure.Identity; public class ApplicationRoleClaim : IdentityRoleClaim { - public string Description { get; set; } - public string Group { get; set; } - public virtual ApplicationRole Role { get; set; } + public string? Description { get; set; } + public string? Group { get; set; } + public virtual ApplicationRole Role { get; set; } = default!; - public ApplicationRoleClaim() : base() - { - } - - public ApplicationRoleClaim(string roleClaimDescription = null, string roleClaimGroup = null) : base() - { - Description = roleClaimDescription; - Group = roleClaimGroup; - } + } diff --git a/src/Infrastructure/Identity/ApplicationUser.cs b/src/Infrastructure/Identity/ApplicationUser.cs index b90c43561..8b965a876 100644 --- a/src/Infrastructure/Identity/ApplicationUser.cs +++ b/src/Infrastructure/Identity/ApplicationUser.cs @@ -9,13 +9,13 @@ namespace CleanArchitecture.Blazor.Infrastructure.Identity; public class ApplicationUser : IdentityUser { - public string DisplayName { get; set; } - public string Site { get; set; } + public string? DisplayName { get; set; } + public string? Site { get; set; } [Column(TypeName = "text")] - public string ProfilePictureDataUrl { get; set; } + public string? ProfilePictureDataUrl { get; set; } public bool IsActive { get; set; } public bool IsLive { get; set; } - public string RefreshToken { get; set; } + public string? RefreshToken { get; set; } public DateTime RefreshTokenExpiryTime { get; set; } public virtual ICollection UserClaims { get; set; } public virtual ICollection UserRoles { get; set; } diff --git a/src/Infrastructure/Identity/ApplicationUserClaim.cs b/src/Infrastructure/Identity/ApplicationUserClaim.cs index 331c9d2cc..8ba9efead 100644 --- a/src/Infrastructure/Identity/ApplicationUserClaim.cs +++ b/src/Infrastructure/Identity/ApplicationUserClaim.cs @@ -7,15 +7,9 @@ namespace CleanArchitecture.Blazor.Infrastructure.Identity; public class ApplicationUserClaim : IdentityUserClaim { - public string Description { get; set; } - public virtual ApplicationUser User { get; set; } - public ApplicationUserClaim() : base() - { - } + public string? Description { get; set; } + public virtual ApplicationUser User { get; set; } = default!; + - public ApplicationUserClaim(string userClaimDescription = null) : base() - { - Description = userClaimDescription; - - } + } diff --git a/src/Infrastructure/Identity/ApplicationUserLogin.cs b/src/Infrastructure/Identity/ApplicationUserLogin.cs index 28b87bd50..a6a8406fa 100644 --- a/src/Infrastructure/Identity/ApplicationUserLogin.cs +++ b/src/Infrastructure/Identity/ApplicationUserLogin.cs @@ -7,5 +7,5 @@ namespace CleanArchitecture.Blazor.Infrastructure.Identity; public class ApplicationUserLogin : IdentityUserLogin { - public virtual ApplicationUser User { get; set; } + public virtual ApplicationUser User { get; set; } = default!; } diff --git a/src/Infrastructure/Identity/ApplicationUserRole .cs b/src/Infrastructure/Identity/ApplicationUserRole .cs index af6c9e3c1..34f981809 100644 --- a/src/Infrastructure/Identity/ApplicationUserRole .cs +++ b/src/Infrastructure/Identity/ApplicationUserRole .cs @@ -7,6 +7,7 @@ namespace CleanArchitecture.Blazor.Infrastructure.Identity; public class ApplicationUserRole : IdentityUserRole { - public virtual ApplicationUser User { get; set; } - public virtual ApplicationRole Role { get; set; } + public virtual ApplicationUser User { get; set; } = default!; + public virtual ApplicationRole Role { get; set; } = default!; + } diff --git a/src/Infrastructure/Identity/ApplicationUserToken.cs b/src/Infrastructure/Identity/ApplicationUserToken.cs index f2c8ef1b6..60a7b479f 100644 --- a/src/Infrastructure/Identity/ApplicationUserToken.cs +++ b/src/Infrastructure/Identity/ApplicationUserToken.cs @@ -7,5 +7,5 @@ namespace CleanArchitecture.Blazor.Infrastructure.Identity; public class ApplicationUserToken : IdentityUserToken { - public virtual ApplicationUser User { get; set; } + public virtual ApplicationUser User { get; set; } = default!; } diff --git a/src/Infrastructure/Persistence/Configurations/IdentityUserConfiguration.cs b/src/Infrastructure/Persistence/Configurations/IdentityUserConfiguration.cs index a83dcc975..031e6cffc 100644 --- a/src/Infrastructure/Persistence/Configurations/IdentityUserConfiguration.cs +++ b/src/Infrastructure/Persistence/Configurations/IdentityUserConfiguration.cs @@ -36,17 +36,7 @@ public void Configure(EntityTypeBuilder builder) .IsRequired(); } } -//public class ApplicationRoleConfiguration : IEntityTypeConfiguration -//{ -// //duplicate definition -// public void Configure(EntityTypeBuilder builder) -// { -// builder.HasMany(e => e.RoleClaims) -// .WithOne() -// .HasForeignKey(uc => uc.RoleId) -// .IsRequired(); -// } -//} + public class ApplicationRoleClaimConfiguration : IEntityTypeConfiguration { From 324d00c218168d405c9688cc7dea3f65256cff4f Mon Sep 17 00:00:00 2001 From: neozhu Date: Fri, 11 Feb 2022 09:54:51 +0800 Subject: [PATCH 17/23] add logout confirmation dialog --- .../Dialogs/LogoutConfirmation.razor | 35 +++++++++++++++++++ .../Components/Shared/UserMenu.razor | 2 +- .../Components/Shared/UserMenu.razor.cs | 19 +++++++--- src/Blazor.Server.UI/_Imports.razor | 5 +-- .../Persistence/ApplicationDbContextSeed.cs | 1 + 5 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 src/Blazor.Server.UI/Components/Dialogs/LogoutConfirmation.razor diff --git a/src/Blazor.Server.UI/Components/Dialogs/LogoutConfirmation.razor b/src/Blazor.Server.UI/Components/Dialogs/LogoutConfirmation.razor new file mode 100644 index 000000000..82d2e529f --- /dev/null +++ b/src/Blazor.Server.UI/Components/Dialogs/LogoutConfirmation.razor @@ -0,0 +1,35 @@ +@inject IStringLocalizer L + + + + + @L["Logout Confirmation"] + + + + @ContentText + + + @L["Cancel"] + @ButtonText + + + +@code { + [Parameter] public string? ContentText { get; set; } + + [Parameter] public string? ButtonText { get; set; } + + [Parameter] public Color Color { get; set; } + + [CascadingParameter] MudDialogInstance MudDialog { get; set; } = default!; + [Inject] private NavigationManager _navigation { get; set; }= default!; + async Task Submit() + { + Snackbar.Add(@L["Logged out"], MudBlazor.Severity.Info); + MudDialog.Close(DialogResult.Ok(true)); + _navigation.NavigateTo("/account/signout", forceLoad: true); + } + + void Cancel() => MudDialog.Cancel(); +} diff --git a/src/Blazor.Server.UI/Components/Shared/UserMenu.razor b/src/Blazor.Server.UI/Components/Shared/UserMenu.razor index 4680bf908..4b3505cbc 100644 --- a/src/Blazor.Server.UI/Components/Shared/UserMenu.razor +++ b/src/Blazor.Server.UI/Components/Shared/UserMenu.razor @@ -37,7 +37,7 @@ @L["Logout"] diff --git a/src/Blazor.Server.UI/Components/Shared/UserMenu.razor.cs b/src/Blazor.Server.UI/Components/Shared/UserMenu.razor.cs index 2e22be19f..0ef1d9458 100644 --- a/src/Blazor.Server.UI/Components/Shared/UserMenu.razor.cs +++ b/src/Blazor.Server.UI/Components/Shared/UserMenu.razor.cs @@ -1,17 +1,26 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Identity; using Blazor.Server.UI.Models; +using MudBlazor; +using Blazor.Server.UI.Components.Dialogs; namespace Blazor.Server.UI.Components.Shared; public partial class UserMenu { [Parameter] public string Class { get; set; } - [EditorRequired] [Parameter] public UserModel User { get; set; } - [Inject] private NavigationManager _navigation { get; set; } - private async Task Logout() + [EditorRequired] [Parameter] public UserModel User { get; set; } = default!; + private Task OnLogout() { - - //_navigation.NavigateTo("/pages/authentication/login"); + var parameters = new DialogParameters + { + { nameof(LogoutConfirmation.ContentText), $"{L["You are attempting to log out of application. Do you really want to log out?"]}"}, + { nameof(LogoutConfirmation.ButtonText), $"{L["Logout"]}"}, + { nameof(LogoutConfirmation.Color), Color.Error} + }; + + var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Small, FullWidth = true }; + DialogService.Show(L["Logout"], parameters, options); + return Task.CompletedTask; } } \ No newline at end of file diff --git a/src/Blazor.Server.UI/_Imports.razor b/src/Blazor.Server.UI/_Imports.razor index 8e3b643e9..1bc7a2d12 100644 --- a/src/Blazor.Server.UI/_Imports.razor +++ b/src/Blazor.Server.UI/_Imports.razor @@ -26,8 +26,9 @@ @using CleanArchitecture.Blazor.Application.Constants.Permission @using CleanArchitecture.Blazor.Application.Common.Interfaces @using CleanArchitecture.Blazor.Application.Common.Models -@using CleanArchitecture.Blazor.Domain.Enums -@using Microsoft.AspNetCore.Components.Authorization +@using CleanArchitecture.Blazor.Domain.Enums + + @inject DashbordSettings Settings @inject ISnackbar Snackbar @inject IDialogService DialogService diff --git a/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs b/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs index 15c8dce58..5be0f74ae 100644 --- a/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs +++ b/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs @@ -98,6 +98,7 @@ public static async Task SeedSampleDataAsync(ApplicationDbContext context) { context.Products.Add(new Domain.Entities.Product() { Name = "IPhone 13 Pro", Description= "Apple iPhone 13 Pro smartphone. Announced Sep 2021. Features 6.1″ display, Apple A15 Bionic chipset, 3095 mAh battery, 1024 GB storage.", Unit="EA",Price=999.98m }); context.Products.Add(new Domain.Entities.Product() { Name = "MI 12 Pro", Description = "Xiaomi 12 Pro Android smartphone. Announced Dec 2021. Features 6.73″ display, Snapdragon 8 Gen 1 chipset, 4600 mAh battery, 256 GB storage.", Unit = "EA", Price = 199.00m }); + context.Products.Add(new Domain.Entities.Product() { Name = "MX KEYS Mini", Description = "Logitech MX Keys Mini Introducing MX Keys Mini – a smaller, smarter, and mightier keyboard made for creators. Type with confidence on a keyboard crafted for efficiency, stability, and...", Unit = "PA", Price = 99.90m }); await context.SaveChangesAsync(); } } From 434a08ea4d29ee726462583827b1107e876a04ad Mon Sep 17 00:00:00 2001 From: neozhu Date: Fri, 11 Feb 2022 12:36:11 +0800 Subject: [PATCH 18/23] wip: developing the product features --- .../AddEdit/AddEditProductCommandValidator.cs | 33 +++-- .../Delete/DeleteProductCommandValidator.cs | 14 +-- .../Features/Products/DTOs/ProductDto.cs | 8 +- .../Components/Shared/UserMenu.razor.cs | 2 +- .../Pages/Products/Products.razor | 88 ++++++++++++- .../Pages/Products/_ProductFormDialog.razor | 119 ++++++++++++++++++ .../Shared/MainLayout.razor.cs | 62 ++++----- 7 files changed, 269 insertions(+), 57 deletions(-) create mode 100644 src/Blazor.Server.UI/Pages/Products/_ProductFormDialog.razor diff --git a/src/Application/Features/Products/Commands/AddEdit/AddEditProductCommandValidator.cs b/src/Application/Features/Products/Commands/AddEdit/AddEditProductCommandValidator.cs index 1628768e1..01f74f91c 100644 --- a/src/Application/Features/Products/Commands/AddEdit/AddEditProductCommandValidator.cs +++ b/src/Application/Features/Products/Commands/AddEdit/AddEditProductCommandValidator.cs @@ -1,17 +1,30 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace CleanArchitecture.Blazor.Application.Features.Products.Commands.AddEdit; - public class AddEditProductCommandValidator : AbstractValidator +public class AddEditProductCommandValidator : AbstractValidator +{ + public AddEditProductCommandValidator() { - public AddEditProductCommandValidator() - { - //TODO:Implementing AddEditProductCommandValidator method - //ex. RuleFor(v => v.Name) - // .MaximumLength(256) - // .NotEmpty(); - throw new System.NotImplementedException(); - } + + RuleFor(v => v.Name) + .MaximumLength(256) + .NotEmpty(); + RuleFor(v => v.Unit) + .MaximumLength(2) + .NotEmpty(); + RuleFor(v => v.Price) + .GreaterThanOrEqualTo(0); + } + public Func>> ValidateValue => async (model, propertyName) => + { + var result = await ValidateAsync(ValidationContext.CreateWithOptions((AddEditProductCommand)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return Array.Empty(); + return result.Errors.Select(e => e.ErrorMessage); + }; +} + diff --git a/src/Application/Features/Products/Commands/Delete/DeleteProductCommandValidator.cs b/src/Application/Features/Products/Commands/Delete/DeleteProductCommandValidator.cs index 82bf2f7e6..6f49b30f5 100644 --- a/src/Application/Features/Products/Commands/Delete/DeleteProductCommandValidator.cs +++ b/src/Application/Features/Products/Commands/Delete/DeleteProductCommandValidator.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace CleanArchitecture.Blazor.Application.Features.Products.Commands.Delete; @@ -7,18 +7,18 @@ public class DeleteProductCommandValidator : AbstractValidator v.Id).NotNull().GreaterThan(0); - throw new System.NotImplementedException(); + + RuleFor(v => v.Id).NotNull().GreaterThan(0); + } } public class DeleteCheckedProductsCommandValidator : AbstractValidator { public DeleteCheckedProductsCommandValidator() { - //TODO:Implementing DeleteProductCommandValidator method - //ex. RuleFor(v => v.Id).NotNull().NotEmpty(); - throw new System.NotImplementedException(); + + RuleFor(v => v.Id).NotNull().NotEmpty(); + } } diff --git a/src/Application/Features/Products/DTOs/ProductDto.cs b/src/Application/Features/Products/DTOs/ProductDto.cs index e167222e3..775f931cf 100644 --- a/src/Application/Features/Products/DTOs/ProductDto.cs +++ b/src/Application/Features/Products/DTOs/ProductDto.cs @@ -8,10 +8,10 @@ public class ProductDto:IMapFrom { public int Id { get; set; } - public string Name { get; set; } - public string Description { get; set; } - public string Unit { get; set; } + public string? Name { get; set; } + public string? Description { get; set; } + public string? Unit { get; set; } public decimal Price { get; set; } - public IList Pictures { get; set; } + public IList? Pictures { get; set; } } diff --git a/src/Blazor.Server.UI/Components/Shared/UserMenu.razor.cs b/src/Blazor.Server.UI/Components/Shared/UserMenu.razor.cs index 0ef1d9458..eab1b7281 100644 --- a/src/Blazor.Server.UI/Components/Shared/UserMenu.razor.cs +++ b/src/Blazor.Server.UI/Components/Shared/UserMenu.razor.cs @@ -19,7 +19,7 @@ private Task OnLogout() { nameof(LogoutConfirmation.Color), Color.Error} }; - var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.Small, FullWidth = true }; + var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall, FullWidth = true }; DialogService.Show(L["Logout"], parameters, options); return Task.CompletedTask; } diff --git a/src/Blazor.Server.UI/Pages/Products/Products.razor b/src/Blazor.Server.UI/Pages/Products/Products.razor index 74bfa80e6..bf30f880c 100644 --- a/src/Blazor.Server.UI/Pages/Products/Products.razor +++ b/src/Blazor.Server.UI/Pages/Products/Products.razor @@ -1,6 +1,9 @@ @page "/pages/products" +@using Blazor.Server.UI.Components.Dialogs +@using CleanArchitecture.Blazor.Application.Features.Products.Commands.Delete @using CleanArchitecture.Blazor.Application.Features.Products.DTOs @using CleanArchitecture.Blazor.Application.Features.Products.Queries.Pagination +@using CleanArchitecture.Blazor.Application.Features.Products.Commands.AddEdit @inject IStringLocalizer L @Title +
- Product - @L["Refresh"] - @if (_canCreate) - { - @L["Create"] - } - @if (_canDelete) - { - @L["Delete"] - } - @if (_canImport) - { - @L["Import Data"] - } - @if (_canExport) - { - @L["Export Data"] - } -
+ Product + @L["Refresh"] + @if (_canCreate) + { + @L["Create"] + } + @if (_canDelete) + { + @L["Delete"] + } + @if (_canImport) + { +