From eec13fa333d6cfb71abd0aac7b8cfcf833b70e88 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Mon, 12 Oct 2020 10:57:09 +0200 Subject: [PATCH 01/40] New extension method to reset an object properties --- .../System/AbpObjectExtensions.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs b/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs index 958398e0c5..9542383983 100644 --- a/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs +++ b/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Globalization; using System.Linq; +using System.Reflection; namespace System { @@ -105,5 +106,36 @@ namespace System return obj; } + + /// + /// Resets all fields of a given object to their default values. + /// + /// Type of the object + /// An object + /// Alternative custom method to reset the values. + /// Returns the original object. + public static T ResetState(this T obj, Action customReset = null) + { + if (customReset != null) + { + customReset(obj); + } + else + { + var properties = typeof(T).GetProperties(); + + foreach (var property in properties) + { + if (!property.CanWrite) + { + continue; + } + + property.SetValue(obj, null); + } + } + + return obj; + } } } From ed9f3f5c0a0d15f328d01dc05d63103bf455948d Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Mon, 12 Oct 2020 11:28:04 +0200 Subject: [PATCH 02/40] Reset model state when AbpCrudPageBase is opened --- framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs index 8723209c53..44fc2403f0 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs @@ -318,7 +318,8 @@ namespace Volo.Abp.BlazoriseUI { await CheckCreatePolicyAsync(); - NewEntity = new TCreateViewModel(); + NewEntity.ResetState(); + CreateModal.Show(); } From 56fce17d70733f23510b0435b2780272b5ab5141 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Mon, 12 Oct 2020 11:28:10 +0200 Subject: [PATCH 03/40] Blazorise client side validation for RoleManagementBase --- .../Pages/Identity/RoleManagement.razor | 28 +++++++++++-------- .../Pages/Identity/RoleManagement.razor.cs | 15 ++++++++++ 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor index 781756dd71..a45aafbe59 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor @@ -78,26 +78,32 @@ { - + @L["NewRole"] - + + + + @L["DisplayName:RoleName"] + + + + + + + - @L["DisplayName:RoleName"] - - - - @L["DisplayName:IsDefault"] - @L["DisplayName:IsPublic"] + @L["DisplayName:IsDefault"] + @L["DisplayName:IsPublic"] - + - + @@ -107,7 +113,7 @@ { - + @L["Edit"] diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs index bccf3ddbd8..591707355b 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Blazorise; using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Dtos; using Volo.Abp.BlazoriseUI; @@ -17,6 +18,8 @@ namespace Volo.Abp.Identity.Blazor.Pages.Identity protected bool ShouldShowEntityActions { get; set; } + protected Validations ValidationsRef { get; set; } + public RoleManagementBase() { ObjectMapperContext = typeof(AbpIdentityBlazorModule); @@ -38,5 +41,17 @@ namespace Volo.Abp.Identity.Blazor.Pages.Identity HasDeletePermission || HasManagePermissionsPermission; } + + protected virtual Task OnCreateEntityClicked() + { + if ( ValidationsRef.ValidateAll() ) + { + CreateModal.Hide(); + + return CreateEntityAsync(); + } + + return Task.CompletedTask; + } } } From 1a44a268f8787649ae32561ad83b2238468f8aaa Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Wed, 14 Oct 2020 12:11:39 +0200 Subject: [PATCH 04/40] Role and User client side Blazorise validations --- .../Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs | 2 +- .../System/AbpObjectExtensions.cs | 28 ++- .../Pages/Identity/RoleManagement.razor | 49 ++-- .../Pages/Identity/RoleManagement.razor.cs | 38 ++- .../Pages/Identity/UserManagement.razor | 219 ++++++++++++------ .../Pages/Identity/UserManagement.razor.cs | 47 +++- 6 files changed, 267 insertions(+), 116 deletions(-) diff --git a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs index 44fc2403f0..7616447552 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs @@ -335,7 +335,7 @@ namespace Volo.Abp.BlazoriseUI var entityDto = await AppService.GetAsync(id); EditingEntityId = id; - EditingEntity = MapToEditingEntity(entityDto); + EditingEntity.ApplyState(MapToEditingEntity(entityDto)); EditModal.Show(); } diff --git a/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs b/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs index 9542383983..a173c2a95d 100644 --- a/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs +++ b/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs @@ -110,7 +110,7 @@ namespace System /// /// Resets all fields of a given object to their default values. /// - /// Type of the object + /// Type of the object. /// An object /// Alternative custom method to reset the values. /// Returns the original object. @@ -137,5 +137,31 @@ namespace System return obj; } + + /// + /// Applies all fields of a given object from it's counterpart. + /// + /// Type of the object. + /// An object. + /// An object from which we copy the values. + /// Returns the original object. + public static T ApplyState(this T obj, T other) + { + // This code is not production ready and it should be removed once + // the proper validation in Blazorise is done! + var properties = typeof(T).GetProperties(); + + foreach (var property in properties) + { + if (!property.CanWrite) + { + continue; + } + + property.SetValue(obj, typeof(T).GetProperty(property.Name).GetValue(other)); + } + + return obj; + } } } diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor index a45aafbe59..033a5293d8 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor @@ -1,5 +1,5 @@ @page "/identity/roles" -@attribute [Authorize(IdentityPermissions.Roles.Default)] +@attribute [Authorize( IdentityPermissions.Roles.Default )] @using Volo.Abp.Identity @using Microsoft.AspNetCore.Authorization @using Microsoft.Extensions.Localization @@ -7,14 +7,13 @@ @using Volo.Abp.PermissionManagement.Blazor.Components @inherits RoleManagementBase @inject IStringLocalizer L - @* ************************* PAGE HEADER ************************* *@

@L["Roles"]

- @if (HasCreatePermission) + @if ( HasCreatePermission ) { @@ -31,7 +30,7 @@ ShowPager="true" PageSize="PageSize"> - @if (ShouldShowEntityActions) + @if ( ShouldShowEntityActions ) { @@ -40,15 +39,15 @@ @L["Actions"] - @if (HasUpdatePermission) + @if ( HasUpdatePermission ) { @L["Edit"] } - @if (HasManagePermissionsPermission) + @if ( HasManagePermissionsPermission ) { @L["Permissions"] } - @if (HasDeletePermission) + @if ( HasDeletePermission ) { @L["Delete"] } @@ -60,11 +59,11 @@ @(context.As().Name) - @if (context.As().IsDefault) + @if ( context.As().IsDefault ) { @L["DisplayName:IsDefault"] } - @if (context.As().IsPublic) + @if ( context.As().IsPublic ) { @L["DisplayName:IsPublic"] } @@ -74,7 +73,7 @@ @* ************************* CREATE MODAL ************************* *@ -@if (HasCreatePermission) +@if ( HasCreatePermission ) { @@ -84,7 +83,7 @@
- + @L["DisplayName:RoleName"] @@ -103,13 +102,13 @@ - +
} @* ************************* EDIT MODAL ************************* *@ -@if (HasUpdatePermission) +@if ( HasUpdatePermission ) { @@ -119,17 +118,23 @@ - + + + + @L["DisplayName:RoleName"] + + + + + + + - @L["DisplayName:RoleName"] - - - - @L["DisplayName:IsDefault"] - @L["DisplayName:IsPublic"] + @L["DisplayName:IsDefault"] + @L["DisplayName:IsPublic"] - + @@ -139,4 +144,4 @@ } - + diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs index 591707355b..1645f1f2a3 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs @@ -8,7 +8,7 @@ using Volo.Abp.PermissionManagement.Blazor.Components; namespace Volo.Abp.Identity.Blazor.Pages.Identity { - public abstract class RoleManagementBase : AbpCrudPageBase + public abstract class RoleManagementBase : AbpCrudPageBase { protected const string PermissionProviderName = "R"; @@ -18,7 +18,9 @@ namespace Volo.Abp.Identity.Blazor.Pages.Identity protected bool ShouldShowEntityActions { get; set; } - protected Validations ValidationsRef { get; set; } + protected Validations CreateValidationsRef { get; set; } + + protected Validations EditValidationsRef { get; set; } public RoleManagementBase() { @@ -42,13 +44,39 @@ namespace Volo.Abp.Identity.Blazor.Pages.Identity HasManagePermissionsPermission; } - protected virtual Task OnCreateEntityClicked() + protected override Task OpenCreateModalAsync() + { + CreateValidationsRef.ClearAll(); + + return base.OpenCreateModalAsync(); + } + + protected override Task OpenEditModalAsync(Guid id) + { + EditValidationsRef.ClearAll(); + + return base.OpenEditModalAsync(id); + } + + protected override Task CreateEntityAsync() { - if ( ValidationsRef.ValidateAll() ) + if (CreateValidationsRef.ValidateAll()) { CreateModal.Hide(); - return CreateEntityAsync(); + return base.CreateEntityAsync(); + } + + return Task.CompletedTask; + } + + protected override Task UpdateEntityAsync() + { + if (EditValidationsRef.ValidateAll()) + { + EditModal.Hide(); + + return base.UpdateEntityAsync(); } return Task.CompletedTask; diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor index bfbd2150c6..e3a24323e8 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor @@ -1,19 +1,18 @@ @page "/identity/users" -@attribute [Authorize(IdentityPermissions.Users.Default)] +@attribute [Authorize( IdentityPermissions.Users.Default )] @using Microsoft.AspNetCore.Authorization @using Microsoft.Extensions.Localization @using Volo.Abp.Identity.Localization @using Volo.Abp.PermissionManagement.Blazor.Components @inherits UserManagementBase @inject IStringLocalizer L - @* ************************* PAGE HEADER ************************* *@

@L["Users"]

- @if (HasCreatePermission) + @if ( HasCreatePermission ) { @@ -30,7 +29,7 @@ ShowPager="true" PageSize="PageSize"> - @if (ShouldShowEntityActions) + @if ( ShouldShowEntityActions ) { @@ -39,17 +38,17 @@ @L["Actions"] - @if (HasUpdatePermission) + @if ( HasUpdatePermission ) { @L["Edit"] } - @if (HasManagePermissionsPermission) + @if ( HasManagePermissionsPermission ) { @L["Permissions"] } - @if (HasDeletePermission) + @if ( HasDeletePermission ) { - + @L["Delete"] } @@ -76,17 +75,17 @@ @* ************************* CREATE MODAL ************************* *@ -@if (HasCreatePermission) +@if ( HasCreatePermission ) { - + @L["NewUser"] - + - + @L["UserInformations"] @@ -94,71 +93,107 @@ + + + @L["DisplayName:UserName"] + + + + + + + + + + @L["DisplayName:Name"] + + + + + + + + + + @L["DisplayName:Surname"] + + + + + + + + + + @L["DisplayName:Password"] + + + + + + + + + + @L["DisplayName:Email"] + + + + + + + + + + @L["DisplayName:PhoneNumber"] + + + + + + + - @L["DisplayName:UserName"] - - - - @L["DisplayName:Name"] - - - - @L["DisplayName:Surname"] - - - - @L["DisplayName:Password"] - - - - @L["DisplayName:Email"] - - - - @L["DisplayName:PhoneNumber"] - - - - @L["DisplayName:LockoutEnabled"] + @L["DisplayName:LockoutEnabled"] - @if (NewUserRoles != null) + @if ( NewUserRoles != null ) { - @foreach (var role in NewUserRoles) + @foreach ( var role in NewUserRoles ) { - - @role.Name + + @role.Name } } - + - + } @* ************************* EDIT MODAL ************************* *@ -@if (HasUpdatePermission) +@if ( HasUpdatePermission ) { - + @L["Edit"] - + - - + + @@ -168,55 +203,89 @@ - @L["DisplayName:UserName"] - - - - @L["DisplayName:Name"] - + + @L["DisplayName:UserName"] + + + + + + + + + @L["DisplayName:Name"] + + + + + + + @L["DisplayName:Surname"] - - - - @L["DisplayName:Password"] - - - - @L["DisplayName:Email"] - - - - @L["DisplayName:PhoneNumber"] - + + + + + + + + @L["DisplayName:Password"] + + + + + + + + + + @L["DisplayName:Email"] + + + + + + + + + + @L["DisplayName:PhoneNumber"] + + + + + + + - @L["DisplayName:LockoutEnabled"] + @L["DisplayName:LockoutEnabled"] - @if (EditUserRoles != null) + @if ( EditUserRoles != null ) { - @foreach (var role in EditUserRoles) + @foreach ( var role in EditUserRoles ) { - - @role.Name + + @role.Name } } - + - + } - \ No newline at end of file + \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs index a488cbd358..dc51f59dc1 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Blazorise; using Microsoft.AspNetCore.Authorization; using Volo.Abp.BlazoriseUI; using Volo.Abp.PermissionManagement.Blazor.Components; @@ -29,6 +30,10 @@ namespace Volo.Abp.Identity.Blazor.Pages.Identity protected string EditModalSelectedTab = DefaultSelectedTab; + protected Validations CreateValidationsRef { get; set; } + + protected Validations EditValidationsRef { get; set; } + public UserManagementBase() { ObjectMapperContext = typeof(AbpIdentityBlazorModule); @@ -60,44 +65,62 @@ namespace Volo.Abp.Identity.Blazor.Pages.Identity protected override Task OpenCreateModalAsync() { + CreateValidationsRef.ClearAll(); + CreateModalSelectedTab = DefaultSelectedTab; NewUserRoles = Roles.Select(x => new AssignedRoleViewModel - { - Name = x.Name, - IsAssigned = x.IsDefault - }).ToArray(); + { + Name = x.Name, + IsAssigned = x.IsDefault + }).ToArray(); return base.OpenCreateModalAsync(); } protected override Task CreateEntityAsync() { - NewEntity.RoleNames = NewUserRoles.Where(x => x.IsAssigned).Select(x => x.Name).ToArray(); + if (CreateValidationsRef.ValidateAll()) + { + CreateModal.Hide(); + + NewEntity.RoleNames = NewUserRoles.Where(x => x.IsAssigned).Select(x => x.Name).ToArray(); - return base.CreateEntityAsync(); + return base.CreateEntityAsync(); + } + + return Task.CompletedTask; } protected override async Task OpenEditModalAsync(Guid id) { + EditValidationsRef.ClearAll(); + EditModalSelectedTab = DefaultSelectedTab; var userRoleNames = (await AppService.GetRolesAsync(id)).Items.Select(r => r.Name).ToList(); EditUserRoles = Roles.Select(x => new AssignedRoleViewModel - { - Name = x.Name, - IsAssigned = userRoleNames.Contains(x.Name) - }).ToArray(); + { + Name = x.Name, + IsAssigned = userRoleNames.Contains(x.Name) + }).ToArray(); await base.OpenEditModalAsync(id); } protected override Task UpdateEntityAsync() { - EditingEntity.RoleNames = EditUserRoles.Where(x => x.IsAssigned).Select(x => x.Name).ToArray(); + if (EditValidationsRef.ValidateAll()) + { + EditModal.Hide(); + + EditingEntity.RoleNames = EditUserRoles.Where(x => x.IsAssigned).Select(x => x.Name).ToArray(); + + return base.UpdateEntityAsync(); + } - return base.UpdateEntityAsync(); + return Task.CompletedTask; } } From f4e568befe16efbf99123e558d24e0c11638a0e8 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Wed, 21 Oct 2020 12:11:11 +0200 Subject: [PATCH 05/40] WIP message arguments localization --- ...dentityBlazorMessageLocalizerExtensions.cs | 15 ++++++ .../Pages/Identity/RoleManagement.razor | 5 +- .../Identity/AbpIdentityDomainSharedModule.cs | 5 +- .../Identity/AbpIdentityMessageLocalizer.cs | 46 +++++++++++++++++++ 4 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityMessageLocalizer.cs diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs new file mode 100644 index 0000000000..480b17ac14 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Linq; +using Blazorise; + +namespace Volo.Abp.Identity.Blazor +{ + public static class AbpIdentityBlazorMessageLocalizerExtensions + { + public static IEnumerable Localize(this AbpIdentityMessageLocalizer abpIdentityMessageLocalizer, + ValidationMessageLocalizerEventArgs eventArgs) + { + return abpIdentityMessageLocalizer.Localize(eventArgs.Messages?.Select(m => (m.Message, m.MessageArguments)))?.ToArray(); + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor index 033a5293d8..e850e5c87a 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor @@ -7,6 +7,7 @@ @using Volo.Abp.PermissionManagement.Blazor.Components @inherits RoleManagementBase @inject IStringLocalizer L +@inject AbpIdentityMessageLocalizer ML @* ************************* PAGE HEADER ************************* *@ @@ -84,7 +85,7 @@ - + @L["DisplayName:RoleName"] @@ -120,7 +121,7 @@ - + @L["DisplayName:RoleName"] diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs index e14ce5e3f4..5a63d52b24 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs @@ -1,4 +1,5 @@ -using Volo.Abp.Features; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Features; using Volo.Abp.Identity.Localization; using Volo.Abp.Localization; using Volo.Abp.Localization.ExceptionHandling; @@ -37,6 +38,8 @@ namespace Volo.Abp.Identity { options.MapCodeNamespace("Volo.Abp.Identity", typeof(IdentityResource)); }); + + context.Services.AddSingleton(typeof(AbpIdentityMessageLocalizer<>), typeof(AbpIdentityMessageLocalizer<>)); } } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityMessageLocalizer.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityMessageLocalizer.cs new file mode 100644 index 0000000000..ff2c50ee83 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityMessageLocalizer.cs @@ -0,0 +1,46 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Localization; + +namespace Volo.Abp.Identity +{ + public class AbpIdentityMessageLocalizer + { + private readonly IStringLocalizer stringLocalizer; + + public AbpIdentityMessageLocalizer(IStringLocalizer stringLocalizer) + { + this.stringLocalizer = stringLocalizer; + } + + public virtual string Localize(string format, params string[] arguments) + { + try + { + return arguments?.Length > 0 + ? string.Format(stringLocalizer[format], LocalizeArguments(arguments)?.ToArray()) + : stringLocalizer[format]; + } + catch + { + return stringLocalizer[format]; + } + } + + public virtual IEnumerable Localize(IEnumerable<(string format, string[] arguments)> messages) + { + return messages?.Select(m => Localize(m.format, m.arguments)); + } + + protected virtual IEnumerable LocalizeArguments(IEnumerable arguments) + { + foreach (var argument in arguments) + { + yield return stringLocalizer[$"DisplayName:{argument}"] + ?? stringLocalizer[argument] + ?? argument; + } + } + } +} From eb2be1d672335f188ffac94f2a715c2588cddc49 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Thu, 22 Oct 2020 11:25:57 +0200 Subject: [PATCH 06/40] Optimized usage of Localize method --- .../Pages/Identity/RoleManagement.razor | 4 +- .../Pages/Identity/UserManagement.razor | 47 ++++++++++--------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor index e850e5c87a..daba629c3e 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor @@ -85,7 +85,7 @@ - + @L["DisplayName:RoleName"] @@ -121,7 +121,7 @@ - + @L["DisplayName:RoleName"] diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor index e3a24323e8..f0fe98c61c 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor @@ -6,6 +6,7 @@ @using Volo.Abp.PermissionManagement.Blazor.Components @inherits UserManagementBase @inject IStringLocalizer L +@inject AbpIdentityMessageLocalizer ML @* ************************* PAGE HEADER ************************* *@ @@ -79,7 +80,7 @@ { - + @L["NewUser"] @@ -93,7 +94,7 @@ - + @L["DisplayName:UserName"] @@ -103,7 +104,7 @@ - + @L["DisplayName:Name"] @@ -113,7 +114,7 @@ - + @L["DisplayName:Surname"] @@ -133,7 +134,7 @@ - + @L["DisplayName:Email"] @@ -143,7 +144,7 @@ - + @L["DisplayName:PhoneNumber"] @@ -186,7 +187,7 @@ { - + @L["Edit"] @@ -202,17 +203,17 @@ - - + + @L["DisplayName:UserName"] - - - + + + @L["DisplayName:Name"] @@ -222,14 +223,16 @@ - - @L["DisplayName:Surname"] - - - - - - + + + @L["DisplayName:Surname"] + + + + + + + @L["DisplayName:Password"] @@ -240,7 +243,7 @@ - + @L["DisplayName:Email"] @@ -250,7 +253,7 @@ - + @L["DisplayName:PhoneNumber"] From f24a7d18e18a35cc7ccf1a7e7ebae4d6b0700c5f Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Thu, 22 Oct 2020 11:26:33 +0200 Subject: [PATCH 07/40] Fixed blazor app element for .Net 5 --- .../MyProjectNameBlazorModule.cs | 2 +- .../src/MyCompanyName.MyProjectName.Blazor/wwwroot/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs index 7554ae8aca..279fe57114 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs @@ -77,7 +77,7 @@ namespace MyCompanyName.MyProjectName.Blazor private static void ConfigureUI(WebAssemblyHostBuilder builder) { - builder.RootComponents.Add("app"); + builder.RootComponents.Add("#app"); } private static void ConfigureHttpClient(ServiceConfigurationContext context, IWebAssemblyHostEnvironment environment) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/index.html b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/index.html index 4566bec3c1..576e00f716 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/index.html +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/index.html @@ -17,7 +17,7 @@ - Loading... +
Loading...
From 38495de1812f66d0151d12f20e97e51961a5ddca Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Thu, 22 Oct 2020 11:27:55 +0200 Subject: [PATCH 08/40] Added editorconfig for identity/module solution --- modules/identity/.editorconfig | 131 +++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 modules/identity/.editorconfig diff --git a/modules/identity/.editorconfig b/modules/identity/.editorconfig new file mode 100644 index 0000000000..9f20b90112 --- /dev/null +++ b/modules/identity/.editorconfig @@ -0,0 +1,131 @@ +# Rules in this file were initially inferred by Visual Studio IntelliCode from the D:\Projects\Volosoft\abp\framework codebase based on best match to current usage at 2.10.2020. +# You can modify the rules from these initially generated values to suit your own policies +# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference +[*.cs] + + +#Core editorconfig formatting - indentation + +#use soft tabs (spaces) for indentation +indent_style = space + +#Formatting - indentation options + +#indent switch case contents. +csharp_indent_case_contents = true +#indent switch labels +csharp_indent_switch_labels = true + +#Formatting - new line options + +#place catch statements on a new line +csharp_new_line_before_catch = true +#place else statements on a new line +csharp_new_line_before_else = true +#require members of object intializers to be on separate lines +csharp_new_line_before_members_in_object_initializers = true +#require braces to be on a new line for accessors, methods, lambdas, object_collection_array_initializers, control_blocks, types, and properties (also known as "Allman" style) +csharp_new_line_before_open_brace = accessors, methods, lambdas, object_collection_array_initializers, control_blocks, types, properties + +#Formatting - organize using options + +#sort System.* using directives alphabetically, and place them before other usings +dotnet_sort_system_directives_first = true + +#Formatting - spacing options + +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +#Formatting - wrapping options + +#leave code block on single line +csharp_preserve_single_line_blocks = true + +#Style - Code block preferences + +#prefer curly braces even for one line of code +csharp_prefer_braces = true:suggestion + +#Style - expression bodied member options + +#prefer block bodies for constructors +csharp_style_expression_bodied_constructors = false:suggestion +#prefer block bodies for methods +csharp_style_expression_bodied_methods = false:suggestion +#prefer expression-bodied members for properties +csharp_style_expression_bodied_properties = true:suggestion + +#Style - expression level options + +#prefer out variables to be declared inline in the argument list of a method call when possible +csharp_style_inlined_variable_declaration = true:suggestion +#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them +dotnet_style_predefined_type_for_member_access = true:suggestion + +#Style - Expression-level preferences + +#prefer default over default(T) +csharp_prefer_simple_default_expression = true:suggestion +#prefer objects to be initialized using object initializers when possible +dotnet_style_object_initializer = true:suggestion +#prefer inferred tuple element names +dotnet_style_prefer_inferred_tuple_names = true:suggestion + +#Style - implicit and explicit types + +#prefer var over explicit type in all cases, unless overridden by another code style rule +csharp_style_var_elsewhere = true:suggestion +#prefer var is used to declare variables with built-in system types such as int +csharp_style_var_for_built_in_types = true:suggestion +#prefer var when the type is already mentioned on the right-hand side of a declaration expression +csharp_style_var_when_type_is_apparent = true:suggestion + +#Style - language keyword and framework type options + +#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion + +#Style - modifier options + +#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods. +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion + +#Style - Modifier preferences + +#when this rule is set to a list of modifiers, prefer the specified ordering. +csharp_preferred_modifier_order = public,protected,private,virtual,async,static,override,readonly,abstract:suggestion + +#Style - Pattern matching + +#prefer pattern matching instead of is expression with type casts +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion + +#Style - qualification options + +#prefer fields not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_field = false:suggestion +#prefer methods not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_method = false:suggestion +#prefer properties not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_property = false:suggestion From d6fce9d9d65e474d0412174cabd113667f5fbdb4 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Thu, 22 Oct 2020 11:36:53 +0200 Subject: [PATCH 09/40] Update Blazorise 0.9.2-preview12 --- .../src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj | 4 ++-- .../MyCompanyName.MyProjectName.Blazor.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj b/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj index e696a2f915..2e67a2b182 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj +++ b/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj index d1bec5d5d3..31f9899115 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj @@ -6,8 +6,8 @@ - - + + From 1d84ce4f8af7b13c9bf80005bbacf3a1adb94de4 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Thu, 22 Oct 2020 11:43:21 +0200 Subject: [PATCH 10/40] Fix Password localization --- .../Pages/Identity/UserManagement.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor index f0fe98c61c..03a8b68fa7 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor @@ -124,7 +124,7 @@
- + @L["DisplayName:Password"] @@ -233,7 +233,7 @@ - + @L["DisplayName:Password"] From 167aae0ec520d2e1bae76fd025f4476ab9e8415e Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Fri, 23 Oct 2020 10:05:34 +0200 Subject: [PATCH 11/40] Fixed formating --- .../Pages/Identity/RoleManagement.razor | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor index daba629c3e..4246d2dd76 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor @@ -11,10 +11,10 @@ @* ************************* PAGE HEADER ************************* *@ -

@L["Roles"]

+ @L["Roles"]
- @if ( HasCreatePermission ) + @if (HasCreatePermission) { @@ -31,7 +31,7 @@ ShowPager="true" PageSize="PageSize"> - @if ( ShouldShowEntityActions ) + @if (ShouldShowEntityActions) { @@ -40,15 +40,15 @@ @L["Actions"] - @if ( HasUpdatePermission ) + @if (HasUpdatePermission) { @L["Edit"] } - @if ( HasManagePermissionsPermission ) + @if (HasManagePermissionsPermission) { @L["Permissions"] } - @if ( HasDeletePermission ) + @if (HasDeletePermission) { @L["Delete"] } @@ -60,11 +60,11 @@ @(context.As().Name) - @if ( context.As().IsDefault ) + @if (context.As().IsDefault) { @L["DisplayName:IsDefault"] } - @if ( context.As().IsPublic ) + @if (context.As().IsPublic) { @L["DisplayName:IsPublic"] } @@ -74,7 +74,7 @@ @* ************************* CREATE MODAL ************************* *@ -@if ( HasCreatePermission ) +@if (HasCreatePermission) { @@ -109,7 +109,7 @@ } @* ************************* EDIT MODAL ************************* *@ -@if ( HasUpdatePermission ) +@if (HasUpdatePermission) { From 157d7b1d6e732bd4f6beb2b8ba2485c3e85b7f37 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Fri, 23 Oct 2020 11:06:07 +0200 Subject: [PATCH 12/40] Use ObjectMapper for NewEntity and EditingEntity --- .../src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs index 7616447552..c6491f1ec1 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs @@ -318,7 +318,11 @@ namespace Volo.Abp.BlazoriseUI { await CheckCreatePolicyAsync(); - NewEntity.ResetState(); + ObjectMapper.Map(new TCreateViewModel(), NewEntity); + + // Mapper will not notify Blazor that binded values are changed + // so we need to notify it manually by calling StateHasChanged + await InvokeAsync(() => StateHasChanged()); CreateModal.Show(); } @@ -334,8 +338,12 @@ namespace Volo.Abp.BlazoriseUI await CheckUpdatePolicyAsync(); var entityDto = await AppService.GetAsync(id); + EditingEntityId = id; - EditingEntity.ApplyState(MapToEditingEntity(entityDto)); + ObjectMapper.Map(entityDto, EditingEntity); + + await InvokeAsync(() => StateHasChanged()); + EditModal.Show(); } @@ -379,8 +387,6 @@ namespace Volo.Abp.BlazoriseUI CreateModal.Hide(); } - - protected virtual async Task UpdateEntityAsync() { await CheckUpdatePolicyAsync(); From b5986115a2ac737ce7bcc90ef9839d5585a639d2 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Fri, 23 Oct 2020 11:13:20 +0200 Subject: [PATCH 13/40] Remove explicit Auto validation mode --- .../Pages/Identity/RoleManagement.razor | 22 +++++++++---------- .../Pages/Identity/UserManagement.razor | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor index 4246d2dd76..ef59e969fd 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor @@ -14,7 +14,7 @@ @L["Roles"] - @if (HasCreatePermission) + @if ( HasCreatePermission ) { @@ -31,7 +31,7 @@ ShowPager="true" PageSize="PageSize"> - @if (ShouldShowEntityActions) + @if ( ShouldShowEntityActions ) { @@ -40,15 +40,15 @@ @L["Actions"] - @if (HasUpdatePermission) + @if ( HasUpdatePermission ) { @L["Edit"] } - @if (HasManagePermissionsPermission) + @if ( HasManagePermissionsPermission ) { @L["Permissions"] } - @if (HasDeletePermission) + @if ( HasDeletePermission ) { @L["Delete"] } @@ -60,11 +60,11 @@ @(context.As().Name) - @if (context.As().IsDefault) + @if ( context.As().IsDefault ) { @L["DisplayName:IsDefault"] } - @if (context.As().IsPublic) + @if ( context.As().IsPublic ) { @L["DisplayName:IsPublic"] } @@ -74,7 +74,7 @@ @* ************************* CREATE MODAL ************************* *@ -@if (HasCreatePermission) +@if ( HasCreatePermission ) { @@ -84,7 +84,7 @@ - + @L["DisplayName:RoleName"] @@ -109,7 +109,7 @@ } @* ************************* EDIT MODAL ************************* *@ -@if (HasUpdatePermission) +@if ( HasUpdatePermission ) { @@ -119,7 +119,7 @@ - + diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor index 03a8b68fa7..0ed1fd3d5a 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor @@ -86,7 +86,7 @@ - + @L["UserInformations"] @@ -193,7 +193,7 @@ - + From e6b9253614c883ae19be2039804d5622e372214b Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Fri, 23 Oct 2020 11:25:09 +0200 Subject: [PATCH 14/40] Removed AbpIdentityMessageLocalizer --- ...dentityBlazorMessageLocalizerExtensions.cs | 35 ++++++++++++-- .../Pages/Identity/RoleManagement.razor | 5 +- .../Pages/Identity/UserManagement.razor | 25 +++++----- .../Identity/AbpIdentityDomainSharedModule.cs | 2 - .../Identity/AbpIdentityMessageLocalizer.cs | 46 ------------------- 5 files changed, 46 insertions(+), 67 deletions(-) delete mode 100644 modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityMessageLocalizer.cs diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs index 480b17ac14..bb5214ac63 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs @@ -1,15 +1,44 @@ using System.Collections.Generic; using System.Linq; using Blazorise; +using Microsoft.Extensions.Localization; namespace Volo.Abp.Identity.Blazor { public static class AbpIdentityBlazorMessageLocalizerExtensions { - public static IEnumerable Localize(this AbpIdentityMessageLocalizer abpIdentityMessageLocalizer, - ValidationMessageLocalizerEventArgs eventArgs) + public static IEnumerable Localize(this IStringLocalizer stringLocalizer, ValidationMessageLocalizerEventArgs eventArgs) { - return abpIdentityMessageLocalizer.Localize(eventArgs.Messages?.Select(m => (m.Message, m.MessageArguments)))?.ToArray(); + return LocalizeMessages(stringLocalizer, eventArgs.Messages?.Select(m => (m.Message, m.MessageArguments)))?.ToArray(); + } + + private static IEnumerable LocalizeMessages(IStringLocalizer stringLocalizer, IEnumerable<(string format, string[] arguments)> messages) + { + return messages?.Select(m => LocalizeMessage(stringLocalizer, m.format, m.arguments)); + } + + private static string LocalizeMessage(IStringLocalizer stringLocalizer, string format, params string[] arguments) + { + try + { + return arguments?.Length > 0 + ? string.Format(stringLocalizer[format], LocalizeMessageArguments(stringLocalizer, arguments)?.ToArray()) + : stringLocalizer[format]; + } + catch + { + return stringLocalizer[format]; + } + } + + private static IEnumerable LocalizeMessageArguments(IStringLocalizer stringLocalizer, IEnumerable arguments) + { + foreach (var argument in arguments) + { + yield return stringLocalizer[$"DisplayName:{argument}"] + ?? stringLocalizer[argument] + ?? argument; + } } } } diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor index ef59e969fd..ac40aca83d 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor @@ -7,7 +7,6 @@ @using Volo.Abp.PermissionManagement.Blazor.Components @inherits RoleManagementBase @inject IStringLocalizer L -@inject AbpIdentityMessageLocalizer ML @* ************************* PAGE HEADER ************************* *@ @@ -85,7 +84,7 @@ - + @L["DisplayName:RoleName"] @@ -121,7 +120,7 @@ - + @L["DisplayName:RoleName"] diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor index 0ed1fd3d5a..6f33181f8a 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor @@ -6,7 +6,6 @@ @using Volo.Abp.PermissionManagement.Blazor.Components @inherits UserManagementBase @inject IStringLocalizer L -@inject AbpIdentityMessageLocalizer ML @* ************************* PAGE HEADER ************************* *@ @@ -94,7 +93,7 @@ - + @L["DisplayName:UserName"] @@ -104,7 +103,7 @@ - + @L["DisplayName:Name"] @@ -114,7 +113,7 @@ - + @L["DisplayName:Surname"] @@ -124,7 +123,7 @@ - + @L["DisplayName:Password"] @@ -134,7 +133,7 @@ - + @L["DisplayName:Email"] @@ -144,7 +143,7 @@ - + @L["DisplayName:PhoneNumber"] @@ -203,7 +202,7 @@ - + @L["DisplayName:UserName"] @@ -213,7 +212,7 @@ - + @L["DisplayName:Name"] @@ -223,7 +222,7 @@ - + @L["DisplayName:Surname"] @@ -233,7 +232,7 @@ - + @L["DisplayName:Password"] @@ -243,7 +242,7 @@ - + @L["DisplayName:Email"] @@ -253,7 +252,7 @@ - + @L["DisplayName:PhoneNumber"] diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs index 5a63d52b24..80d039b7d7 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityDomainSharedModule.cs @@ -38,8 +38,6 @@ namespace Volo.Abp.Identity { options.MapCodeNamespace("Volo.Abp.Identity", typeof(IdentityResource)); }); - - context.Services.AddSingleton(typeof(AbpIdentityMessageLocalizer<>), typeof(AbpIdentityMessageLocalizer<>)); } } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityMessageLocalizer.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityMessageLocalizer.cs deleted file mode 100644 index ff2c50ee83..0000000000 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/AbpIdentityMessageLocalizer.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Extensions.Localization; - -namespace Volo.Abp.Identity -{ - public class AbpIdentityMessageLocalizer - { - private readonly IStringLocalizer stringLocalizer; - - public AbpIdentityMessageLocalizer(IStringLocalizer stringLocalizer) - { - this.stringLocalizer = stringLocalizer; - } - - public virtual string Localize(string format, params string[] arguments) - { - try - { - return arguments?.Length > 0 - ? string.Format(stringLocalizer[format], LocalizeArguments(arguments)?.ToArray()) - : stringLocalizer[format]; - } - catch - { - return stringLocalizer[format]; - } - } - - public virtual IEnumerable Localize(IEnumerable<(string format, string[] arguments)> messages) - { - return messages?.Select(m => Localize(m.format, m.arguments)); - } - - protected virtual IEnumerable LocalizeArguments(IEnumerable arguments) - { - foreach (var argument in arguments) - { - yield return stringLocalizer[$"DisplayName:{argument}"] - ?? stringLocalizer[argument] - ?? argument; - } - } - } -} From 429e62d06384b274f6887957b5546c4f908b1219 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Tue, 27 Oct 2020 11:34:27 +0100 Subject: [PATCH 15/40] Added more Before and After methods for AbpCrudPageBase --- .../Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs | 94 ++++++++++++++++--- 1 file changed, 82 insertions(+), 12 deletions(-) diff --git a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs index c6491f1ec1..fae7e136ba 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs @@ -184,6 +184,8 @@ namespace Volo.Abp.BlazoriseUI protected TUpdateViewModel EditingEntity; protected Modal CreateModal; protected Modal EditModal; + protected Validations CreateValidationsRef; + protected Validations EditValidationsRef; protected string CreatePolicyName { get; set; } protected string UpdatePolicyName { get; set; } @@ -316,15 +318,31 @@ namespace Volo.Abp.BlazoriseUI protected virtual async Task OpenCreateModalAsync() { + await OnOpeningCreateModalAsync(); + + CreateValidationsRef.ClearAll(); + await CheckCreatePolicyAsync(); - ObjectMapper.Map(new TCreateViewModel(), NewEntity); + NewEntity = new TCreateViewModel(); // Mapper will not notify Blazor that binded values are changed // so we need to notify it manually by calling StateHasChanged await InvokeAsync(() => StateHasChanged()); CreateModal.Show(); + + await OnOpeningCreateModalAsync(); + } + + protected virtual Task OnOpeningCreateModalAsync() + { + return Task.CompletedTask; + } + + protected virtual Task OnOpenedCreateModalAsync() + { + return Task.CompletedTask; } protected virtual Task CloseCreateModalAsync() @@ -335,16 +353,32 @@ namespace Volo.Abp.BlazoriseUI protected virtual async Task OpenEditModalAsync(TKey id) { + await OnOpeningEditModalAsync(id); + + EditValidationsRef.ClearAll(); + await CheckUpdatePolicyAsync(); var entityDto = await AppService.GetAsync(id); EditingEntityId = id; - ObjectMapper.Map(entityDto, EditingEntity); + EditingEntity = MapToEditingEntity(entityDto); await InvokeAsync(() => StateHasChanged()); EditModal.Show(); + + await OnOpenedEditModalAsync(id); + } + + protected virtual Task OnOpeningEditModalAsync(TKey id) + { + return Task.CompletedTask; + } + + protected virtual Task OnOpenedEditModalAsync(TKey id) + { + return Task.CompletedTask; } protected virtual TUpdateViewModel MapToEditingEntity(TGetOutputDto entityDto) @@ -380,20 +414,56 @@ namespace Volo.Abp.BlazoriseUI protected virtual async Task CreateEntityAsync() { - await CheckCreatePolicyAsync(); - var createInput = MapToCreateInput(NewEntity); - await AppService.CreateAsync(createInput); - await GetEntitiesAsync(); - CreateModal.Hide(); + if (CreateValidationsRef.ValidateAll()) + { + await OnCreatingEntityAsync(); + + await CheckCreatePolicyAsync(); + var createInput = MapToCreateInput(NewEntity); + await AppService.CreateAsync(createInput); + await GetEntitiesAsync(); + + await OnCreatedEntityAsync(); + + CreateModal.Hide(); + } + } + + protected virtual Task OnCreatingEntityAsync() + { + return Task.CompletedTask; + } + + protected virtual Task OnCreatedEntityAsync() + { + return Task.CompletedTask; } protected virtual async Task UpdateEntityAsync() { - await CheckUpdatePolicyAsync(); - var updateInput = MapToUpdateInput(EditingEntity); - await AppService.UpdateAsync(EditingEntityId, updateInput); - await GetEntitiesAsync(); - EditModal.Hide(); + if (EditValidationsRef.ValidateAll()) + { + await OnUpdatingEntityAsync(); + + await CheckUpdatePolicyAsync(); + var updateInput = MapToUpdateInput(EditingEntity); + await AppService.UpdateAsync(EditingEntityId, updateInput); + await GetEntitiesAsync(); + + await OnUpdatedEntityAsync(); + + EditModal.Hide(); + } + } + + protected virtual Task OnUpdatingEntityAsync() + { + return Task.CompletedTask; + } + + protected virtual Task OnUpdatedEntityAsync() + { + return Task.CompletedTask; } protected virtual async Task DeleteEntityAsync(TListViewModel entity) From 0aaa5b8c4617af17cfdf44fecff6fcc1740c7443 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Tue, 27 Oct 2020 11:35:00 +0100 Subject: [PATCH 16/40] Updated to Blazorise 0.9.2-preview14 --- .../src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj | 4 ++-- .../MyCompanyName.MyProjectName.Blazor.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj b/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj index 2e67a2b182..1134821b78 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj +++ b/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj index 31f9899115..c9abf32119 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj @@ -6,8 +6,8 @@ - - + + From 6ee17e04c631e2258279bfdf32306c1c95edd058 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Tue, 27 Oct 2020 11:35:57 +0100 Subject: [PATCH 17/40] Most of crud operations moved to base class --- .../Pages/Identity/RoleManagement.razor.cs | 42 ------------------ .../Pages/Identity/UserManagement.razor.cs | 44 +++++-------------- 2 files changed, 12 insertions(+), 74 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs index 1645f1f2a3..a2cb77478f 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs @@ -18,10 +18,6 @@ namespace Volo.Abp.Identity.Blazor.Pages.Identity protected bool ShouldShowEntityActions { get; set; } - protected Validations CreateValidationsRef { get; set; } - - protected Validations EditValidationsRef { get; set; } - public RoleManagementBase() { ObjectMapperContext = typeof(AbpIdentityBlazorModule); @@ -43,43 +39,5 @@ namespace Volo.Abp.Identity.Blazor.Pages.Identity HasDeletePermission || HasManagePermissionsPermission; } - - protected override Task OpenCreateModalAsync() - { - CreateValidationsRef.ClearAll(); - - return base.OpenCreateModalAsync(); - } - - protected override Task OpenEditModalAsync(Guid id) - { - EditValidationsRef.ClearAll(); - - return base.OpenEditModalAsync(id); - } - - protected override Task CreateEntityAsync() - { - if (CreateValidationsRef.ValidateAll()) - { - CreateModal.Hide(); - - return base.CreateEntityAsync(); - } - - return Task.CompletedTask; - } - - protected override Task UpdateEntityAsync() - { - if (EditValidationsRef.ValidateAll()) - { - EditModal.Hide(); - - return base.UpdateEntityAsync(); - } - - return Task.CompletedTask; - } } } diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs index dc51f59dc1..e2773e8334 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs @@ -30,10 +30,6 @@ namespace Volo.Abp.Identity.Blazor.Pages.Identity protected string EditModalSelectedTab = DefaultSelectedTab; - protected Validations CreateValidationsRef { get; set; } - - protected Validations EditValidationsRef { get; set; } - public UserManagementBase() { ObjectMapperContext = typeof(AbpIdentityBlazorModule); @@ -63,10 +59,8 @@ namespace Volo.Abp.Identity.Blazor.Pages.Identity HasManagePermissionsPermission; } - protected override Task OpenCreateModalAsync() + protected override async Task OnOpeningCreateModalAsync() { - CreateValidationsRef.ClearAll(); - CreateModalSelectedTab = DefaultSelectedTab; NewUserRoles = Roles.Select(x => new AssignedRoleViewModel @@ -75,27 +69,19 @@ namespace Volo.Abp.Identity.Blazor.Pages.Identity IsAssigned = x.IsDefault }).ToArray(); - return base.OpenCreateModalAsync(); + await base.OnOpeningCreateModalAsync(); } - protected override Task CreateEntityAsync() + protected override Task OnCreatingEntityAsync() { - if (CreateValidationsRef.ValidateAll()) - { - CreateModal.Hide(); - - NewEntity.RoleNames = NewUserRoles.Where(x => x.IsAssigned).Select(x => x.Name).ToArray(); + // apply roles before saving + NewEntity.RoleNames = NewUserRoles.Where(x => x.IsAssigned).Select(x => x.Name).ToArray(); - return base.CreateEntityAsync(); - } - - return Task.CompletedTask; + return base.OnCreatingEntityAsync(); } - protected override async Task OpenEditModalAsync(Guid id) + protected override async Task OnOpeningEditModalAsync(Guid id) { - EditValidationsRef.ClearAll(); - EditModalSelectedTab = DefaultSelectedTab; var userRoleNames = (await AppService.GetRolesAsync(id)).Items.Select(r => r.Name).ToList(); @@ -106,21 +92,15 @@ namespace Volo.Abp.Identity.Blazor.Pages.Identity IsAssigned = userRoleNames.Contains(x.Name) }).ToArray(); - await base.OpenEditModalAsync(id); + await base.OnOpeningEditModalAsync(id); } - protected override Task UpdateEntityAsync() + protected override Task OnUpdatingEntityAsync() { - if (EditValidationsRef.ValidateAll()) - { - EditModal.Hide(); - - EditingEntity.RoleNames = EditUserRoles.Where(x => x.IsAssigned).Select(x => x.Name).ToArray(); - - return base.UpdateEntityAsync(); - } + // apply roles before saving + EditingEntity.RoleNames = EditUserRoles.Where(x => x.IsAssigned).Select(x => x.Name).ToArray(); - return Task.CompletedTask; + return base.OnUpdatingEntityAsync(); } } From 31e39c1ce36cfdd276fde0ae20b52ab212c0b7e3 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Tue, 27 Oct 2020 11:37:05 +0100 Subject: [PATCH 18/40] Updated Blazorise MessageLocalizer according to latest changes --- .../AbpIdentityBlazorMessageLocalizerExtensions.cs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs index bb5214ac63..18a6242bab 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs @@ -7,21 +7,11 @@ namespace Volo.Abp.Identity.Blazor { public static class AbpIdentityBlazorMessageLocalizerExtensions { - public static IEnumerable Localize(this IStringLocalizer stringLocalizer, ValidationMessageLocalizerEventArgs eventArgs) - { - return LocalizeMessages(stringLocalizer, eventArgs.Messages?.Select(m => (m.Message, m.MessageArguments)))?.ToArray(); - } - - private static IEnumerable LocalizeMessages(IStringLocalizer stringLocalizer, IEnumerable<(string format, string[] arguments)> messages) - { - return messages?.Select(m => LocalizeMessage(stringLocalizer, m.format, m.arguments)); - } - - private static string LocalizeMessage(IStringLocalizer stringLocalizer, string format, params string[] arguments) + public static string Localize(this IStringLocalizer stringLocalizer, string format, IEnumerable arguments) { try { - return arguments?.Length > 0 + return arguments?.Count() > 0 ? string.Format(stringLocalizer[format], LocalizeMessageArguments(stringLocalizer, arguments)?.ToArray()) : stringLocalizer[format]; } From b4f6ef6d03e973c940e222b7e43b3f1d8d37fd23 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Wed, 28 Oct 2020 10:25:32 +0100 Subject: [PATCH 19/40] Implemented UiNotificationService --- .../Themes/Basic/MainLayout.razor | 1 + .../WebAssembly/IUiNotificationService.cs | 11 +-- .../WebAssembly/NullUiNotificationService.cs | 11 +-- .../WebAssembly/UiNotificationEventArgs.cs | 23 ++++++ .../WebAssembly/UiNotificationOptions.cs | 18 +++++ .../WebAssembly/UiNotificationType.cs | 10 +++ .../BlazoriseUiNotificationService.cs | 62 ++++++++++++++-- .../Components/UiNotificationAlert.razor | 1 + .../Components/UiNotificationAlert.razor.cs | 71 +++++++++++++++++++ .../Volo.Abp.BlazoriseUI.csproj | 1 + .../src/Volo.Abp.BlazoriseUI/_Imports.razor | 3 +- .../wwwroot/index.html | 1 + 12 files changed, 195 insertions(+), 18 deletions(-) create mode 100644 framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiNotificationEventArgs.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiNotificationOptions.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiNotificationType.cs create mode 100644 framework/src/Volo.Abp.BlazoriseUI/Components/UiNotificationAlert.razor create mode 100644 framework/src/Volo.Abp.BlazoriseUI/Components/UiNotificationAlert.razor.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/MainLayout.razor b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/MainLayout.razor index 45496a7984..20b96bef45 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/MainLayout.razor +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/MainLayout.razor @@ -22,4 +22,5 @@ @Body + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IUiNotificationService.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IUiNotificationService.cs index a9816dc325..753dad4dcd 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IUiNotificationService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IUiNotificationService.cs @@ -1,12 +1,13 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; namespace Volo.Abp.AspNetCore.Components.WebAssembly { public interface IUiNotificationService { - Task Info(string message); - Task Success(string message); - Task Warn(string message); - Task Error(string message); + Task Info(string message, string title = null, Action options = null); + Task Success(string message, string title = null, Action options = null); + Task Warn(string message, string title = null, Action options = null); + Task Error(string message, string title = null, Action options = null); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/NullUiNotificationService.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/NullUiNotificationService.cs index 85ee10287c..950cca3047 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/NullUiNotificationService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/NullUiNotificationService.cs @@ -1,25 +1,26 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Volo.Abp.DependencyInjection; namespace Volo.Abp.AspNetCore.Components.WebAssembly { public class NullUiNotificationService : IUiNotificationService, ITransientDependency { - public Task Info(string message) + public Task Info(string message, string title = null, Action options = null) { return Task.CompletedTask; } - public Task Success(string message) + public Task Success(string message, string title = null, Action options = null) { return Task.CompletedTask; } - public Task Warn(string message) + public Task Warn(string message, string title = null, Action options = null) { return Task.CompletedTask; } - public Task Error(string message) + public Task Error(string message, string title = null, Action options = null) { return Task.CompletedTask; } diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiNotificationEventArgs.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiNotificationEventArgs.cs new file mode 100644 index 0000000000..402bfb329b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiNotificationEventArgs.cs @@ -0,0 +1,23 @@ +using System; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly +{ + public class UiNotificationEventArgs : EventArgs + { + public UiNotificationEventArgs(UiNotificationType notificationType, string message, string title, UiNotificationOptions options) + { + NotificationType = notificationType; + Message = message; + Title = title; + Options = options; + } + + public UiNotificationType NotificationType { get; set; } + + public string Message { get; } + + public string Title { get; } + + public UiNotificationOptions Options { get; } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiNotificationOptions.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiNotificationOptions.cs new file mode 100644 index 0000000000..2d51716cd9 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiNotificationOptions.cs @@ -0,0 +1,18 @@ +namespace Volo.Abp.AspNetCore.Components.WebAssembly +{ + /// + /// Options to override notification appearance. + /// + public class UiNotificationOptions + { + /// + /// Custom text for the Ok button. + /// + public string OkButtonText { get; set; } + + /// + /// Custom icon for the Ok button. + /// + public object OkButtonIcon { get; set; } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiNotificationType.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiNotificationType.cs new file mode 100644 index 0000000000..78f3a86bb5 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiNotificationType.cs @@ -0,0 +1,10 @@ +namespace Volo.Abp.AspNetCore.Components.WebAssembly +{ + public enum UiNotificationType + { + Info, + Success, + Warning, + Error, + } +} diff --git a/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiNotificationService.cs b/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiNotificationService.cs index 549a1f4d07..d1e6ed6ce2 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiNotificationService.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiNotificationService.cs @@ -1,4 +1,7 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; +using Localization.Resources.AbpUi; +using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.AspNetCore.Components.WebAssembly; @@ -7,34 +10,79 @@ using Volo.Abp.DependencyInjection; namespace Volo.Abp.BlazoriseUI { [Dependency(ReplaceServices = true)] - public class BlazoriseUiNotificationService : IUiNotificationService, ITransientDependency + public class BlazoriseUiNotificationService : IUiNotificationService, IScopedDependency { + /// + /// An event raised after the notification is received. + /// + public event EventHandler NotificationReceived; + + private readonly IStringLocalizer localizer; + public ILogger Logger { get; set; } - public BlazoriseUiNotificationService() + public BlazoriseUiNotificationService( + IStringLocalizer localizer) { + this.localizer = localizer; + Logger = NullLogger.Instance; } - public Task Info(string message) + public Task Info(string message, string title = null, Action options = null) { + var uiNotificationOptions = CreateDefaultOptions(); + options?.Invoke(uiNotificationOptions); + Logger.LogInformation(message); + + NotificationReceived?.Invoke(this, new UiNotificationEventArgs(UiNotificationType.Info, message, title, uiNotificationOptions)); + return Task.CompletedTask; } - public Task Success(string message) + public Task Success(string message, string title = null, Action options = null) { + var uiNotificationOptions = CreateDefaultOptions(); + options?.Invoke(uiNotificationOptions); + + Logger.LogInformation(message); + + NotificationReceived?.Invoke(this, new UiNotificationEventArgs(UiNotificationType.Success, message, title, uiNotificationOptions)); + return Task.CompletedTask; } - public Task Warn(string message) + public Task Warn(string message, string title = null, Action options = null) { + var uiNotificationOptions = CreateDefaultOptions(); + options?.Invoke(uiNotificationOptions); + + Logger.LogWarning(message); + + NotificationReceived?.Invoke(this, new UiNotificationEventArgs(UiNotificationType.Warning, message, title, uiNotificationOptions)); + return Task.CompletedTask; } - public Task Error(string message) + public Task Error(string message, string title = null, Action options = null) { + var uiNotificationOptions = CreateDefaultOptions(); + options?.Invoke(uiNotificationOptions); + + Logger.LogError(message); + + NotificationReceived?.Invoke(this, new UiNotificationEventArgs(UiNotificationType.Error, message, title, uiNotificationOptions)); + return Task.CompletedTask; } + + protected virtual UiNotificationOptions CreateDefaultOptions() + { + return new UiNotificationOptions + { + OkButtonText = localizer["Ok"], + }; + } } } diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/UiNotificationAlert.razor b/framework/src/Volo.Abp.BlazoriseUI/Components/UiNotificationAlert.razor new file mode 100644 index 0000000000..349eee2c40 --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/UiNotificationAlert.razor @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/UiNotificationAlert.razor.cs b/framework/src/Volo.Abp.BlazoriseUI/Components/UiNotificationAlert.razor.cs new file mode 100644 index 0000000000..691b6a7a1a --- /dev/null +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/UiNotificationAlert.razor.cs @@ -0,0 +1,71 @@ +using System; +using System.Threading.Tasks; +using Blazorise.Snackbar; +using Microsoft.AspNetCore.Components; +using Volo.Abp.AspNetCore.Components.WebAssembly; + +namespace Volo.Abp.BlazoriseUI.Components +{ + public partial class UiNotificationAlert : ComponentBase, IDisposable + { + protected SnackbarStack SnackbarStack { get; set; } + + [Parameter] public UiNotificationType NotificationType { get; set; } + + [Parameter] public string Message { get; set; } + + [Parameter] public string Title { get; set; } + + [Parameter] public UiNotificationOptions Options { get; set; } + + [Parameter] public EventCallback Okayed { get; set; } + + [Parameter] public EventCallback Closed { get; set; } + + [Inject] protected BlazoriseUiNotificationService UiNotificationService { get; set; } + + protected virtual SnackbarColor GetSnackbarColor(UiNotificationType notificationType) + { + return notificationType switch + { + UiNotificationType.Info => SnackbarColor.Info, + UiNotificationType.Success => SnackbarColor.Success, + UiNotificationType.Warning => SnackbarColor.Warning, + UiNotificationType.Error => SnackbarColor.Danger, + _ => SnackbarColor.None, + }; + } + + protected override void OnInitialized() + { + base.OnInitialized(); + + UiNotificationService.NotificationReceived += OnNotificationReceived; + } + + protected virtual void OnNotificationReceived(object sender, UiNotificationEventArgs e) + { + NotificationType = e.NotificationType; + Message = e.Message; + Title = e.Title; + Options = e.Options; + + SnackbarStack.Push(Message, GetSnackbarColor(e.NotificationType)); + } + + public virtual void Dispose() + { + if (UiNotificationService != null) + { + UiNotificationService.NotificationReceived -= OnNotificationReceived; + } + } + + protected virtual Task OnSnackbarClosed(SnackbarClosedEventArgs eventArgs) + { + return eventArgs.CloseReason == SnackbarCloseReason.UserClosed + ? Okayed.InvokeAsync() + : Closed.InvokeAsync(); + } + } +} diff --git a/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj b/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj index 1134821b78..110387927d 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj +++ b/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj @@ -14,6 +14,7 @@ + diff --git a/framework/src/Volo.Abp.BlazoriseUI/_Imports.razor b/framework/src/Volo.Abp.BlazoriseUI/_Imports.razor index 37be71828c..0aae8ed947 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/_Imports.razor +++ b/framework/src/Volo.Abp.BlazoriseUI/_Imports.razor @@ -1,2 +1,3 @@ @using Microsoft.AspNetCore.Components.Web -@using Blazorise \ No newline at end of file +@using Blazorise +@using Blazorise.Snackbar \ No newline at end of file diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/index.html b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/index.html index 9f11ef4bd8..b8037cca39 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/index.html +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/wwwroot/index.html @@ -12,6 +12,7 @@ + From 1c0baa6ca64ac6db9686a13964f71e4edc5332df Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Wed, 28 Oct 2020 15:23:58 +0100 Subject: [PATCH 20/40] Remove OnOpenedCreateModalAsync --- .../src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs index f1563947c0..35b436565d 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs @@ -294,8 +294,6 @@ namespace Volo.Abp.BlazoriseUI await InvokeAsync(() => StateHasChanged()); CreateModal.Show(); - - await OnOpeningCreateModalAsync(); } protected virtual Task OnOpeningCreateModalAsync() @@ -303,11 +301,6 @@ namespace Volo.Abp.BlazoriseUI return Task.CompletedTask; } - protected virtual Task OnOpenedCreateModalAsync() - { - return Task.CompletedTask; - } - protected virtual Task CloseCreateModalAsync() { CreateModal.Hide(); @@ -330,8 +323,6 @@ namespace Volo.Abp.BlazoriseUI await InvokeAsync(() => StateHasChanged()); EditModal.Show(); - - await OnOpenedEditModalAsync(id); } protected virtual Task OnOpeningEditModalAsync(TKey id) @@ -339,11 +330,6 @@ namespace Volo.Abp.BlazoriseUI return Task.CompletedTask; } - protected virtual Task OnOpenedEditModalAsync(TKey id) - { - return Task.CompletedTask; - } - protected virtual TUpdateViewModel MapToEditingEntity(TGetOutputDto entityDto) { return ObjectMapper.Map(entityDto); From 66d60024ed8bd597c134a0e4f5a90dea3eed7b5c Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Wed, 28 Oct 2020 15:26:20 +0100 Subject: [PATCH 21/40] Remove ResetState and ApplyState temporary helpers --- .../System/AbpObjectExtensions.cs | 57 ------------------- 1 file changed, 57 deletions(-) diff --git a/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs b/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs index a173c2a95d..a3fd87188f 100644 --- a/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs +++ b/framework/src/Volo.Abp.Core/System/AbpObjectExtensions.cs @@ -106,62 +106,5 @@ namespace System return obj; } - - /// - /// Resets all fields of a given object to their default values. - /// - /// Type of the object. - /// An object - /// Alternative custom method to reset the values. - /// Returns the original object. - public static T ResetState(this T obj, Action customReset = null) - { - if (customReset != null) - { - customReset(obj); - } - else - { - var properties = typeof(T).GetProperties(); - - foreach (var property in properties) - { - if (!property.CanWrite) - { - continue; - } - - property.SetValue(obj, null); - } - } - - return obj; - } - - /// - /// Applies all fields of a given object from it's counterpart. - /// - /// Type of the object. - /// An object. - /// An object from which we copy the values. - /// Returns the original object. - public static T ApplyState(this T obj, T other) - { - // This code is not production ready and it should be removed once - // the proper validation in Blazorise is done! - var properties = typeof(T).GetProperties(); - - foreach (var property in properties) - { - if (!property.CanWrite) - { - continue; - } - - property.SetValue(obj, typeof(T).GetProperty(property.Name).GetValue(other)); - } - - return obj; - } } } From 18a3c6271675713cd99af64247a7b9034029ca84 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Wed, 28 Oct 2020 15:34:33 +0100 Subject: [PATCH 22/40] Use stringLocalizer default formater --- .../AbpIdentityBlazorMessageLocalizerExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs index 18a6242bab..a8aa779151 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs @@ -12,7 +12,7 @@ namespace Volo.Abp.Identity.Blazor try { return arguments?.Count() > 0 - ? string.Format(stringLocalizer[format], LocalizeMessageArguments(stringLocalizer, arguments)?.ToArray()) + ? stringLocalizer[format, LocalizeMessageArguments(stringLocalizer, arguments)?.ToArray()] : stringLocalizer[format]; } catch From 26d54f693283f31f44c0ec664bc68196f19ad6c0 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Wed, 28 Oct 2020 15:35:38 +0100 Subject: [PATCH 23/40] Rename format parameter to message --- .../AbpIdentityBlazorMessageLocalizerExtensions.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs index a8aa779151..72328eb86d 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs @@ -7,17 +7,17 @@ namespace Volo.Abp.Identity.Blazor { public static class AbpIdentityBlazorMessageLocalizerExtensions { - public static string Localize(this IStringLocalizer stringLocalizer, string format, IEnumerable arguments) + public static string Localize(this IStringLocalizer stringLocalizer, string message, IEnumerable arguments) { try { return arguments?.Count() > 0 - ? stringLocalizer[format, LocalizeMessageArguments(stringLocalizer, arguments)?.ToArray()] - : stringLocalizer[format]; + ? stringLocalizer[message, LocalizeMessageArguments(stringLocalizer, arguments)?.ToArray()] + : stringLocalizer[message]; } catch { - return stringLocalizer[format]; + return stringLocalizer[message]; } } From 11c9daf11a0f0af8e9d8315bc6bc72e5a490ca9f Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Wed, 28 Oct 2020 15:37:01 +0100 Subject: [PATCH 24/40] Fix formating --- .../Pages/Identity/RoleManagement.razor | 2 +- .../Pages/Identity/UserManagement.razor | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor index ac40aca83d..97001ee6ec 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor @@ -1,5 +1,5 @@ @page "/identity/roles" -@attribute [Authorize( IdentityPermissions.Roles.Default )] +@attribute [Authorize(IdentityPermissions.Roles.Default)] @using Volo.Abp.Identity @using Microsoft.AspNetCore.Authorization @using Microsoft.Extensions.Localization diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor index 6f33181f8a..0a61fc70ab 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor @@ -1,5 +1,5 @@ @page "/identity/users" -@attribute [Authorize( IdentityPermissions.Users.Default )] +@attribute [Authorize(IdentityPermissions.Users.Default)] @using Microsoft.AspNetCore.Authorization @using Microsoft.Extensions.Localization @using Volo.Abp.Identity.Localization From 4a3835fde94d39f45dc6fe04191a83f807129756 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Thu, 29 Oct 2020 11:31:01 +0100 Subject: [PATCH 25/40] Remove logging --- .../BlazoriseUiNotificationService.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiNotificationService.cs b/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiNotificationService.cs index d1e6ed6ce2..be2d5c9d51 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiNotificationService.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiNotificationService.cs @@ -2,8 +2,6 @@ using System.Threading.Tasks; using Localization.Resources.AbpUi; using Microsoft.Extensions.Localization; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.AspNetCore.Components.WebAssembly; using Volo.Abp.DependencyInjection; @@ -19,14 +17,10 @@ namespace Volo.Abp.BlazoriseUI private readonly IStringLocalizer localizer; - public ILogger Logger { get; set; } - public BlazoriseUiNotificationService( IStringLocalizer localizer) { this.localizer = localizer; - - Logger = NullLogger.Instance; } public Task Info(string message, string title = null, Action options = null) @@ -34,8 +28,6 @@ namespace Volo.Abp.BlazoriseUI var uiNotificationOptions = CreateDefaultOptions(); options?.Invoke(uiNotificationOptions); - Logger.LogInformation(message); - NotificationReceived?.Invoke(this, new UiNotificationEventArgs(UiNotificationType.Info, message, title, uiNotificationOptions)); return Task.CompletedTask; @@ -46,8 +38,6 @@ namespace Volo.Abp.BlazoriseUI var uiNotificationOptions = CreateDefaultOptions(); options?.Invoke(uiNotificationOptions); - Logger.LogInformation(message); - NotificationReceived?.Invoke(this, new UiNotificationEventArgs(UiNotificationType.Success, message, title, uiNotificationOptions)); return Task.CompletedTask; @@ -58,8 +48,6 @@ namespace Volo.Abp.BlazoriseUI var uiNotificationOptions = CreateDefaultOptions(); options?.Invoke(uiNotificationOptions); - Logger.LogWarning(message); - NotificationReceived?.Invoke(this, new UiNotificationEventArgs(UiNotificationType.Warning, message, title, uiNotificationOptions)); return Task.CompletedTask; @@ -70,8 +58,6 @@ namespace Volo.Abp.BlazoriseUI var uiNotificationOptions = CreateDefaultOptions(); options?.Invoke(uiNotificationOptions); - Logger.LogError(message); - NotificationReceived?.Invoke(this, new UiNotificationEventArgs(UiNotificationType.Error, message, title, uiNotificationOptions)); return Task.CompletedTask; From 20accec68a2931ac5ece91455aeb242872d71974 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Thu, 29 Oct 2020 12:44:58 +0100 Subject: [PATCH 26/40] editorconfig added to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5b8dfe9c11..b9576fda4c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ *.user *.userosscache *.sln.docstates +*.editorconfig # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs From 51284a2e16b276109c2c911e64917177fea8af55 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Thu, 29 Oct 2020 12:49:14 +0100 Subject: [PATCH 27/40] Removed .editorconfig files from tracking --- framework/.editorconfig | 131 --------------------------------- modules/identity/.editorconfig | 131 --------------------------------- 2 files changed, 262 deletions(-) delete mode 100644 framework/.editorconfig delete mode 100644 modules/identity/.editorconfig diff --git a/framework/.editorconfig b/framework/.editorconfig deleted file mode 100644 index 9f20b90112..0000000000 --- a/framework/.editorconfig +++ /dev/null @@ -1,131 +0,0 @@ -# Rules in this file were initially inferred by Visual Studio IntelliCode from the D:\Projects\Volosoft\abp\framework codebase based on best match to current usage at 2.10.2020. -# You can modify the rules from these initially generated values to suit your own policies -# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference -[*.cs] - - -#Core editorconfig formatting - indentation - -#use soft tabs (spaces) for indentation -indent_style = space - -#Formatting - indentation options - -#indent switch case contents. -csharp_indent_case_contents = true -#indent switch labels -csharp_indent_switch_labels = true - -#Formatting - new line options - -#place catch statements on a new line -csharp_new_line_before_catch = true -#place else statements on a new line -csharp_new_line_before_else = true -#require members of object intializers to be on separate lines -csharp_new_line_before_members_in_object_initializers = true -#require braces to be on a new line for accessors, methods, lambdas, object_collection_array_initializers, control_blocks, types, and properties (also known as "Allman" style) -csharp_new_line_before_open_brace = accessors, methods, lambdas, object_collection_array_initializers, control_blocks, types, properties - -#Formatting - organize using options - -#sort System.* using directives alphabetically, and place them before other usings -dotnet_sort_system_directives_first = true - -#Formatting - spacing options - -csharp_space_after_cast = false -csharp_space_after_colon_in_inheritance_clause = true -csharp_space_after_comma = true -csharp_space_after_dot = false -csharp_space_after_keywords_in_control_flow_statements = true -csharp_space_after_semicolon_in_for_statement = true -csharp_space_around_binary_operators = before_and_after -csharp_space_around_declaration_statements = false -csharp_space_before_colon_in_inheritance_clause = true -csharp_space_before_comma = false -csharp_space_before_dot = false -csharp_space_before_open_square_brackets = false -csharp_space_before_semicolon_in_for_statement = false -csharp_space_between_empty_square_brackets = false -csharp_space_between_method_call_empty_parameter_list_parentheses = false -csharp_space_between_method_call_name_and_opening_parenthesis = false -csharp_space_between_method_call_parameter_list_parentheses = false -csharp_space_between_method_declaration_empty_parameter_list_parentheses = false -csharp_space_between_method_declaration_name_and_open_parenthesis = false -csharp_space_between_method_declaration_parameter_list_parentheses = false -csharp_space_between_parentheses = false -csharp_space_between_square_brackets = false - -#Formatting - wrapping options - -#leave code block on single line -csharp_preserve_single_line_blocks = true - -#Style - Code block preferences - -#prefer curly braces even for one line of code -csharp_prefer_braces = true:suggestion - -#Style - expression bodied member options - -#prefer block bodies for constructors -csharp_style_expression_bodied_constructors = false:suggestion -#prefer block bodies for methods -csharp_style_expression_bodied_methods = false:suggestion -#prefer expression-bodied members for properties -csharp_style_expression_bodied_properties = true:suggestion - -#Style - expression level options - -#prefer out variables to be declared inline in the argument list of a method call when possible -csharp_style_inlined_variable_declaration = true:suggestion -#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them -dotnet_style_predefined_type_for_member_access = true:suggestion - -#Style - Expression-level preferences - -#prefer default over default(T) -csharp_prefer_simple_default_expression = true:suggestion -#prefer objects to be initialized using object initializers when possible -dotnet_style_object_initializer = true:suggestion -#prefer inferred tuple element names -dotnet_style_prefer_inferred_tuple_names = true:suggestion - -#Style - implicit and explicit types - -#prefer var over explicit type in all cases, unless overridden by another code style rule -csharp_style_var_elsewhere = true:suggestion -#prefer var is used to declare variables with built-in system types such as int -csharp_style_var_for_built_in_types = true:suggestion -#prefer var when the type is already mentioned on the right-hand side of a declaration expression -csharp_style_var_when_type_is_apparent = true:suggestion - -#Style - language keyword and framework type options - -#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them -dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion - -#Style - modifier options - -#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods. -dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion - -#Style - Modifier preferences - -#when this rule is set to a list of modifiers, prefer the specified ordering. -csharp_preferred_modifier_order = public,protected,private,virtual,async,static,override,readonly,abstract:suggestion - -#Style - Pattern matching - -#prefer pattern matching instead of is expression with type casts -csharp_style_pattern_matching_over_as_with_null_check = true:suggestion - -#Style - qualification options - -#prefer fields not to be prefaced with this. or Me. in Visual Basic -dotnet_style_qualification_for_field = false:suggestion -#prefer methods not to be prefaced with this. or Me. in Visual Basic -dotnet_style_qualification_for_method = false:suggestion -#prefer properties not to be prefaced with this. or Me. in Visual Basic -dotnet_style_qualification_for_property = false:suggestion diff --git a/modules/identity/.editorconfig b/modules/identity/.editorconfig deleted file mode 100644 index 9f20b90112..0000000000 --- a/modules/identity/.editorconfig +++ /dev/null @@ -1,131 +0,0 @@ -# Rules in this file were initially inferred by Visual Studio IntelliCode from the D:\Projects\Volosoft\abp\framework codebase based on best match to current usage at 2.10.2020. -# You can modify the rules from these initially generated values to suit your own policies -# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference -[*.cs] - - -#Core editorconfig formatting - indentation - -#use soft tabs (spaces) for indentation -indent_style = space - -#Formatting - indentation options - -#indent switch case contents. -csharp_indent_case_contents = true -#indent switch labels -csharp_indent_switch_labels = true - -#Formatting - new line options - -#place catch statements on a new line -csharp_new_line_before_catch = true -#place else statements on a new line -csharp_new_line_before_else = true -#require members of object intializers to be on separate lines -csharp_new_line_before_members_in_object_initializers = true -#require braces to be on a new line for accessors, methods, lambdas, object_collection_array_initializers, control_blocks, types, and properties (also known as "Allman" style) -csharp_new_line_before_open_brace = accessors, methods, lambdas, object_collection_array_initializers, control_blocks, types, properties - -#Formatting - organize using options - -#sort System.* using directives alphabetically, and place them before other usings -dotnet_sort_system_directives_first = true - -#Formatting - spacing options - -csharp_space_after_cast = false -csharp_space_after_colon_in_inheritance_clause = true -csharp_space_after_comma = true -csharp_space_after_dot = false -csharp_space_after_keywords_in_control_flow_statements = true -csharp_space_after_semicolon_in_for_statement = true -csharp_space_around_binary_operators = before_and_after -csharp_space_around_declaration_statements = false -csharp_space_before_colon_in_inheritance_clause = true -csharp_space_before_comma = false -csharp_space_before_dot = false -csharp_space_before_open_square_brackets = false -csharp_space_before_semicolon_in_for_statement = false -csharp_space_between_empty_square_brackets = false -csharp_space_between_method_call_empty_parameter_list_parentheses = false -csharp_space_between_method_call_name_and_opening_parenthesis = false -csharp_space_between_method_call_parameter_list_parentheses = false -csharp_space_between_method_declaration_empty_parameter_list_parentheses = false -csharp_space_between_method_declaration_name_and_open_parenthesis = false -csharp_space_between_method_declaration_parameter_list_parentheses = false -csharp_space_between_parentheses = false -csharp_space_between_square_brackets = false - -#Formatting - wrapping options - -#leave code block on single line -csharp_preserve_single_line_blocks = true - -#Style - Code block preferences - -#prefer curly braces even for one line of code -csharp_prefer_braces = true:suggestion - -#Style - expression bodied member options - -#prefer block bodies for constructors -csharp_style_expression_bodied_constructors = false:suggestion -#prefer block bodies for methods -csharp_style_expression_bodied_methods = false:suggestion -#prefer expression-bodied members for properties -csharp_style_expression_bodied_properties = true:suggestion - -#Style - expression level options - -#prefer out variables to be declared inline in the argument list of a method call when possible -csharp_style_inlined_variable_declaration = true:suggestion -#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them -dotnet_style_predefined_type_for_member_access = true:suggestion - -#Style - Expression-level preferences - -#prefer default over default(T) -csharp_prefer_simple_default_expression = true:suggestion -#prefer objects to be initialized using object initializers when possible -dotnet_style_object_initializer = true:suggestion -#prefer inferred tuple element names -dotnet_style_prefer_inferred_tuple_names = true:suggestion - -#Style - implicit and explicit types - -#prefer var over explicit type in all cases, unless overridden by another code style rule -csharp_style_var_elsewhere = true:suggestion -#prefer var is used to declare variables with built-in system types such as int -csharp_style_var_for_built_in_types = true:suggestion -#prefer var when the type is already mentioned on the right-hand side of a declaration expression -csharp_style_var_when_type_is_apparent = true:suggestion - -#Style - language keyword and framework type options - -#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them -dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion - -#Style - modifier options - -#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods. -dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion - -#Style - Modifier preferences - -#when this rule is set to a list of modifiers, prefer the specified ordering. -csharp_preferred_modifier_order = public,protected,private,virtual,async,static,override,readonly,abstract:suggestion - -#Style - Pattern matching - -#prefer pattern matching instead of is expression with type casts -csharp_style_pattern_matching_over_as_with_null_check = true:suggestion - -#Style - qualification options - -#prefer fields not to be prefaced with this. or Me. in Visual Basic -dotnet_style_qualification_for_field = false:suggestion -#prefer methods not to be prefaced with this. or Me. in Visual Basic -dotnet_style_qualification_for_method = false:suggestion -#prefer properties not to be prefaced with this. or Me. in Visual Basic -dotnet_style_qualification_for_property = false:suggestion From 0b35024e533bd97bd8c764ab31052ed7f82b7731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 30 Oct 2020 11:08:01 +0300 Subject: [PATCH 28/40] Documented ASP.NET Core MVC / Razor Pages: Branding --- docs/en/UI/AspNetCore/Branding.md | 44 +++++++++++++++++- .../Customization-User-Interface.md | 4 +- docs/en/docs-nav.json | 4 ++ docs/en/images/branding-appname.png | Bin 0 -> 28099 bytes docs/en/images/branding-nobrand.png | Bin 0 -> 33490 bytes 5 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 docs/en/images/branding-appname.png create mode 100644 docs/en/images/branding-nobrand.png diff --git a/docs/en/UI/AspNetCore/Branding.md b/docs/en/UI/AspNetCore/Branding.md index b262c13d46..ee19cd5845 100644 --- a/docs/en/UI/AspNetCore/Branding.md +++ b/docs/en/UI/AspNetCore/Branding.md @@ -1,3 +1,45 @@ # ASP.NET Core MVC / Razor Pages: Branding -TODO \ No newline at end of file +## IBrandingProvider + +`IBrandingProvider` is a simple interface that is used to show the application name and logo on the layout. + +The screenshot below shows *MyProject* as the application name: + +![branding-nobrand](../../images/branding-nobrand.png) + +You can implement the `IBrandingProvider` interface or inherit from the `DefaultBrandingProvider` to set the application name: + +````csharp +using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Components; +using Volo.Abp.DependencyInjection; + +namespace MyProject.Web +{ + [Dependency(ReplaceServices = true)] + public class MyProjectBrandingProvider : DefaultBrandingProvider + { + public override string AppName => "Book Store"; + } +} +```` + +The result will be like shown below: + +![branding-appname](../../images/branding-appname.png) + +`IBrandingProvider` has the following properties: + +* `AppName`: The application name. +* `LogoUrl`: A URL to show the application logo. +* `LogoReverseUrl`: A URL to show the application logo on a reverse color theme (dark, for example). + +> **Tip**: `IBrandingProvider` is used in every page refresh. For a multi-tenant application, you can return a tenant specific application name to customize it per tenant. + +## Overriding the Branding Area + +The [Basic Theme](Basic-Theme.md) doesn't implement the logos. However, you can see the [UI Customization Guide](Customization-User-Interface.md) to learn how you can replace the branding area with a custom view component. + +An example screenshot with an image is used in the branding area: + +![bookstore-added-logo](../../images/bookstore-added-logo.png) \ No newline at end of file diff --git a/docs/en/UI/AspNetCore/Customization-User-Interface.md b/docs/en/UI/AspNetCore/Customization-User-Interface.md index d2a1193a53..ccb832ccc7 100644 --- a/docs/en/UI/AspNetCore/Customization-User-Interface.md +++ b/docs/en/UI/AspNetCore/Customization-User-Interface.md @@ -128,11 +128,11 @@ The ABP Framework, pre-built themes and modules define some **re-usable view com ### Example -The screenshot below was taken from the **basic theme** comes with the application startup template. +The screenshot below was taken from the [Basic Theme](Basic-Theme.md) comes with the application startup template. ![bookstore-brand-area-highlighted](../../images/bookstore-brand-area-highlighted.png) -[The basic theme](Basic-Theme.md) defines some view components for the layout. For example, the highlighted area with the red rectangle above is called **Brand component**. You probably want to customize this component by adding your **own application logo**. Let's see how to do it. +The [Basic Theme](Basic-Theme.md) defines some view components for the layout. For example, the highlighted area with the red rectangle above is called **Brand component**. You probably want to customize this component by adding your **own application logo**. Let's see how to do it. First, create your logo and place under a folder in your web application. We used `wwwroot/logos/bookstore-logo.png` path. Then copy the Brand component's view ([from here](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/Default.cshtml)) from the basic theme files under the `Themes/Basic/Components/Brand` folder. The result should be similar the picture below: diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 536c5d9a03..f292226ae3 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -456,6 +456,10 @@ "text": "Page Header", "path": "UI/AspNetCore/Page-Header.md" }, + { + "text": "Branding", + "path": "UI/AspNetCore/Branding.md" + }, { "text": "Layout Hooks", "path": "UI/AspNetCore/Layout-Hooks.md" diff --git a/docs/en/images/branding-appname.png b/docs/en/images/branding-appname.png new file mode 100644 index 0000000000000000000000000000000000000000..9300ad2c7fe5255566d119320fd2fdb8d3b9382b GIT binary patch literal 28099 zcmeFZXH=6*8#c;Tku3;p3nCz(0wP^{FN%PnO79>dgd)9!9z_MEN~BlmozO!K3eu&9 zfOM&$B|r#-1VT>S+x@;jzIE0*>s#lXb-s1J`$yJO=b4#%<}TMed*i0{J=K3^9~Vb z+7Yf-Z(m|nIj8TRRQbmh{=7q*ib-^tT8&A&lUKgeJj}8j`KTf4$J6V=<+Xp_yfJP1 z$?R&InChK34n3L-v>nQhwR)WwAfM2OA$z^wGsRIMBbk4|QHqNPgvOR*$irW?_*)Ju z{8Xj?J$ZHG^<$2IPvk%Lo&SCOjOJ?Kzn6DZ?lS&9e#QP|^^f1j)#pDb{QHRi?F5?N z$6pw@vHxEF@xKfIpY0Y;^?#I9(l;$C#5d-UF5JaW`z}OYdi5e}3*c7=KV|+~7o$!x zA~$7jb4@!Z!hb^A9B+e`Qs{r&|Mf(P05?`NL(HeEF97gW2YR_#pf%T$qT$iG)Y3O~ zLEq$KXhkeupYnX$|Mhfw^hLnxF8SGzrw2~Wq`EJs6$2g7$w+Z^{luDhl6Ps!R=VCp zvlbB(EfCz?^rQA1VOH51x2kmzLO&%(d0)Osgq>9=&USyFRqoN>1;+MBsV{goEeFSD zo~E4T*A*|EuC_$D>$YzPbVIWP_R1b>)H`^2sb})8K@ujvq^u9sw%SqN^xu-H_Q(^k zxMh3O<@1fCPg2V>+VOV=a}I`=+5OtR^vL?EpEA{*Y=?K&ikrOZEGPRe$2&8v31!r6 zsmx13ZPH4+YKcwkCb#h`fdVA@c~&Z_XLV=!?z-M4JJ0=Y$v8f#Mf`cAD(A2tGxKtL zyhi$7OF>>+nvV%m)i5K0xHFxN-E{S0l}cbli(ED<9JyTFYp>q(UjAyY4!ElYvyMvl znQB{kn?gn{WDk#{y1%{>`3t@wVdY-THT?dlicSp5shA5^2gQ>Q$M7E~&Qm?>cx`|0 zo_wLo8Jo|PQLDjL^@q^82*Je`{Q7QvjhBwm^Qd0)rFSjSE3FRJIZL8OdV$1EEq_^h zK+$YQ8GG@OG2lHY4RSd}K;K34L!0!%=EoN@)&K(^%+YzO(f5yAYLQMGkL5+@#JvNS z5%sNCj^>_n>DpGXN+d9@cYv%5wF%)Be2m&0X&WD}r=&_c%=rw#p0}b>^yXMTtW>II z+-N5*9~>MAR4u`sI(4LDv>N!~L_|M~5wA(>jii{4^bc<@01GWIM!zA9Eg#3FaYYm~ zpKIuw4F0AOvA~8|RYLn$oE+gSEb!jZ=WL1*e-h|)2`L$!p=zb>6x}eb<%F#D6g;!N zJ+$I)_ByXE!kuC#ovve}&+Q8rU%{cdM%=<>LTg-J8oR-pY~?$$ z^z%hTud%H2SG%MyT##-LTd|(jr!64DhSPiQ2T+FdX&}2}q3QBvP}0%@yLj(qfDbEV zDax-4Ep}}#;`-%xw~98g1*tQmf0q3;`e`ZYvF;rAm=2w-d~$u4?x?_KMxY-p_J|EQ zwMc(~8P_H#I=xa)yI3x zMZrk;v*U!Ww8?6nQjM-pic2(Sna*R&lxn@Y@!eBKk;&$M?22pGW#+C+OiU4ifZ<~s zjGU|8!Oy68yo^qMa*nG|bNeNC4aMDGrq_v|b@pZ;VB88k78)ft?dzi;4V|`MKP@JPfR(B1eZE zeM7d*E`Fp2iVgY^f2NX;oY2$B*Iw;y;sV`eCis+DZ-!UQy59^A4Ei;zzh5k0e)c5= z=FmMiWlLJ+X12)YSaFAY`8q;ommb{R6>bcFeTsHvZYO?ko3ns5{h9y)9wU46ldoe% z&ZgYz>^sJNf}tr3vYRe=aJ6F-?36AJ7d$1{0b4cy8vfN(ulzLIQ1&~YxO7ZR%Jn(i zz{l3wBpH_(d)Q^|2P@o6l{repGg`H`b8mK2afn>StMamUP5XY$E)P<$h>~RS2V+rc zigPW!eN)rNM9|7!=F`b#Kr**^GXLdjhIh2jXIy$E;`#W@>IaL@$L0A#o#m|f`sJRU z40Bjy&b5UAL~S-D8(k?*>nZOBqlASHX+omsu~IoWB;eI)ZkvNve&tV=gFP)6{k{o{ z8Yjv#D43x+7ub=@1)Kxz1|zLp53^6Pt2K)AUP(4_;6mSFvN$ddj%fele1_ zoNmAo7!&htKR0#dSgjepWE0Pwe{wjYcYK^}?x$b?7iOp%A#rMRVua@3>MvQy5QxRu zaM66jZuW1onG4xnoSA8&ID7{joh71oLb2ga*;XHz54E*+UL>2))l@t!>4OOXktu3S zQ`&4^ZbiKx*G2+?H6r`tuN7CUi|znSN=iHjA;z{5+@T! z0PBRb(D6@$ll9@bp&3fnpV&&ET(KWoK}E&h>;x=7QvMRrpQ)#I`rGvsKEBo2KotXb zo6o@T%?DYRJ`7a4Z@dZ&8&TTq=MXn|&T~o#xz+H^1y->wu0XM5#56imKk4L9+f(i? z@ZQsE=jnk{51#&cbQF9uYhYOW^E1R&5+PFHWhuEN<5T96VH?*#51t3tzmc@+pCnP* zm(t-i9}$IEP@fbT$S|*Y8r=GpgQXoZ$54lRYuT+v5MXxSOKRik^aL)*h#EpPr46d+ z%O9nY#371Bcg*2Pab|U5fIhtE4=|?aQ1(Q#XoQ~A;*#n3w~SAn0?D^t{$p~_HG2$| zTX|3Hq)u`v%6{E^Fd)S#h(%$nI-G3_Hv>i3gYEMK#<2$jInn87!ryN92(+sQ#L`fy zSeLj(a#vVC%89YLR5;H#NK5T4T{-+=Te>EG_T!o*e&LiAn7|_A_`FuU4S_|hPK=h9 zmKlJ*@MG6dAy@r|KDkRtng(8H{f|{4FIo@=hH|ppELp?_?9AZgNS=1e%!%}_k|=-f3l-29oL&AP zS`ZK+z{ex>m4&xILB@SxH)ix(?_~GH&+mL4t#wdwUC5)^MX=!d*uJH0T04(;$Zg8J z1b*K+o3S@spZv?t!+gJH3;>x^6wz}QDGgWzmSeTZo3kCI!;A<*20*nf=^E)zN;Pd; z6}@i^G}`*pX12S7mn65D8=6gj8M@`R`oRdC<^!$T(Y<4jt_NA!1!3md$FX$DyYHp& zCHdqM8^GFD7Fi3IqGc4SpSIhno>g zp7$7TZR(n}Iyr|GCL)9}3-%&xOG>_m0(8pr)O4=rFQqe;jvBa@+m{+%!l$% z{rgOsa*?Oj@8$AwST^; zq&DS5Jl|f|QxL!((jwN`&YseW9h2TM;AaRqno%7w zW|vw(ou#-(U-%m7e|p$l3Kg!b*6kGtBV^D(0bWnyvSA9OLF8125xj1}t8=j9!|Fu$ z8Iy>juWjK41+NK&-hynO{e9uEiNs>5SUC8spB2qQKCMsK_kz4&5)9bsNs;;%_60tz zr>jSrpBP+O;g(&FT)_8!-`_@c5axWwdMic3{9KOlA5N72whl)xF z&{}_`i)%{!^=YgtY-bNF02rnVI(o93{fyNy1Ue`p6y@FUQ(M=-Yl+CS?SJw}Y<^yh zrXX0SYp>vP`t z9tX>L!>;IYMS1(y+rEW(F{4D767ay?_KCO615Xw8TEv6Q59d!%BNoO8k!#s!OE>4S zXe&H2By$`E z-)HE;71^-Lelb2+Fe{8YpZ{EDt9I9=*(cXOdjj`;;&F;|lR@N*cX?JPvR5OT2G~8C zJLopw7=We}Zb2_fT5U?s{Bo6B1{LfOe`l`Fz;Y(snui7Kj`Zg~@=wL9iU5q^0=Yj( zlF4Y6)3XP7WG>FzI0iG8j5~W}ceD1<>+q~?(2l}W?tTU3I&gWs3rS`W>f>xvf4~}?~_iZbyvt0R&(V({Al}ZmI*+rUUxUWW^=)ops_=&Yx zDamdZdVkn^-+zs(!QoVZqwNS@2EKHFng{h#wkgDUGmFy6ESd8dvaG!*j3>mY9f` z&J10&o`w{}f#z;1(M>Y%l9zi@_}lScGHG^ovqfF4Agh}FtWZLo-o6aQMek&OkX}qR zYlU$;7Q9;tSR~SNJv?XYPF&Tp{X0=fIziY)+*u?Navm>S#qQd)e7PmB)XjrfOEk6{ zn=H(c{m7EF|p@S%+g$1&@}-!7{t1WoBC+T)>KYrrP>3S1f2xTK1=`Frsh*tKu%`rBLy4| zcvHWK!lsPiV_Ky8`4DMVt#-$24TC#uoP^Ddl|Tn&zD>);VM!m_^XB$q3P`JycOz<> zn{))2FlpkJyx15|7ZW(a-(9i$hOtlKR4SB`o60noSWi1CkN3{p;oIJDPqUpI6Cx`g z^EK9+w5Q)Ey{7K@vCk*W!hljU_=I6Jd$>qUq64MGdNGD3)puD zPQi`7NX=iNyOD#;RWbLlozngkzlyeRp{kAs$MT<)!7fM{rMtkHR<^E19aGK87X9Ej0;s5tUiaG zgS4r5^$WzkjdA5XIp|cL^XcXzg_waNo!xRyULUS8lxW}!BU7F22VvE3K7^a&~m z_&YT!cm=j6tQ>mmt1(EmKc6QaT(c_bWd3M;l{`9(q?Vj>WXF0`94C(st2{WpU+tmR ztp@MhOA}qTs$tjY>&W-M4wr}BZ==s&cY7QW!=kRkK4WHPJbN3^oFiys-kja~=#N1N zHzFaZtW_MLIJ^h)BMp?XkBwrOmVy^_Ko%iMg|AOFIh#&hqs~EId_zj;KNR#nz?z0w z`0G#g{Zf31R*m12tDV&6^Zk8QTcy!mwoYOZFJFb0^5C_qrVq1i1z))0-092voh0rmMQzQpB8{We(Z7l za3Pu3m56rOKW(!V_LAj6AXe@UF_3%2WW)_N#Xh#))(j*Xz=4e+UdGRv0hr^O@p64r z@AzQ!SYKi>1yY&2S@N(T&@Xx@zU!`~g^S3N|tvqUy5S|1r6DA&8P-Lwxh`r;*ojzZET`TFW?=GMx@-mw_0WvuOEShXm&F;If}WN2>!;WFX8hW zblmb;-Yvn+#y5`B_m>8f?}F=zSYl(z5B`$}0+Lc2&^gA-)Q^&HMTCD1O&ZY-yDy_6 zYz(Wp&o)wteeQl=;_2|Gd)V2~*Ds0@4^|@MI8Ca3279t-bob?Cu9v_w_2uJUh1}JGP^KHy(f*Xcps!hcNMciBTP%s8 z!{u~}l1pb=4OvfnKi~dXe~|23oOc$vP4`wXt$0$u#28vsL~%Gl84tlqg+QG(`mec{2!|ZNgtH zsD_R`sH8`Lv)xZvXcpv_s z9DRVDsy5m8Jk_fQK&yak=JJ^YYWYXG+DY-__Rq|b-5U;XbiR9P%xiNfi#E5QZ$@w( zWo5^iEeCWIoG(^L96)rjWBzJ300;TtzRnc!9)=J)q7<|qbRo_$O-YOxSDH_nA_YZb z5bPIyy>tBry}vJ|T`IC`p`^-sm@AZYWcNp#BrF+GX5Kxf_eQ{PkBJG0hiC#f<>;4o z%b57fjywQg${a0j9Ka|-rrbbXWQx@EEW8&}_kEu;kMjFJhiuaU+xlUi+; zru_#cCbQjqlq4Ibj>{%jMZ&OEbEYd zdC{ik6HXaNE0Y}lq&u!7+U&_l-UYiSCSOcbJN6L{S~o&)J(l8&KX#yUUnwx5)5EPo zdE@x@^rHNgFHdfHP@g~X236T&_iG7tK=}S-Fw!{J*+y}8y~wV7(g^jX!nwTHfdZ0w zcQc}M3p3bS)Yh)ksCuV*K@?b|$+e(it%j`8!&PsTQb^(M8DkM6z6ap-!E{dgxH*{3 z8l9plpX?^LNnPg=&8|a%nVkQ8=P?i?o@szvqIOplu#$22MucJTr}?TO*O!c5r+Hjv)6*Ds8|#F zz~8+b+pbrd4LE~}w~9>>z6C|WSrIP?lc7cpav{`1FyL&a#PDMqEQ*NwyE|C=Ey3_2-L69zm(ouMCv3B|3#Xl%ub?6$Mg$?3?)xPr#d!_C&b;6^A zhhX+8ACb${hFNiYAsJ)DHUDbfIFSq`>FSR3JM6iBXL&}&S`h+pN<^GoaH`_@YL=(V z*Y-T*H_i>`qJ3#6n$k4jl*g`sI)ZPc8buruqdRV_Qw*(-*BRqUJl= z(+4{X4Z+q1roB6on_wF@TM=0QcUzcL(HL$Utp?!l04mt%f1w49SPlcK*Pg= zvEgblt}I{P%o*=bygrk|?02IG@pEyFjuI@CrUY#TAZon)>v-c*S^}*THhs#o!fn{R zLzB6?KBck*I;4o)1~BLieBno)Q%o^l#1`??i`-=Kvytw#zlkypaQA0RTveElCBm*p zjHo?QNBZcu&F50>8;kQekf{15jJTFz7XE6s?3%S{Lz=5tS~J|nvCyh z_3Ki0trK+7$!YyrS5F*ErU~jq(7}I@HO)@zmdN4z_zT#R#>wi*xX?eeAeq@pONM)H zy4j167c!qjIE{}8G9rDqMBSaj?aXzck2Kfc>zk8W0|%~R4j+gebK@j#FwUWeBcu(tRoZnj_lzxxz239PsoTy;9@}Ae~)t}Y0&G4mE$_n?M502&Y8xpa=J~T?S{v8V{o2A&@GR% z?VirCtgGBS3|V6C>l8?xuK9&-v#{8K%MvjyC$ar(i?_9>uu#6>DAXf$61bn}H@2|D zA$^G5dr_SmOJ`GAlfcH&in-H%@~z~8s@J}1@oANe#C}c5Y>dn4$}SE+l6kZ>?gCW~ z*4w_q8Fn3q<_8t<9ERK)E<+@gVi@b9^pe+f2b^{x%Lexpo>)@hn#N?fBT;BA+1(0Y2KL?hqh?;1cg41&CQBVk)gECo3%*3d1P-@sjM1Ue%8YV z8*Echywz)&%#*+D*5Jq1TW;+b-ntsLz-l_z3@wSAweL&%h%txt@Rs5ZL^GF0ig7D7 zA=}dYigkAG!ZLp9nTVDCKFxur-G=NqH*G_Gf zzr(TLk*i#zt?PkeDXFLEsQlQPo~gpx1hmLPX*54@c}V)6df_@r91-3}1PJ$<}!=|>pOT{O!XsfW0{f=W&nnkD|2-R3%9AG|=Ahhd%Ugc6hzq zr)Q2?wN+A*4>OKj9fR+#G8V^2VY^-h@3A9*VS9oOZj>>Dj%_PwJyi^Dd{C>rVEotn9kYl#=E+Qd!>w(^#H!Zk3Cmur zknnfWj_?DUqhXcl)J(a!6&`U#g@6J68BlH4bk;T<0J5noys1Yt8Ok6#!$qDR?VFi= zeuk>ud8gyOlQe=7DGMnhHE-V-g5o~uIdwtwK!itY406f(hVslna9jE9r$qL~iZ5{X z9AOLA_wXPL@1QsBAT$)x9IYy!GLqiMuVQM&8Z0-ocr2GbH_Y4uMK&Ca(_Pq za&@GHAjQb;l*_fD#ec80m_i<%(4AlFkS;>ZFX&5^-D}!R$}wj7Ge_289g*Ct~t7JhCdgF&CRXzhV-O&8#5XY{&rRnJuJ z4@Ca8rc#?d#4vKq|BiODYJ|RD`w3_@wzbAL8%dP;PD@!QX~vmeE0uX#?PKbGteuWo zeH#GnRhYg(@ho&>!8@^h!jx3jX_A-byHb8np%fK(wZ)W4#G$pgU&KnBlOzf1upbn z6v$15ZoH~>FtOli$>-OW=Ok#|5${$?@3BscG<|D2g5G*nfl&0DP?)UhZk#4NxVf$XFa?p`Rtt$vcVOr zRn`h9)+K2s8oV3v1*R6;UAMLJK;TDCo#&BD&XTvkCn-a}C6vkX>ziOug(a>hzauDU z>Gjqk1H~-9w@-gkHa@e|I)6?i z>QzLk1!WoaRr0r;)a3LHQF?*Zk@@D?DTrnmWevOj@_g0JoaOs7DH$;6v&Gk*ppi99 z@+4RHwkRbS4ix~3jJW-fIv4vNUdt=Qn9wiu=t^iB(+q5XWlg>;?E;6BGb=?=zX%SL zFFCHX{$z|;dC653+gV7<(_dS$%MjM!GdQ3p#S}L6x$->gwVwZyY9c4;=l^+;@@sM7 ze^YG!?Tr5wm`9Zirz_7?VU+OyZ+W%88#7tSa_?ViluA3-|1<~6Q@z}zlC|acprWFZ z0RNwGsGk0BvexhJ|9^mHCX@dMK6Lrphl_Lazd!Ps#}!$YG!*>u)nj10S9N&H&~6+roP zPu0t7zvJu>3MOsaqE2T!y1TPj;#Rih6JdM_BC#v+9Ez;89Gj!LU1`8<0tvs!sT_8C z?Db#s^Ag$Ow>?>D2_}>9G>jY)?5NDZBKF*nZQ`tN>A1{UBxl&4luE>h;qn{ZW^XVv zM_vnLqktm+h)Q5@JSX|=WY@C6WzD-eT;q(;#i*R9K79y71nI46#V>vPaBVRZ5A{dS zute-rzAs)Z@cZ4*c3CoBd6X6bb3%_mizIJ>0NOpPu9ZHk${c`Sqi^Nr4 z-OLAf33>GunZo(`@1|UPq~+UHE1@je^so) zHD%P>m*OFv`!C7yb3*y9*=$jowc3Bifz!8_PwL#t{`(T0X~zeJmF0hJgpRJ_yV;ok zX|wQufp;mBpUd`d6B-UebTih1Ej{E%iCaK&OV#fRK2o)Co6t2i=e6sD}+B# z|IPPQ`Lqw#NFOg`k_=(NOVO0r=a+nTzDaEKDoB-q6J}RENF({IM~!2n^rgWepx=Ec@y7_N z9^lV0Lid+*B>JX1@cCwQ@LC~f8fYZ%n$IboH z5zBwQtxR(zjRogvV_bAlzUVCHOTQhN|FBnoU^u_+3w{5v9x$FUD{}dtA}K~#mm7Py zIjRA(`XSD9iaLsCkn;8U_a=P_!L(BLc!~Wy=WwZG?if58-$@6S!)`s~YHfrynE!t6 z)s3Qe@d+@7(1g~VH;ZR3r=z&?9CN>t+>BpruV3N>w+OgvOUcaUEh88AVNkXf_J3|r zVv91$H%fnQnNI3%afIERBX>^)cLb2Llm+KFq68bfixl?tI~LlD>%Ei!y_zYa*oc8G_q3t8F?ajs*Wt0#Z=Y>Dy+xtKu@2{QCM4r`yHP($C` zD1FC+)t3>&obB$bZyGK}h}`G>Js~9Ix%@!2!*1o`vs+e?_b!h~($C+mSK-DQSm6h& z1cMucM$RH!f03Ul@`~u?*j)_ln}2|Ia!=M9`@mZt{8AN`|G~-5Z#6$!zL-);xrX>? zxXJ!mWppyxZMw~EmiFknIb?Ou9}St?9oVj730sOU4>Z({!h2Zbrj)@=&ks@54*_EG zpysBeTHH!h=dK?1s_jGxHU%#0>rHP$zgVQQPnWH^#Ng2rn==n)uGCKT*N?r zjM9XdoI)?6j9Qm-?rlaej6ZA$8mnr|_2NoM@!i;^cF+^vnkyZk{W{O7B)+yE7+j$# z!n{Lhdp3g7Iy(-)ruOEXpx>8yC^SQvDIC@%O-lwe>3R1;q-h%|01Iy08+b#SF%8k! zUj!-#psxvpGubvodE*5~LcIg$)J*-GH?Gh8?)%S27wJd8-5Pqi;rO}qvr8=QFbY4F zsMNm$%FSP1Cgmtdcv-EDw%ru~VMzA9@aoDYe|g~r()zcYIa^Y4oxI7^Xwi1~ z&b6-WAO0?-t*jBM@m5o#ZYqee(R+#^GAFb+mGz{*EvgTw;RFG_m zJKJU96gF=STAM*?t#=Umz*aAl1#6Gm4uATvxg_*+noAwdFvB7d0r`U^&UL1knMUWS zMxEBi%I{$zMyNguYLCU^?`7kaVTv9^OP*L!2zjM+ry&9Ma;8BDxE2Z3YoB2Y{&!qv z#)Wr5R&~aL0rd0NC9VhwCcqwP#9fHLb%{gPdt-N2$@Xwb{90s8u&>UGn3l1%2;eTf z`qA1^``T5{K=R5^p-=X)vpVVsmAN8=$~+=31moU#kalBo0<}!`+DXT)t@bYg`=}98 z5a31c!Zs!6-!Qh;>Rp#_u_kP6#{-k=Wlzv*k)4c;?v^&hm4{QU#mX{yKFyGQFLpWG zkfAQ+!WX}zuV?Y$=ouevNKeefmk9UE>2Gf&_klfui(%(aj@W*{7Dp?#J(>@fK+=!5 zI&NBPD2t)8Y;BCZ?jh#~&y3PvfwCn?Ul)>SXP$V!m{TTYkDtR*gRR?niz05BA7OSN zoacI!L#EV>9X(0=dpWTpfo(x!J#UsYB#nlDkBw(_pWSDx^2gKIah-j?YH-fcANj6} zd=fVsdAMsO=|+BjNCSuTrgb#cYDuzfcu&Xnk0>Vy0I))HjrjGdta9@@BhM}@6HmN` zwZC1Gf{j5YJ}+AG#0M~*jy{pBcr%gf!>lU1WEM6;$Eb@}l5VywcJiRGOn-?*$=l&A zS9MEI$2I1*c|V7^6f$5f+a#3@n-Sc>9Eh?snh(ZIjWHY)P5p= zc3-JY$BLyrqKGA9<(w^5l-1#X1NFxTgddIwv$VKNk=aY zU?aE%$@I~Ow8~kAjlfa_5J(qn%iXNo2caN|Mqr zya99sm?9HD3cH9{%WQqch;z_`9e&Ltfo(Sp{1uojKKkpM7Exj1p1q>~gEybr9BKj@d{j(1*l~uRme0ZGW^+YYgK3&W*UNeT>(4E*D z|KodaL8F0Xv;0PcfMi<+@k}|qg!NF-HjVDNo81ATvuyJ8 zrTPnw3Hk$|_7)RC>4K?3(BUc`Z<3YRFeiYHEmPeXlxefp;$%K%=o$UEY%!U`=d6ix z4I>jpT5dYfh0ijdVZM5CU1Vq?>6JexivFX2DyK&GXwf*Rxh4IsdB))Z?nxDQ)~va> zm|kOT!7z{4DJcCg*Fv$Nj>90RaWagz0hyYc&JD0=wq9DohDMqM;Bg099DbwoYs|i; zQ+3?VXtD0)@58LV5hfL;L{*o=r~Om}pN7+-C$~C)85b4vP2$Uts+uE{c@zVoJDFF* zyLgPvkm$R^j))pr4SfPsp4kk0x`rRwX2LD8@sCX^L;9NP1l%o29?8GE)N@ z=4g!6R#I1QZd%q%hwW7YfoUiyk&HvFi+5M&vV5Umw zApqMV&=&g9Qm#nxVd---TpAF@B>bLFp_o72HN zxxv|s3&htv0FxtYMqBLW?!}yIe#m83USpvFq4MliZ?(lo4eI2hH@4PBtuUFE*kj~2 z7e=lLw9>gqUP7wPYW4I)CM$ZOak>JWn;O)={Gp=Q=h}_ta4G5 zmcdsHzZ&X5Vyd`RQB#D4x?|&Gg#0Sz^Xu_}JuOnwf(drBAwTGAZWSt}{Um|`vZ`!W zyAEKVCXTVO~<8a1ODH>(sDdbL&=|6}awQ+PzRGX$K)TB2&j$ zk1B3xSocq)K?*K)jKAHI@{PFr&c(YPSVC+xCi0A*1F$aGKpI2%L-Q5SCt^n54hMmu z5=PcMp~Y&m_HJnDTz-*eCfzeqpz)2Q`+owAdEdk~COlLg`;cSQI@ydAc3!tQrtDkT z@?3)!`HKWTcTgsFqIe2_tcX>?8+^Z=H1cAy;JI59rQMb}L3;#J0&mYAoBF9tQIlNqVrSjnj%WR_1bXB84eQI%dQ5pB7UIsVv#FcPP%}Z$jL+U4a0oK7!#tRPoig2;E zB9lYz`nFG2dHgth$bNuRwg8CP2Ra<1d#=MuGvoFUqb`ts~ft{I#5korl(3=;1*4GdpX5(Hp znb!?t=vYpcRz5k&t!0j84BdzI=A>C;Cz`O3D$&N@-d-h(-zsma^I;%Jk<;&1KtJPo zaSns>tut)_I*L2hgbcrg0>&j3SCeT&5}FNiZ~zGGjggpt*obNI6!B?+AF8F?y4zKA ze9WGAu`i0TT=`2R?f~KHj6g1zPd_^<^X$g&tF%)#Yr zLwwaG5n8w7K`!Z2o|$_wtF~>2bH+9F9~pP+$WU8yccbOmvD$Pplr+f23(hgFCw9x% zm7SCh_MM{9%;M1B>HX+ajQQs!a%q*D->hW%erQs6tFh;YHi)!glvJ>bLyq$xkEXI# zSL?P@lfh|3AEa}Le}m5>SVzv(Celd9jcD50yv$2|9_Wfp6Sc?A#uetUyhlxQ>xSUwB9Ay*c-?G(jGv96r8CI};Qx^Arp}ZlI z$9a^8^~T^Nd@T75WTGK*eB=|u_PcS35a`i`!a4~BtH%q)!Md&JrBbgE1=~UKd_fX| zs+ouSaHcF>N=(|yxj5rq!w;8zBv*pSSlEyA9l}DqP&Yk=0~mBL*dMRpvzEp|$3xGX_&7S@MtuM@g->v$(K)gX8ZumB>c}T_^1*KIYEAIE@nGmtC7X zIV{%ZNo>m=HOI#zF@SUFLUJ|puqEU-}WJgqJ;wkWz){zQZi<)ml}0tKO>y90SB1cszGG!|+ZR&(s0t*B^Tj*7r4)1uSL@8glr29==1S=#71@=V*U zdycK9ORsrSb_$kU1Qpj6piK(x89`hSjdUbEfx3@2T;)B+JBE3teVGtRYeu3@3 z)ksgL4!UEycndLRPrO%RKKOoRjH+z-@Hc!yH8iRSo>ZitzgX}2e*?_)`O>j|>$P7% zbhV+trUqJ9!dGDH=D#?}t9w85;(Fy}(=C;vS$(}TH z<6m@J1yQ}BT+t-;B7Ml#wKU7?`PiX}s&oNnAN_kMjY{}>-#=j$81oAW*}?;c*QJ73 zAuTO&WQny%VRZaR^SYTvU9P1eXoZE4OOl0AW@S+5t!j&>0*2Hz=PZC<_``)h+g#meeeUo? zVV%! zugcZmizxqJex&22&yW9d0sfb&Iu+IbSlzdBl8Ue9o%&G%E++Sq~ zcxKc>b-|IwB$Ia z;X?_E5pmhAwM81|slI>)!{ujgExdBM@kG4=YJeHD4%dDb0iphXNr05 zbXv+4V}>oOLJx<(;@jD;Tq$>)m&Q#>`J9#OblB2X=MBX7sZ#wWJBzAlbf4J(t zgj`J+FX)xD*D$-yNTrhGpbYLF4Q9q1Npe$BeTg${aGVtb>YCXc*LL3#N*1!caV0_7 zoj7;Jx@Fg5DLqNVX5wHVk2fwDCyaMTRHY~>;m568C>HFb>CQ@>$If3@t^{f=1|Kss zE7siZ*vs1K&B+ez4=pyEqC69P({&Z*{B?pR+N2M*LCVkWM)7KAHT#>MG`d`(dgTHM za!Rp{Yu{cq0NMnJwDzb|9HNo>-46OdpCWBR3&%un&5prfoKV2+`tpnY_di*kS^^|dzPUPg{%gm_T>^cYquPz$AmKSogaM)E$!VUG(MWZ}Tw zKe~iBXW_K$L!8L7%;G49h?o(0P1gWE8-AGkK^!G)&YUp0ek?BB92P+KUke(rOeYNN zltOiL@Xr4Jj$@ab3{iCv)?Owfjr1@CIUL0qeEI=pBd|eU^?^2>4uqju)xXW`q#1IA zz3iObb$~`caYdX|2vF|DU_rvw{$n^^4mNVo*V|l$fL0D(MhqQbwfl+rld z0$a5cz+kcm*+k^-S;J{Q!L`Hr2i*1K&Gr;-)FtyV+#qmw=%(qdK0I0fnXBn9sJwvEFfJrB0{&d#g}gv!N&#O&+sOppabdB zLaqz-y&C>)>!md*{w28br$+~>A}wAfVf}t-(0S$KhSIKkIxGt{$6B+s%;Y+960}ml zXQ_oJ%_rqWQMOElt-P%~Wr`|L23?1Z_k^R$h=1XNl`MX&9NGlLm|Sa{N{$I|8-Fha zR6PEM2HvvhW~2 zD}Klj!#O$`#YFY05~-`DTy@YPMEu!xc!h1s!x_2X)EK)xiTiwXp={E~5&fO-QzdyoDJcf!Hja z6t|)!82EBxqVOH`V0|$dK%kI{UMZR{)XL%+ScntYyp>7j1p?xTXIHrGFg$l{<#@cd z*d}m>XGEwHM;ZsEE4z%!I;TsZ@Bj zz)+HCZ@o*v-j&`DE75>eftn+9zjzem@Rnhdc9GiD8A~_NHVaD}@HTZg3)?vKU>-R( zl(SM;FHB2|W_A#x6>`Yi?Q*xj+X~IGE219sJ`qP|Il$C z3C^g~&5PPy+gt~&U@R) z8XnuIpqNY(i8I-{d$BFmv4_eOGZ|BSu|c*iG^1-4CR;Z0vp3oRn;1ivbJNl#3AB)@ zuiA5+W`IwFF?byre#vH?EuRL)CznG$+RMlhQEr~sTnJwz<;kc}xX|;|*9N-}fQnu= z`g)u(O&&cAFPjo`z#Cu2>XfV!+XKeJfx=FPTd^odV3NyJf&Jn(XQl+Y9ghMs4^{PE zerb)vKxleLDrIhrj41o|{hDib zD&cFqf@GUkG^M|{a_2pQkJllWmUe(P)5F-K2Jv1cLkXb5swsXk%-yd4a6&cs)H`4? z&ji}8RJ@fC5?xMNX2ejl-I3hE4$f-ct55iaU(r46&Cv_|Sq$`jiKw?5Yg1@Y8xau3 z=+6awg+!$s!4<%h&$q!-0Yex8>=2cp+PH5XD-n#(pE)+cCy z%UqMHjg8TLAfd_pE+wcc(_|;mR)#nXuyxZYV-|8>I0Ve*gXivw z(RI8Jc@TJ1dqBf9Rq;JtvXJ{bheCmev6hz!vR17wI_Gg5#~WRD%k}w*s`innOKM5} zMc*xbweY%Y!7?>^C`NO5>xC+xclk7!*Ho6DNZKl}k zo7mvtmtZ}fA(N_@lI4k-|D(M#k7jdk_r5-TTF-8ao!voe+?!pknyQMTVeblxqS&Z0 zRW(;)orEt8%rxznX1``TxOO0fv zY$wk1PTeoy>5_wDuNsXv?XEe{%|+{qd{zbFmc&nRB=nQJURMw0lqo#>(kFG?A~>~%Tqs?u8&ucm*z`vEMdiJd@Q+r z7GCWZA^lS;URZ^}=I!UoKmf;&qo|+LB<%M`T#`mM*E6;nD?O~V`S?^}-TUjJS{>wm zcbvq{N5M!)$4B)F{C=V?wBeoQaEI?gm4y|#>G6|Pt-^e@gsX}|)f=+&z3U~Z=%L;U zCOG!mg-&URQxf4$#%zE2YQ&P_X#VIICc*>{G{kh_>73e74$Mx7ec z24)DC!RNpu&kOnkv*8F>4-g`NOv8c$IQ&+yOjQ_+dEi5VKyE&<@ERDN>~|%!n97u! z==Yo|FiR_cndl1ptG;eb9rseLsS4Zd+*A!`k4{Kq-CAxSX6IuH2CpH1zQulHU&skG zGJFS=1(|%sR2+Vx!;d7axICD1oxX`2H3&3JKu4Ph%6c+s$@vR~MQd~;pQUh*6d%)H z`tF6Oz#=_+4}9d{4w2TTsD@B?vUW&9b0Kv2=lUklhNAa_a4y(4a8ZGpT5yc-p)eqy zQ~ODhePm>5@*<|SrD)p9OTTOLOW{sV^k7XKAK z=xrG8aui?Z z71XWWulnmYs*8wzRM5yQQidIGNK$)clBW4;l?D% zePhvHl03Vd93_hznkbY21O6SiyYqeaH@zSAFim8I?>XVGB1%!GCA6F8pSzxQce{Z1 z3h(_i8ZW)WnNVl4by{<(YkbMs^*0cU=nIrSC0v+N5dbx_~4 zd`~-S*%RHTjj|v2x9|$KCqN(*w$rT-q1?KNem&CdjK->m{`G1*2T(`ts!(M5jc?CY z>UPlX8QD=rKdqin7ZSM{zB!U?I7iNZqr&wijP8US=KZlb5tGfksU-EtXR!n7u-CbX zw}L>t(poMrJf&Z8WTilLY<5ZU-S$m45QDF@U|B6}(&}-(4?^a(1)2SC&SHfn-|qsl zFiu2uePpj|+hRBv*Vi@Bi!XC*LVJuau@K>{kqh20>`luv5WMM>Ev3kr7b%-^n?Pcf z=b~>OmbvbJQgZJeWSHrY>Zvtd2Q^zqA*_)Q2y)RfjT!2-rY{TL#KUc}e|A!3zd^X^ zSoUNX2(3|JOHIIrP;a>5e}HZ|8#8d#A-{5ZQ7dQdaKF*S65)iH(ip|PcDSzRxYvnR znYviX_~N5@5jDp&t`gH+UW9^!2Ei}l=Sa;VQ(q8SQ`CEduFIbCbAu~l;gVCC$1k1&in=In9EA`%% zyN;KEwBcn6KSnIld4;i1EUteqoiqJc3T5MgIKZu=xKp3^3KK4cIpw@0>Z#xLPVu8p zC*?sSQ7rHwWu1()1eUeKYHL<8(oY?E@3=IoB&NY4gaG>3d>{juymmi}oUNKWCivTd zN<|~W+6e*~-IV5XXk~_Z(F|})XnGouyBSZ#Z)NYUe%3jx@+P>46>lJ!U3)Y+iq%Zu z9w|?zb-T}$z;uqQy|ve4({;$&O*~O=>^V}o#h9Z-a3;l})T#i%jR5v;NUuq&G!P=w zT7)NA9W}gFc=Q>*PlDrc7usg{*By#Q-IIwb*FG!(icHGRZpncCZQ9hja^*_l_A`D$ zi51|BwPUARG>26F-{6YB%@+SH(fFTp$epZoiBGVa`JMCM({=z}1Us!^2fV}oMdmLVrWjQ?~y$iT=OiUEcwZE}*JDs%RHee!fiO;)6{vFTB3B zW2ZRYqsT)+%1u0_AYI~nG2xrIYeK|{#E3wiMh=Pls2w@GAK3}j!Bww9+0ASW*+8cb zWrF#R-}e)fyVF9(b=mNJ8QOl?=92fJA5%65SI%I$2el4jf-(PEI9l~nk)Hz<=PFu> zhkH?KI8K^QpSJfxw4IMAyk(F2Q5F0na17tHmcgz=XN}n^a<|oMZEH@OnDX5|yY6>naxCND!Zqh+EY%-96up|wgNU&wsA8>OT*`}O!L z>-kB(4;Ymh;FOp*4(wYFU6^q(I^2kjf&o=9*6RZ|W5A3%MAwOA4)HRE=^WyKrPl=G zUwjX9E+4sqnKEmw6A=@c{Tii2;_h!H6J(PNIPdyWvz7N2nEByk&^qFud>`OdM^%Z4 zr3hbmnqXJ(itm;|)4Qz&CFo9`;1y1ePQ>eaQBBM`LzLB8>pK-c7vJ16+ZRA%O^*hN zdWHWCBrg|^2;o~+8H;@yUXJ0INpSkE5NLa82U8Ao*kUp;WU+Y(Y{X;|5`b8~uw~FFn@sav< zTViz!S^;!0VFk1}U(+Q3XiPAaYHfNcF4nx7rV(J>=fQa_kN}v|m~PN{pK##TsDk6( zgX=<9%*wANVkn z$IPi2(Pl(I1j0T75mrN9P)-ZEQ`Vr+(yY-bX|>^aobP@l9gF%lyHz0)NQ*uCIy^}e z1P?VWNB^*6ZAE0o3psJ9R#B!p+;K(m#0@0XA2|}C z$Pj{t&b4J;2?AQiioY!3*E8-8|8%wqx~Sp;CV4~%~!;)>QZDtl02AK}BI&|RCxZMazYQW?jK)b&q5 z#eWt#@bX>IQ?BuZjK21DoorMd8NCL#(>a-asW}{y*J-XKCDN54H~T)MG@{Tl*X4JC zp2cm7zTeE?=2?U*?;=sre`-9*9?(`-9^9D0x9y=aB|#ur{({2tp@Z4#J!m6uw!Xme zU_V7i>ssx-xdjyi2W8HX#tD9bkx^;)?J{>1cnv41tq~zw-)+yOs|q{p5m~QJ3Rpl!O*SuRo-rg0YT>xQw8Lh+QT-NoIw%h(CYFNdo?bXP>+O0 zQU_Q|tJ(uXWi4Ewx7F~c14p841z>Of2zzhiOv-mlFm~Eo1?kcf3QK}Z4TR#uL5*v< zwsyOkmmiHJCf0tn-H^&d&SSOMu$#hPkMph15S4hhS_y_EhrQB(L?81XpX2N~g`$cd zH>hS3_z+gH83S%Ud1EWD33rjZlzCT!UWl3W+un{jS2q~G&7GP=PFST*%|yk(SeE34 zvs0@%Nf$ts%sE+-K=WEwW-JLD_|*^&rf*l0Oz?Tj+Z8uC7>IsF?Dez zU*Qs%lY8EPc4MqZ-Q5>nOu-CJuXuE|jyl*K+tZvTgT1lW2krg|{Vf3OU z#YR|UMISTx{ril3ZeuDXHe5Gvlf3fAD><(WGOAuYj*NTusEFYuZtC%GyW04g(b(d8;NF?@5KBMaX-h zQ2KUX{OmsZZr`mr^`YB=!6_Ug__rBh18sk(^P&Vu0JDrD!4Z%S`ZnfvhURq72wYrs zo+u|6j~2QzWNT47+`3cWu<~x|T<10@DIH11p#qRQ=>097<`l~bt5Uj}RXO_iVg;Q8+Ul(N-+EC#!hU*55z4{uIk|qU3Nl03qxY1WRkOv-$uZ zBRZz8>PU?s$s79<$4_a=E-zuD>Up5xg*o7xUYOLql}_}t+woBwKPRgGrA~Mz@V0!o z^^7&xEcPY(0mxh!45@W3wSLs0&H{ zn*wQYM>82l!!PZoMZ1CqXEsm~6UHJEgq;R7dRZ;-ZX?Z3GU$#GM1HV%7F!(S;2(Cd zT__PoE|vvt`wg;sE9@F8>5@MNk1u957BTla4?iqaGEUdqfZrnXe$lzjY`X?NfB!Mh zdTwyTKZJ5z$XIqFkMWenmYfI`C7mlFVt|s308pbL-HbOPbv}tJR+(K7dts0!n8;7I z8EG&LeEGVu+1`EJTCm9<;RErb(Aa*8 zR9=w@4!P~qibj{U8Z^m*u5_#-hdp#1RIf?M6Rb+}W}it6mm1zIvEOg>xA`(|>!~#d3neKvq{TqejeQ;F0vdc3q?vA*O>R zx2k%W>~UqCTkjdQj_Eb>nw$v62-=$R@vRj9P4Ks;{)JxZw|16^Hj22}>SGMpO3-)@ z!yDfE3vgnZv7Cx9^>n4!?clhXX+ZuK=ySiV^Fsow)1I0;47}YjbSlxH%N|r7 zZ%mWC@Fq$+57trepjtEc_nRf6D+AZKCXd!R=yBv#C7|i6MVA=4+a-!*OgBNXPO4d0 z+BKg;cUS9TROS95+JnYpej@-o zy!Ps1il{Kw@q8e8JE`faW}=+KITTgT&wcH#j^<|0IU55=$Lp3Pb9JJ8tdrS=pmBML zjY@)AwO*ZQZ~WY`r(1vFF`E+M!_^u|{-HTg0WiBEPf+Jqg?wi_uMUF{NyKoqeZ9{7 z+nRE-%@^tKiPUWfQtrSzCwvKhHe_`K=dZJ|hlD{O%v|RIks)u&&r(vC7rL)GO{tZ8 zBypqDXkmN33m{Ns_(2d(5!XvFu~^%j4n4*<9so!{`?BKq{+x6Ht%;crL=fooh1AS9 z>7t3zNQhh77>p&1 zeuM{^z;;D!Eb!g070 zndr0n_ry^Yo(5z5rS6DGfmPk&ZPm+C+`5M34s|%y$?6TBdW^l*yCAx=qs=m4=8w{v zD~yhXp5X4SC0~1)gL=awm}%%0cmOeD;*I92oow=K-oFCr;(p0(Fhv8 zz4^q^pWf?|YunK|B=_K5YrCBRsS^ZJ;x#^3$JzY^8ZPrZ0DB6{%i7;SnmjH~M0eqd5=2-B?qe6Dmm#Lgj;w>kE8doZ$MdZw~XhLtcXQ0RAa8X3Hbs>bray28w z1F0U8hmp9zkRr}wCwx^GyPgMuK#mwC4N|E&1VUW065Xq-jo(E-5LUt!PY(=fV^l%r zxF?`&bY|m%Jyy;_XT5#=;&73xe1kG=T@qZ+lCvss`1@I}_P35SXF_QFc{!og4tX^$ zrkm$MauSgq4!pn#dW3g7mal%{CcxK6ABkS} zXd$hCQ;x~k8(YH;Z?|303t%xsohoJpjIjigPC0u!q}FrtM(#pe%p_syPvuJ#{PkQ| z`Jt(Y-GZSCAn|Vlu~I{xhL@2EjCWfYBqfw8#u*zAWZH{M!n5u-=y}D~Gr)Rx?Cc(d*knjxh+T$Uu zn7@_hPV$_f=Xyx6Tkj2qCb?NSkGcW7joP&wTCM%&32>np%C|39!A|F3$$jgO?P%MH ztumzuTO9Y9E^<7KcA{`lYoX|ufw(k^&1&Wr3TKB(0Mf*0!}$=Cl;sCKXKNTDT`r`~ zO?cI&3XC_q(c10ByHPTe<+of3J+3z zl3A?{NKj7PVHVwRULP`d@xkyvnAQq;=8z|oZy+t7`a|a4^GhGT=#DKS-9#dKgK>O( z`-8?{dP$E;c0<9CIe$Kco2IcQA{Nw3FvIW12x|n90$(29@=oU{Pa-FL=O&xI>#Sjhsaj9($prm?D2mUANJq3Gf)PY`jI1BcXN+gz3+#Wb3MTcyBqtmS;rAsN+3mzpf%iG@` zIyl-^rkPgGW_nyuX7t=b7@pYLyRAgi)O=*=pmSq0&VwPDG#ZA8)W?Up09zy61d~Ej z2x9*EfT|?ogD%xS&%N_)B*@RsZeB5Q(JXCi=D>TltTSon_nPcKLS=U9y3rRAmJSc< z7GBG_c#L+l5H%SV+Yjp~57liER6*X>F+fn-@dg%;spvew!*orAe*HyUL_Kt|Lwb!= zhdu?wRAb}P&g7pd$tfwL`=6q`{-%?MM#Z$I*R+k2A-9e{Q?*cG#28B4V2s%L0jWnp zx9^*aNrF_Mu_r+0>L##TJnP2>fqr(h* z!_G4LvL9JZI9kYC9F$ZoGGg8 zNX-lSFSE}jz~eg2I^8MDb%?++QIPTy)#qkh->F>YA|QCs=>1>i9n+}%%W5qWbvK3Gd!R#*N`J*BQr3K%@C{5-XHC?B!fsAbB-+agvq4T#%1@ zCe-Dc^Thj))G%+unNK3BiNNs#A43namK!!&0rYcDBX_2?Ijq+&OYq)u&%%7x*ZHTd zyV?Z=t`<}nC@+EEXwqHRA>s$U=f=F0d{2caff2#r%br*bbvt5bX*LMUCU-H0E50>Vz;7+Go1JIJ^7f{ElHt4lHs zz>WA2;Yv?{F1`im;?O>q8+5zl3_urCy0;Y1E*dnq=bAU)#R-d&3^rMz2T|z~X8~EF z%fwlF?{o|t5tM2|fj(mvuEYCS&;?YXfR7Uf%rqaSM{tN*as(trL0qto zx1^n`TRm{eq<+_{)>aH)etdjmua2y&;IUe&nj^);8MxNKt0Y17ZzK5s2TSogQxlPA3NTLCgY;Pu-t-ha=!_&*Jx|Me~$k$ArUJ>=*r@cn0{iG%8fxIW2_E$Kb_yP=*b Kq~gxQ=l=mZkMnT= literal 0 HcmV?d00001 diff --git a/docs/en/images/branding-nobrand.png b/docs/en/images/branding-nobrand.png new file mode 100644 index 0000000000000000000000000000000000000000..1fdf78472be43e956b21609f1417d9a6750116ad GIT binary patch literal 33490 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%0A!}VA^ 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 literal 0 HcmV?d00001 From 7c52485c32090222c7b16e1dac8b07b5b18524cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 30 Oct 2020 11:50:29 +0300 Subject: [PATCH 29/40] Added Dynamic JavaScript API Client Proxies document --- docs/en/API/Dynamic-CSharp-API-Clients.md | 2 +- .../AspNetCore/Dynamic-JavaScript-Proxies.md | 92 ++++++++++++++++++- docs/en/docs-nav.json | 4 + 3 files changed, 95 insertions(+), 3 deletions(-) diff --git a/docs/en/API/Dynamic-CSharp-API-Clients.md b/docs/en/API/Dynamic-CSharp-API-Clients.md index 23ec336acf..d92846f5e8 100644 --- a/docs/en/API/Dynamic-CSharp-API-Clients.md +++ b/docs/en/API/Dynamic-CSharp-API-Clients.md @@ -1,4 +1,4 @@ -# Dynamic C# API Clients +# Dynamic C# API Client Proxies ABP can dynamically create C# API client proxies to call remote HTTP services (REST APIs). In this way, you don't need to deal with `HttpClient` and other low level HTTP features to call remote services and get results. diff --git a/docs/en/UI/AspNetCore/Dynamic-JavaScript-Proxies.md b/docs/en/UI/AspNetCore/Dynamic-JavaScript-Proxies.md index 391a63a910..3464d4bc68 100644 --- a/docs/en/UI/AspNetCore/Dynamic-JavaScript-Proxies.md +++ b/docs/en/UI/AspNetCore/Dynamic-JavaScript-Proxies.md @@ -1,3 +1,91 @@ -# Dynamic JavaScript HTTP API Proxies +# Dynamic JavaScript API Client Proxies -TODO \ No newline at end of file +It is typical to consume your HTTP APIs from your JavaScript code. To do that, you normally deal with low level AJAX calls, like $.ajax, or better [abp.ajax](JavaScript-API/Ajax.md). ABP Framework provides **a better way** to call your HTTP APIs from your JavaScript code: Dynamic JavaScript API Client Proxies! + +## A Quick Example + +Assume that you have an application service defined as shown below: + +````csharp +using System; +using System.Threading.Tasks; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace Acme.BookStore.Authors +{ + public interface IAuthorAppService : IApplicationService + { + Task GetAsync(Guid id); + + Task> GetListAsync(GetAuthorListDto input); + + Task CreateAsync(CreateAuthorDto input); + + Task UpdateAsync(Guid id, UpdateAuthorDto input); + + Task DeleteAsync(Guid id); + } +} +```` + +> You can follow the [web application development tutorial](../../Tutorials/Part-1.md) to learn how to create [application services](../../Application-Services.md), expose them as [HTTP APIs](../../API/Auto-API-Controllers.md) and consume from the JavaScript code as a complete example. + +You can call any of the methods just like calling a JavaScript function. The JavaScript function has the identical function **name**, **parameters** and the **return value** with the C# method. + +**Example: Get the authors list** + +````js +acme.bookStore.authors.author.getList({ + maxResultCount: 10 +}).then(function(result){ + console.log(result.items); +}); +```` + +**Example: Delete an author** + +```js +acme.bookStore.authors.author + .delete('7245a066-5457-4941-8aa7-3004778775f0') //Get id from somewhere! + .then(function() { + abp.notify.info('Successfully deleted!'); + }); +``` + +## AJAX Details + +Dynamic JavaScript client proxy functions use the [abp.ajax](JavaScript-API/Ajax.md) under the hood. So, you have the same benefits like **automatic error handling**. Also, you can fully control the AJAX call by providing the options. + +### The Return Value + +Every function returns a [Deferred object](https://api.jquery.com/category/deferred-object/). That means you can chain with `then` to get the result, `catch` to handle the error, `always` to perform an action once the operation completes (success or failed). + +### AJAX Options + +Every function gets an additional **last parameter** after your own parameters. The last parameter is called as `ajaxParams`. It is an object that overrides the AJAX options. + +**Example: Set `type` and `dataType` AJAX options** + +````js +acme.bookStore.authors.author + .delete('7245a066-5457-4941-8aa7-3004778775f0', { + type: 'POST', + dataType: 'xml' + }) + .then(function() { + abp.notify.info('Successfully deleted!'); + }); +```` + +See the [jQuery.ajax](https://api.jquery.com/jQuery.ajax/) documentation for all the available options. + +## Service Proxy Script Endpoint + +The magic is done by the `/Abp/ServiceProxyScript` endpoint defined by the ABP Framework and automatically added to the layout. You can visit this endpoint in your application to see the client proxy function definitions. This script file is automatically generated by the ABP Framework based on the server side method definitions and the related HTTP endpoint details. + +## See Also + +* [Web Application Development Tutorial](../../Tutorials/Part-1.md) +* [Auto API Controllers](../../API/Auto-API-Controllers.md) +* [Dynamic C# API Client Proxies](../../API/Dynamic-CSharp-API-Clients.md) \ No newline at end of file diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index f292226ae3..78b2e8d324 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -422,6 +422,10 @@ "text": "Page Alerts", "path": "UI/AspNetCore/Page-Alerts.md" }, + { + "text": "Dynamic JavaScript API Client Proxies", + "path": "UI/AspNetCore/Dynamic-JavaScript-Proxies.md" + }, { "text": "Client Side Package Management", "path": "UI/AspNetCore/Client-Side-Package-Management.md" From 4c76679941528ed55e86d006d1812fb040c6a52d Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Fri, 30 Oct 2020 12:39:32 +0300 Subject: [PATCH 30/40] Fixed double ')' --- docs/en/UI/AspNetCore/Tag-Helpers/Index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/UI/AspNetCore/Tag-Helpers/Index.md b/docs/en/UI/AspNetCore/Tag-Helpers/Index.md index 54c37af688..f85edb5142 100644 --- a/docs/en/UI/AspNetCore/Tag-Helpers/Index.md +++ b/docs/en/UI/AspNetCore/Tag-Helpers/Index.md @@ -13,7 +13,7 @@ ABP Framework also adds some **useful features** to the standard bootstrap compo Here, the list of components those are wrapped by the ABP Framework: * [Alerts](Alerts.md) -* [Badges](Badges.md)) +* [Badges](Badges.md) * [Blockquote](Blockquote.md) * [Borders](Borders.md) * [Breadcrumb](Breadcrumb.md) @@ -42,4 +42,4 @@ Here, the list of components those are wrapped by the ABP Framework: ## Dynamic Forms -**Abp Tag helpers** offer an easy way to build complete **Bootstrap forms**. See [Dynamic Forms documentation](Dynamic-Forms.md). \ No newline at end of file +**Abp Tag helpers** offer an easy way to build complete **Bootstrap forms**. See [Dynamic Forms documentation](Dynamic-Forms.md). From bb3152c0209578a0e45c3f70a1c86e9f880f4c2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 30 Oct 2020 13:14:59 +0300 Subject: [PATCH 31/40] Initial ASP.NET Core MVC / Razor Pages UI overall document. --- docs/en/UI/AspNetCore/Overall.md | 73 +++++++++++++++++++++++++++++++ docs/en/UI/AspNetCore/Toolbars.md | 2 +- 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 docs/en/UI/AspNetCore/Overall.md diff --git a/docs/en/UI/AspNetCore/Overall.md b/docs/en/UI/AspNetCore/Overall.md new file mode 100644 index 0000000000..11f8314e5f --- /dev/null +++ b/docs/en/UI/AspNetCore/Overall.md @@ -0,0 +1,73 @@ +# ASP.NET Core MVC / Razor Pages UI + +## Introduction + +ABP Framework provides a convenient and comfortable way of creating web applications using the ASP.NET Core MVC / Razor Pages as the User Interface framework. + +ABP doesn't offer a new/custom way of UI development. You can continue to use your current skills to create the UI. However, it offers a lot of features to make your development easier and have a more maintainable code base. + +### MVC vs Razor Pages + +ASP.NET Core provides two models for UI development: + +* **[MVC (Model-View-Controller)](https://docs.microsoft.com/en-us/aspnet/core/mvc/)** is the classic way that exists from the version 1.0. This model can be used to create UI pages/components and HTTP APIs. +* **[Razor Pages](https://docs.microsoft.com/en-us/aspnet/core/razor-pages/)** was introduced with the ASP.NET Core 2.0 as a new way to create web pages. + +**ABP Framework supports both** of the MVC and the Razor Pages models. However, it is suggested to create the **UI pages with Razor Pages** approach and use the **MVC model to build HTTP APIs**. So, all the pre-build modules, samples and the documentation is based on the Razor Pages for the UI development, while you can always apply the MVC pattern to create your own pages. + +### Modularity + +[Modularity](../../Module-Development-Basics.md) is one of the key goals of the ABP Framework. It is not different for the UI; It is possible to develop modular applications and reusable application modules with isolated and reusable UI pages and components. + +The [application startup template](../../Startup-Templates/Application.md) comes with some application modules pre-installed. These modules have their own UI pages embedded into their own NuGet packages. You don't see their code in your solution, but they work as expected on runtime. + +## Theme System + +ABP Framework provides a complete [Theming](Theming.md) system with the following goals: + +* Reusable [application modules](../../Modules/Index.md) are developed **theme-independent**, so they can work with any UI theme. +* UI theme is **decided by the final application**. +* The theme is distributed via NuGet/NPM packages, so it is **easily upgradable**. +* The final application can **customize** the selected theme. + +### Current Themes + +Currently, two themes are **officially provided**: + +* The [Basic Theme](Basic-Theme.md) is the minimalist theme with the plain Bootstrap style. It is **open source and free**. +* The [Lepton Theme](https://commercial.abp.io/themes) is a **commercial** theme developed by the core ABP team and is a part of the [ABP Commercial](https://commercial.abp.io/) license. + +There are also some community-driven themes for the ABP Framework (you can search on the web). + +### Base Libraries + +There are a set of standard JavaScript/CSS libraries that comes pre-installed and supported by all the themes: + +- [Twitter Bootstrap](https://getbootstrap.com/) as the fundamental HTML/CSS framework. +- [JQuery](https://jquery.com/) for DOM manipulation. +- [DataTables.Net](https://datatables.net/) for data grids. +- [JQuery Validation](https://jqueryvalidation.org/) for client side & [unobtrusive](https://github.com/aspnet/jquery-validation-unobtrusive) validation +- [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library. +- [SweetAlert](https://sweetalert.js.org/) to show fancy alert message and confirmation dialogs. +- [Toastr](https://github.com/CodeSeven/toastr) to show toast notifications. +- [Lodesh](https://lodash.com/) as a utility library. +- [Luxon](https://moment.github.io/luxon/) for date/time operations. +- [JQuery Form](https://github.com/jquery-form/form) for AJAX forms. +- [bootstrap-datepicker](https://github.com/uxsolutions/bootstrap-datepicker) to show date pickers. +- [Select2](https://select2.org/) for better select/combo boxes. +- [Timeago](http://timeago.yarp.com/) to show automatically updating fuzzy timestamps. +- [malihu-custom-scrollbar-plugin](https://github.com/malihu/malihu-custom-scrollbar-plugin) for custom scrollbars. + +You can use these libraries directly in your applications, without needing to manually import your page. + +### Layouts + +The themes provide the standard layouts. So, you have responsive layouts with the standard features already implemented. The screenshot below has taken from the Application Layout of the [Basic Theme](Basic-Theme.md): + +![basic-theme-application-layout](../../images/basic-theme-application-layout.png) + +See the [Theming](Theming.md) document for more layout options and other details. + +## Features + +TODO \ No newline at end of file diff --git a/docs/en/UI/AspNetCore/Toolbars.md b/docs/en/UI/AspNetCore/Toolbars.md index 524ef3f18b..3f1185f61b 100644 --- a/docs/en/UI/AspNetCore/Toolbars.md +++ b/docs/en/UI/AspNetCore/Toolbars.md @@ -1,4 +1,4 @@ -# Toolbars +# ASP.NET Core MVC / Razor Pages UI: Toolbars The Toolbar system is used to define **toolbars** on the user interface. Modules (or your application) can add **items** to a toolbar, then the [theme](Theming.md) renders the toolbar on the **layout**. From 2ecee7e148648b3220d3de8d123f77efa09cb909 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Fri, 30 Oct 2020 11:38:31 +0100 Subject: [PATCH 32/40] Updated Blazorise version to 0.9.2-rc1 --- .../src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj b/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj index 1134821b78..47e8b32f00 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj +++ b/framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj @@ -12,8 +12,8 @@ - - + + From c4cc529f39ddaca7f7d76fa9222e0c05b1103d24 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Fri, 30 Oct 2020 11:39:22 +0100 Subject: [PATCH 33/40] Created AbpIdentityBlazorMessageLocalizerHelper instead of extension methods --- ...bpIdentityBlazorMessageLocalizerHelper.cs} | 17 ++++++++---- .../AbpIdentityBlazorModule.cs | 2 ++ .../Pages/Identity/RoleManagement.razor | 7 ++--- .../Pages/Identity/UserManagement.razor | 27 ++++++++++--------- 4 files changed, 32 insertions(+), 21 deletions(-) rename modules/identity/src/Volo.Abp.Identity.Blazor/{AbpIdentityBlazorMessageLocalizerExtensions.cs => AbpIdentityBlazorMessageLocalizerHelper.cs} (57%) diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerHelper.cs similarity index 57% rename from modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs rename to modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerHelper.cs index 72328eb86d..5d858843b8 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorMessageLocalizerHelper.cs @@ -1,18 +1,25 @@ using System.Collections.Generic; using System.Linq; -using Blazorise; +using JetBrains.Annotations; using Microsoft.Extensions.Localization; namespace Volo.Abp.Identity.Blazor { - public static class AbpIdentityBlazorMessageLocalizerExtensions + public class AbpIdentityBlazorMessageLocalizerHelper { - public static string Localize(this IStringLocalizer stringLocalizer, string message, IEnumerable arguments) + private readonly IStringLocalizer stringLocalizer; + + public AbpIdentityBlazorMessageLocalizerHelper(IStringLocalizer stringLocalizer) + { + this.stringLocalizer = stringLocalizer; + } + + public string Localize(string message, [CanBeNull] IEnumerable arguments) { try { return arguments?.Count() > 0 - ? stringLocalizer[message, LocalizeMessageArguments(stringLocalizer, arguments)?.ToArray()] + ? stringLocalizer[message, LocalizeMessageArguments(arguments)?.ToArray()] : stringLocalizer[message]; } catch @@ -21,7 +28,7 @@ namespace Volo.Abp.Identity.Blazor } } - private static IEnumerable LocalizeMessageArguments(IStringLocalizer stringLocalizer, IEnumerable arguments) + private IEnumerable LocalizeMessageArguments(IEnumerable arguments) { foreach (var argument in arguments) { diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs index cebde068c3..7f11267132 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs @@ -32,6 +32,8 @@ namespace Volo.Abp.Identity.Blazor { options.AdditionalAssemblies.Add(typeof(AbpIdentityBlazorModule).Assembly); }); + + context.Services.AddSingleton(typeof(AbpIdentityBlazorMessageLocalizerHelper<>)); } } } diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor index 97001ee6ec..4639f81be2 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor @@ -1,5 +1,5 @@ @page "/identity/roles" -@attribute [Authorize(IdentityPermissions.Roles.Default)] +@attribute [Authorize( IdentityPermissions.Roles.Default )] @using Volo.Abp.Identity @using Microsoft.AspNetCore.Authorization @using Microsoft.Extensions.Localization @@ -7,6 +7,7 @@ @using Volo.Abp.PermissionManagement.Blazor.Components @inherits RoleManagementBase @inject IStringLocalizer L +@inject AbpIdentityBlazorMessageLocalizerHelper LH @* ************************* PAGE HEADER ************************* *@ @@ -84,7 +85,7 @@ - + @L["DisplayName:RoleName"] @@ -120,7 +121,7 @@ - + @L["DisplayName:RoleName"] diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor index 0a61fc70ab..93e0ccaa85 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor @@ -1,11 +1,12 @@ @page "/identity/users" -@attribute [Authorize(IdentityPermissions.Users.Default)] +@attribute [Authorize( IdentityPermissions.Users.Default )] @using Microsoft.AspNetCore.Authorization @using Microsoft.Extensions.Localization @using Volo.Abp.Identity.Localization @using Volo.Abp.PermissionManagement.Blazor.Components @inherits UserManagementBase @inject IStringLocalizer L +@inject AbpIdentityBlazorMessageLocalizerHelper LH @* ************************* PAGE HEADER ************************* *@ @@ -93,7 +94,7 @@ - + @L["DisplayName:UserName"] @@ -103,7 +104,7 @@ - + @L["DisplayName:Name"] @@ -113,7 +114,7 @@ - + @L["DisplayName:Surname"] @@ -123,7 +124,7 @@ - + @L["DisplayName:Password"] @@ -133,7 +134,7 @@ - + @L["DisplayName:Email"] @@ -143,7 +144,7 @@ - + @L["DisplayName:PhoneNumber"] @@ -202,7 +203,7 @@ - + @L["DisplayName:UserName"] @@ -212,7 +213,7 @@ - + @L["DisplayName:Name"] @@ -222,7 +223,7 @@ - + @L["DisplayName:Surname"] @@ -232,7 +233,7 @@ - + @L["DisplayName:Password"] @@ -242,7 +243,7 @@ - + @L["DisplayName:Email"] @@ -252,7 +253,7 @@ - + @L["DisplayName:PhoneNumber"] From 947e06aaead8bf1d160e6a2d1302cb9721924713 Mon Sep 17 00:00:00 2001 From: Mladen Macanovic Date: Fri, 30 Oct 2020 12:20:51 +0100 Subject: [PATCH 34/40] OkButtonText converted to ILocalizableString --- .../Components/WebAssembly/UiNotificationOptions.cs | 6 ++++-- .../Volo.Abp.BlazoriseUI/BlazoriseUiNotificationService.cs | 1 - .../Components/UiNotificationAlert.razor.cs | 7 ++++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiNotificationOptions.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiNotificationOptions.cs index 2d51716cd9..ae8a400368 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiNotificationOptions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/UiNotificationOptions.cs @@ -1,4 +1,6 @@ -namespace Volo.Abp.AspNetCore.Components.WebAssembly +using Volo.Abp.Localization; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly { /// /// Options to override notification appearance. @@ -8,7 +10,7 @@ /// /// Custom text for the Ok button. /// - public string OkButtonText { get; set; } + public ILocalizableString OkButtonText { get; set; } /// /// Custom icon for the Ok button. diff --git a/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiNotificationService.cs b/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiNotificationService.cs index be2d5c9d51..05c1cd5163 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiNotificationService.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiNotificationService.cs @@ -67,7 +67,6 @@ namespace Volo.Abp.BlazoriseUI { return new UiNotificationOptions { - OkButtonText = localizer["Ok"], }; } } diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/UiNotificationAlert.razor.cs b/framework/src/Volo.Abp.BlazoriseUI/Components/UiNotificationAlert.razor.cs index 691b6a7a1a..67c890f922 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Components/UiNotificationAlert.razor.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/UiNotificationAlert.razor.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Blazorise.Snackbar; using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; using Volo.Abp.AspNetCore.Components.WebAssembly; namespace Volo.Abp.BlazoriseUI.Components @@ -24,6 +25,8 @@ namespace Volo.Abp.BlazoriseUI.Components [Inject] protected BlazoriseUiNotificationService UiNotificationService { get; set; } + [Inject] protected IStringLocalizerFactory StringLocalizerFactory { get; set; } + protected virtual SnackbarColor GetSnackbarColor(UiNotificationType notificationType) { return notificationType switch @@ -50,7 +53,9 @@ namespace Volo.Abp.BlazoriseUI.Components Title = e.Title; Options = e.Options; - SnackbarStack.Push(Message, GetSnackbarColor(e.NotificationType)); + var okButtonText = Options?.OkButtonText?.Localize(StringLocalizerFactory); + + SnackbarStack.Push(Message, GetSnackbarColor( e.NotificationType ), okButtonText); } public virtual void Dispose() From f55a80c569e59eee23031c8a7981e0f6d0972425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 30 Oct 2020 14:29:56 +0300 Subject: [PATCH 35/40] Created Overall AspNet Core documentation. --- .../UI/AspNetCore/Libraries/DatatablesNet.md | 3 - docs/en/UI/AspNetCore/Overall.md | 90 ++++++++++++++++++- docs/en/docs-nav.json | 6 +- 3 files changed, 93 insertions(+), 6 deletions(-) delete mode 100644 docs/en/UI/AspNetCore/Libraries/DatatablesNet.md diff --git a/docs/en/UI/AspNetCore/Libraries/DatatablesNet.md b/docs/en/UI/AspNetCore/Libraries/DatatablesNet.md deleted file mode 100644 index a5a66e9e19..0000000000 --- a/docs/en/UI/AspNetCore/Libraries/DatatablesNet.md +++ /dev/null @@ -1,3 +0,0 @@ -# ABP Datatables.Net Integration for ASP.NET Core UI - -TODO \ No newline at end of file diff --git a/docs/en/UI/AspNetCore/Overall.md b/docs/en/UI/AspNetCore/Overall.md index 11f8314e5f..a499cd93a3 100644 --- a/docs/en/UI/AspNetCore/Overall.md +++ b/docs/en/UI/AspNetCore/Overall.md @@ -4,7 +4,7 @@ ABP Framework provides a convenient and comfortable way of creating web applications using the ASP.NET Core MVC / Razor Pages as the User Interface framework. -ABP doesn't offer a new/custom way of UI development. You can continue to use your current skills to create the UI. However, it offers a lot of features to make your development easier and have a more maintainable code base. +> ABP doesn't offer a new/custom way of UI development. You can continue to use your current skills to create the UI. However, it offers a lot of features to make your development easier and have a more maintainable code base. ### MVC vs Razor Pages @@ -68,6 +68,92 @@ The themes provide the standard layouts. So, you have responsive layouts with th See the [Theming](Theming.md) document for more layout options and other details. +### Layout Parts + +A typical layout consists of multiple parts. The [Theming](Theming.md) system provides [menus](Navigation-Menu.md), [toolbars](Toolbars.md), [layout hooks](Layout-Hooks.md) and more to dynamically control the layout by your application and the modules you are using. + ## Features -TODO \ No newline at end of file +This section highlights some of the features provided by the ABP Framework for the ASP.NET Core MVC / Razor Pages UI. + +### Dynamic JavaScript API Client Proxies + +Dynamic JavaScript API Client Proxy system allows you to consume your server side HTTP APIs from your JavaScript client code, just like calling local functions. + +**Example: Get a list of authors from the server** + +````js +acme.bookStore.authors.author.getList({ + maxResultCount: 10 +}).then(function(result){ + console.log(result.items); +}); +```` + +`acme.bookStore.authors.author.getList` is an auto-generated function that internally makes an AJAX call to the server. + +See the [Dynamic JavaScript API Client Proxies](Dynamic-JavaScript-Proxies.md) document for more. + +### Bootstrap Tag Helpers + +ABP makes it easier & type safe to write Bootstrap HTML. + +**Example: Render a Bootstrap modal** + +````html + + + + Woohoo, you're reading this text in a modal! + + + +```` + +See the [Tag Helpers](Tag-Helpers/Index.md) document for more. + +### Forms & Validation + +ABP provides `abp-dynamic-form` and `abp-input` tag helpers to dramatically simplify to create a fully functional form that automates localization, validation and AJAX submission. + +**Example: Use `abp-dynamic-form` to create a complete form based on a model** + +````html + +```` + +See the [Forms & Validation](Forms-Validation.md) document for details. + +### Bundling & Minification / Client Side Libraries + +ABP provides a flexible and modular Bundling & Minification system to create bundles and minify style/script files on runtime. + +````html + + + + + + +```` + +Also, Client Side Package Management system offers a modular and consistent way of managing 3rd-party library dependencies. + +See the [Bundling & Minification](Bundling-Minification.md) and [Client Side Package Management](Client-Side-Package-Management.md) documents. + +### JavaScript APIs + +[JavaScript APIs](JavaScript-API/Index.md) provides a strong abstractions to the server side localization, settings, permissions, features... etc. They also provide a simple way to show messages and **notifications** to the user. + +### Modals, Alerts, Widgets and More + +ABP Framework provides a lot of built-in solutions to common application requirements; + +* [Widget System](Widgets.md) can be used to create reusable widgets & create dashboards. +* [Page Alerts](Page-Alerts.md) makes it easy to show alerts to the user. +* [Modal Manager](Modals.md) provides a simple way to build and use modals. +* [Data Tables](Data-Tables.md) integration makes straightforward to create data grids. + +## Customization + +There are a lot of ways to customize the theme and the UIs of the pre-built modules. You can override components, pages, static resources, bundles and more. See the [User Interface Customization Guide](Customization-User-Interface.md). \ No newline at end of file diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 78b2e8d324..2c55767ecc 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -400,8 +400,12 @@ "text": "User Interface", "items": [ { - "text": "ASP.NET Core MVC / Razor Pages", + "text": "MVC / Razor Pages", "items": [ + { + "text": "Overall", + "path": "UI/AspNetCore/Overall.md" + }, { "text": "Navigation / Menus", "path": "UI/AspNetCore/Navigation-Menu.md" From 0f335da0e1712104d39197345f153cf103194171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 30 Oct 2020 16:42:55 +0300 Subject: [PATCH 36/40] Fix title --- docs/en/Domain-Services.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/Domain-Services.md b/docs/en/Domain-Services.md index 7eb6c917cd..9f4a5ee544 100644 --- a/docs/en/Domain-Services.md +++ b/docs/en/Domain-Services.md @@ -1,3 +1,3 @@ -# ABP Documentation +# Domain Services -TODO! \ No newline at end of file +TODO \ No newline at end of file From 29473ae70e5464cc06d242e680add81bae85255d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 30 Oct 2020 16:43:07 +0300 Subject: [PATCH 37/40] Update Background-Workers-Quartz.md --- docs/en/Background-Workers-Quartz.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Background-Workers-Quartz.md b/docs/en/Background-Workers-Quartz.md index e1d8287019..ce82222c14 100644 --- a/docs/en/Background-Workers-Quartz.md +++ b/docs/en/Background-Workers-Quartz.md @@ -37,7 +37,7 @@ public class YourModule : AbpModule ```` > Quartz background worker integration provided `QuartzPeriodicBackgroundWorkerAdapter` to adapt `PeriodicBackgroundWorkerBase` and `AsyncPeriodicBackgroundWorkerBase` derived class. So, you can still fllow the [background workers document](Background-Workers.md) to define the background worker. -> `BackgroundJobWorker` checks todo jobs every 5 seconds, but quartz will not block when long time jobs are executing. So,after Added Quartz background worker integration, you also need to add [Quartz Background Jobs](Background-Jobs-Quartz.md) or [Hangfire Background Jobs](Background-Jobs-Hangfire.md) to avoid duplicate execution jobs. +> `BackgroundJobWorker` checks jobs every 5 seconds, but quartz will not block when long time jobs are executing. So,after Added Quartz background worker integration, you also need to add [Quartz Background Jobs](Background-Jobs-Quartz.md) or [Hangfire Background Jobs](Background-Jobs-Hangfire.md) to avoid duplicate execution jobs. ## Configuration From 73b376aa7a84f9b820cfc8a177de696ff44776a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 30 Oct 2020 16:56:12 +0300 Subject: [PATCH 38/40] Organize Blazor UI docs. --- docs/en/UI/Blazor/Localization.md | 2 +- docs/en/UI/Blazor/Message.md | 3 +++ docs/en/UI/Blazor/Notification.md | 3 +++ docs/en/UI/Blazor/Overall.md | 4 +++- docs/en/UI/Blazor/Services/Notification.md | 3 --- docs/en/UI/Blazor/Settings.md | 2 +- docs/en/docs-nav.json | 14 +++++++++++++- 7 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 docs/en/UI/Blazor/Message.md create mode 100644 docs/en/UI/Blazor/Notification.md delete mode 100644 docs/en/UI/Blazor/Services/Notification.md diff --git a/docs/en/UI/Blazor/Localization.md b/docs/en/UI/Blazor/Localization.md index e6cf761ce3..23b31a5a24 100644 --- a/docs/en/UI/Blazor/Localization.md +++ b/docs/en/UI/Blazor/Localization.md @@ -1,3 +1,3 @@ -# Blazor UI: Localization +# Blazor: Localization Blazor applications can reuse the same `IStringLocalizer` service that is explained in the [localization document](../../Localization.md). All the localization resources and texts available in the server side are usable in the Blazor application. \ No newline at end of file diff --git a/docs/en/UI/Blazor/Message.md b/docs/en/UI/Blazor/Message.md new file mode 100644 index 0000000000..8373bd1d82 --- /dev/null +++ b/docs/en/UI/Blazor/Message.md @@ -0,0 +1,3 @@ +# Blazor: UI Message Service + +TODO \ No newline at end of file diff --git a/docs/en/UI/Blazor/Notification.md b/docs/en/UI/Blazor/Notification.md new file mode 100644 index 0000000000..ef8945347b --- /dev/null +++ b/docs/en/UI/Blazor/Notification.md @@ -0,0 +1,3 @@ +# Blazor: Notification + +`UiNotificationService` is used to show toastr style notifications on the user interface. The documentation is in progress... \ No newline at end of file diff --git a/docs/en/UI/Blazor/Overall.md b/docs/en/UI/Blazor/Overall.md index 74c469e3c4..8af0e2fd0d 100644 --- a/docs/en/UI/Blazor/Overall.md +++ b/docs/en/UI/Blazor/Overall.md @@ -1,6 +1,8 @@ # Blazor UI for the ABP Framework -The detailed documentation for the Blazor UI is in progress. However, you can follow the documents below to start with the Blazor UI today. +## Getting Started + +You can follow the documents below to start with the ABP Framework and the Blazor UI: * [Get started](https://docs.abp.io/en/abp/latest/Getting-Started?UI=Blazor) with the Blazor UI for the ABP Framework. * [Web Application Development Tutorial](https://docs.abp.io/en/abp/latest/Tutorials/Part-1?UI=Blazor) with the Blazor UI. \ No newline at end of file diff --git a/docs/en/UI/Blazor/Services/Notification.md b/docs/en/UI/Blazor/Services/Notification.md deleted file mode 100644 index 0e5ababa0e..0000000000 --- a/docs/en/UI/Blazor/Services/Notification.md +++ /dev/null @@ -1,3 +0,0 @@ -# Blazor UI Notification - -`UiNotificationService` is used to show toastr style notifications on the user interface. The documentation is in progress... \ No newline at end of file diff --git a/docs/en/UI/Blazor/Settings.md b/docs/en/UI/Blazor/Settings.md index 276462c411..ad03dce5e2 100644 --- a/docs/en/UI/Blazor/Settings.md +++ b/docs/en/UI/Blazor/Settings.md @@ -1,3 +1,3 @@ -# Blazor UI: Settings +# Blazor: Settings Blazor applications can reuse the same `ISettingProvider` service that is explained in the [settings document](../../Settings.md). \ No newline at end of file diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 2c55767ecc..2301e845a3 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -560,8 +560,20 @@ "text": "Services", "items": [ { - "text": "Overall", + "text": "Localization", + "path": "UI/Blazor/Services/Localization.md" + }, + { + "text": "Settings", + "path": "UI/Blazor/Services/Settings.md" + }, + { + "text": "Notification", "path": "UI/Blazor/Services/Notification.md" + }, + { + "text": "Message", + "path": "UI/Blazor/Services/Message.md" } ] } From 740fb05644d1097877bbb34446956958e4dc36bd Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Fri, 30 Oct 2020 16:57:52 +0300 Subject: [PATCH 39/40] Update CliHttpClient.cs --- .../src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Http/CliHttpClient.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Http/CliHttpClient.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Http/CliHttpClient.cs index bd3037d830..3712d0be13 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Http/CliHttpClient.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Http/CliHttpClient.cs @@ -17,7 +17,8 @@ namespace Volo.Abp.Cli.Http { public static TimeSpan DefaultTimeout { get; set; } = TimeSpan.FromMinutes(1); - public CliHttpClient(TimeSpan? timeout = null) : base(new CliHttpClientHandler()) + public CliHttpClient(TimeSpan? timeout = null) + : base(new CliHttpClientHandler()) { Timeout = timeout ?? DefaultTimeout; @@ -98,4 +99,4 @@ namespace Volo.Abp.Cli.Http } } -} \ No newline at end of file +} From ec72a386620bb517988e3aa17c214348e74a60cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 30 Oct 2020 17:22:41 +0300 Subject: [PATCH 40/40] Update docs-nav.json --- docs/en/docs-nav.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 2301e845a3..732e86bd41 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -561,19 +561,19 @@ "items": [ { "text": "Localization", - "path": "UI/Blazor/Services/Localization.md" + "path": "UI/Blazor/Localization.md" }, { "text": "Settings", - "path": "UI/Blazor/Services/Settings.md" + "path": "UI/Blazor/Settings.md" }, { "text": "Notification", - "path": "UI/Blazor/Services/Notification.md" + "path": "UI/Blazor/Notification.md" }, { "text": "Message", - "path": "UI/Blazor/Services/Message.md" + "path": "UI/Blazor/Message.md" } ] }