Browse Source

Merge branch 'dev' into cli-template-build-symbols

pull/8168/head
Yunus Emre Kalkan 6 years ago
committed by GitHub
parent
commit
4b9928ff96
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json
  2. 2
      framework/src/Volo.Abp.AspNetCore.Components.Server/Volo.Abp.AspNetCore.Components.Server.csproj
  3. 4
      framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/AbpAspNetCoreComponentsServerModule.cs
  4. 92
      framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/Extensibility/BlazorServerLookupApiRequestService.cs
  5. 12
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/PageHeader.razor
  6. 76
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/PageHeader.razor.cs
  7. 9
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/IPageToolbarContributor.cs
  8. 9
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/IPageToolbarManager.cs
  9. 12
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/PageToolbar.cs
  10. 21
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/PageToolbarContributionContext.cs
  11. 9
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/PageToolbarContributor.cs
  12. 8
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/PageToolbarContributorList.cs
  13. 9
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/PageToolbarDictionary.cs
  14. 71
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/PageToolbarExtensions.cs
  15. 27
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/PageToolbarItem.cs
  16. 9
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/PageToolbarItemList.cs
  17. 40
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/PageToolbarManager.cs
  18. 53
      framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/SimplePageToolbarContributor.cs
  19. 20
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Extensibility/EntityActions/EntityAction.cs
  20. 12
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Extensibility/EntityActions/EntityActionDictionary.cs
  21. 10
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Extensibility/ILookupApiRequestService.cs
  22. 25
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Extensibility/TableColumns/TableColumn.cs
  23. 12
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Extensibility/TableColumns/TableColumnDictionary.cs
  24. 69
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Extensibility/WebAssemblyLookupApiRequestService.cs
  25. 85
      framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs
  26. 261
      framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiObjectExtensionPropertyInfoExtensions.cs
  27. 121
      framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor
  28. 50
      framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor.cs
  29. 6
      framework/src/Volo.Abp.BlazoriseUI/Components/EntityAction.razor
  30. 6
      framework/src/Volo.Abp.BlazoriseUI/Components/EntityAction.razor.cs
  31. 2
      framework/src/Volo.Abp.BlazoriseUI/Components/EntityActions.razor
  32. 2
      framework/src/Volo.Abp.BlazoriseUI/Components/EntityActions.razor.cs
  33. 11
      framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/CheckExtensionProperty.razor
  34. 32
      framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/CheckExtensionProperty.razor.cs
  35. 17
      framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/DateTimeExtensionProperty.razor
  36. 37
      framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/DateTimeExtensionProperty.razor.cs
  37. 33
      framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/EnumHelper.cs
  38. 29
      framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor
  39. 20
      framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor.cs
  40. 18
      framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/LookupExtensionProperty.razor
  41. 109
      framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/LookupExtensionProperty.razor.cs
  42. 13
      framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/SelectExtensionProperty.razor
  43. 61
      framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/SelectExtensionProperty.razor.cs
  44. 14
      framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TextExtensionProperty.razor
  45. 33
      framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TextExtensionProperty.razor.cs
  46. 13
      framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TimeExtensionProperty.razor
  47. 37
      framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TimeExtensionProperty.razor.cs
  48. 29
      framework/src/Volo.Abp.BlazoriseUI/Components/PageHeader.razor.cs
  49. 7
      framework/src/Volo.Abp.BlazoriseUI/Components/ToolbarButton.razor
  50. 25
      framework/src/Volo.Abp.BlazoriseUI/Components/ToolbarButton.razor.cs
  51. 40
      framework/src/Volo.Abp.BlazoriseUI/ObjectExtensionPropertyInfoBlazorExtensions.cs
  52. 11
      framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj
  53. 1
      framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj
  54. 12
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs
  55. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs
  56. 62
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/SourceCodeDownloadService.cs
  57. 5
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs
  58. 21
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/PackageProjectBuildPipelineBuilder.cs
  59. 7
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildContext.cs
  60. 10
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/INugetPackageInfoProvider.cs
  61. 1
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleProjectBuilder.cs
  62. 62
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/NugetPackageInfoProvider.cs
  63. 109
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/PackageProjectBuilder.cs
  64. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/SourceCodeTypes.cs
  65. 1
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateProjectBuilder.cs
  66. 165
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs
  67. 81
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionFileModifier.cs
  68. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs
  69. 16
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyLookupConfiguration.cs
  70. 15
      modules/cms-kit/src/Volo.CmsKit.Admin.Web/Menus/CmsKitAdminMenuContributor.cs
  71. 2
      modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Blogs/EfCoreBlogPostRepository.cs
  72. 2
      modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Blogs/EfCoreBlogRepository.cs
  73. 2
      modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Comments/EfCoreCommentRepository.cs
  74. 2
      modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Pages/EfCorePageRepository.cs
  75. 2
      modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogPostRepository.cs
  76. 2
      modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogRepository.cs
  77. 2
      modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Comments/MongoCommentRepository.cs
  78. 2
      modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Pages/MongoPageRepository.cs
  79. 2
      modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Projects/EfCoreProjectRepository.cs
  80. 2
      modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Projects/MongoProjectRepository.cs
  81. 27
      modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs
  82. 93
      modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor
  83. 90
      modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs
  84. 16
      modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleNameComponent.razor
  85. 9
      modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleNameComponent.razor.cs
  86. 115
      modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor
  87. 92
      modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs
  88. 10
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs
  89. 10
      modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs
  90. 3
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/LinkLoginExtensionGrantValidator.cs
  91. 19
      modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/AbpTenantManagementBlazorModule.cs
  92. 85
      modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor
  93. 85
      modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor.cs
  94. 2
      modules/tenant-management/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/EfCoreTenantRepository.cs
  95. 2
      modules/tenant-management/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/MongoTenantRepository.cs
  96. 2
      modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo/Abp/Users/EntityFrameworkCore/EfCoreAbpUserRepositoryBase.cs
  97. 2
      modules/users/src/Volo.Abp.Users.MongoDB/Volo/Abp/Users/MongoDB/MongoUserRepositoryBase.cs
  98. 5
      npm/ng-packs/packages/core/src/lib/services/auth.service.ts
  99. 14
      npm/ng-packs/packages/core/src/lib/strategies/auth-flow.strategy.ts
  100. 4
      npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts

3
abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json

@ -196,6 +196,7 @@
"MultipleDBOptionsExplanation": "The framework can work with any data source, while the following providers are officially developed and supported;",
"SelectLanguage": "Select language",
"LatestArticleOnCommunity": "Latest Article on ABP Community",
"Register": "Register"
"Register": "Register",
"IsDownloadable": "Is downloadable"
}
}

2
framework/src/Volo.Abp.AspNetCore.Components.Server/Volo.Abp.AspNetCore.Components.Server.csproj

@ -11,6 +11,8 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.AspNetCore.Components.Web\Volo.Abp.AspNetCore.Components.Web.csproj" />
<ProjectReference Include="..\Volo.Abp.Http.Client\Volo.Abp.Http.Client.csproj" />
<ProjectReference Include="..\Volo.Abp.AspNetCore.SignalR\Volo.Abp.AspNetCore.SignalR.csproj" />
</ItemGroup>

4
framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/AbpAspNetCoreComponentsServerModule.cs

@ -4,13 +4,17 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Volo.Abp.AspNetCore.Auditing;
using Volo.Abp.AspNetCore.Components.Web;
using Volo.Abp.AspNetCore.SignalR;
using Volo.Abp.AspNetCore.Uow;
using Volo.Abp.Http.Client;
using Volo.Abp.Modularity;
namespace Volo.Abp.AspNetCore.Components.Server
{
[DependsOn(
typeof(AbpHttpClientModule),
typeof(AbpAspNetCoreComponentsWebModule),
typeof(AbpAspNetCoreSignalRModule)
)]
public class AbpAspNetCoreComponentsServerModule : AbpModule

92
framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/Extensibility/BlazorServerLookupApiRequestService.cs

@ -0,0 +1,92 @@
using System;
using System.Globalization;
using System.Linq.Dynamic.Core;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Volo.Abp.AspNetCore.Components.Web.Extensibility;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client;
using Volo.Abp.Http.Client.Authentication;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.AspNetCore.Components.Server.Extensibility
{
public class BlazorServerLookupApiRequestService : ILookupApiRequestService, ITransientDependency
{
public IHttpClientFactory HttpClientFactory { get; }
public IRemoteServiceHttpClientAuthenticator HttpClientAuthenticator { get; }
public AbpRemoteServiceOptions RemoteServiceOptions { get; }
public ICurrentTenant CurrentTenant { get; }
public IHttpContextAccessor HttpContextAccessor { get; }
public NavigationManager NavigationManager { get; }
public BlazorServerLookupApiRequestService(IHttpClientFactory httpClientFactory,
IRemoteServiceHttpClientAuthenticator httpClientAuthenticator,
ICurrentTenant currentTenant,
IOptions<AbpRemoteServiceOptions> remoteServiceOptions,
IHttpContextAccessor httpContextAccessor,
NavigationManager navigationManager)
{
HttpClientFactory = httpClientFactory;
HttpClientAuthenticator = httpClientAuthenticator;
RemoteServiceOptions = remoteServiceOptions.Value;
CurrentTenant = currentTenant;
HttpContextAccessor = httpContextAccessor;
NavigationManager = navigationManager;
}
public async Task<string> SendAsync(string url)
{
var client = HttpClientFactory.CreateClient();
var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
var uri = new Uri(url, UriKind.RelativeOrAbsolute);
if (!uri.IsAbsoluteUri)
{
var baseUrl = string.Empty;
try
{
//Blazor tiered -- mode
var remoteServiceConfig = RemoteServiceOptions.RemoteServices.GetConfigurationOrDefault("Default");
baseUrl = remoteServiceConfig.BaseUrl;
client.BaseAddress = new Uri(baseUrl);
AddHeaders(requestMessage);
await HttpClientAuthenticator.Authenticate(new RemoteServiceHttpClientAuthenticateContext(client,
requestMessage, new RemoteServiceConfiguration(baseUrl), string.Empty));
}
catch (AbpException) // Blazor-Server mode.
{
baseUrl = NavigationManager.BaseUri;
client.BaseAddress = new Uri(baseUrl);
foreach (var header in HttpContextAccessor.HttpContext.Request.Headers)
{
requestMessage.Headers.Add(header.Key, header.Value.ToArray());
}
}
}
var response = await client.SendAsync(requestMessage);
return await response.Content.ReadAsStringAsync();
}
protected virtual void AddHeaders(HttpRequestMessage requestMessage)
{
if (CurrentTenant.Id.HasValue)
{
requestMessage.Headers.Add(TenantResolverConsts.DefaultTenantKey, CurrentTenant.Id.Value.ToString());
}
var currentCulture = CultureInfo.CurrentUICulture.Name ?? CultureInfo.CurrentCulture.Name;
if (!currentCulture.IsNullOrEmpty())
{
requestMessage.Headers.AcceptLanguage.Add(new(currentCulture));
}
}
}
}

12
framework/src/Volo.Abp.BlazoriseUI/Components/PageHeader.razor → framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/PageHeader.razor

@ -1,4 +1,5 @@
<Row Class="entry-row">
@using Blazorise
<Row Class="entry-row">
<Column ColumnSize="ColumnSize.IsAuto">
<h1 class="content-header-title">@Title</h1>
</Column>
@ -31,7 +32,12 @@
}
<Column>
<Row Class="justify-content-end mx-n1">
@ChildContent
@foreach (var toolbarItemRender in ToolbarItemRenders)
{
<Column ColumnSize="ColumnSize.IsAuto" Class="px-1 pt-2">
@toolbarItemRender
</Column>
}
</Row>
</Column>
</Row>
</Row>

76
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Layout/PageHeader.razor.cs

@ -0,0 +1,76 @@
using System;
using Microsoft.AspNetCore.Components;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars;
using Volo.Abp.BlazoriseUI;
namespace Volo.Abp.AspNetCore.Components.Web.Theming.Layout
{
public partial class PageHeader : ComponentBase
{
protected List<RenderFragment> ToolbarItemRenders { get; set; }
public IPageToolbarManager PageToolbarManager { get; set; }
[Parameter]
public string Title { get; set; }
[Parameter]
public bool BreadcrumbShowHome { get; set; } = true;
[Parameter]
public bool BreadcrumbShowCurrent { get; set; } = true;
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public List<BreadcrumbItem> BreadcrumbItems { get; set; }
[Parameter]
public PageToolbar Toolbar { get; set; }
[Parameter]
public string PageName { get; set; }
public PageHeader()
{
BreadcrumbItems = new List<BreadcrumbItem>();
ToolbarItemRenders = new List<RenderFragment>();
}
protected override async Task OnParametersSetAsync()
{
await base.OnParametersSetAsync();
if (Toolbar!=null)
{
var toolbarItems = await PageToolbarManager.GetItemsAsync(Toolbar);
ToolbarItemRenders.Clear();
foreach (var item in toolbarItems)
{
var sequence = 0;
ToolbarItemRenders.Add(builder =>
{
builder.OpenComponent(sequence, item.ComponentType);
if (item.Arguments != null)
{
foreach (var argument in item.Arguments)
{
sequence++;
builder.AddAttribute(sequence, argument.Key, argument.Value);
}
}
builder.CloseComponent();
});
}
}
}
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
}
}
}

9
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/IPageToolbarContributor.cs

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars
{
public interface IPageToolbarContributor
{
Task ContributeAsync(PageToolbarContributionContext context);
}
}

9
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/IPageToolbarManager.cs

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars
{
public interface IPageToolbarManager
{
Task<PageToolbarItem[]> GetItemsAsync(PageToolbar toolbar);
}
}

12
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/PageToolbar.cs

@ -0,0 +1,12 @@
namespace Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars
{
public class PageToolbar
{
public PageToolbarContributorList Contributors { get; set; }
public PageToolbar()
{
Contributors = new PageToolbarContributorList();
}
}
}

21
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/PageToolbarContributionContext.cs

@ -0,0 +1,21 @@
using JetBrains.Annotations;
using System;
namespace Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars
{
public class PageToolbarContributionContext
{
[NotNull]
public IServiceProvider ServiceProvider { get; }
[NotNull]
public PageToolbarItemList Items { get; }
public PageToolbarContributionContext(
[NotNull] IServiceProvider serviceProvider)
{
ServiceProvider = Check.NotNull(serviceProvider, nameof(serviceProvider));
Items = new PageToolbarItemList();
}
}
}

9
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/PageToolbarContributor.cs

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars
{
public abstract class PageToolbarContributor : IPageToolbarContributor
{
public abstract Task ContributeAsync(PageToolbarContributionContext context);
}
}

8
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/PageToolbarContributorList.cs

@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars
{
public class PageToolbarContributorList : List<IPageToolbarContributor>
{
}
}

9
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/PageToolbarDictionary.cs

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars
{
public class PageToolbarDictionary : Dictionary<string, PageToolbar>
{
}
}

71
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/PageToolbarExtensions.cs

@ -0,0 +1,71 @@
using Blazorise;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.BlazoriseUI.Components;
using Volo.Abp.Localization;
namespace Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars
{
public static class PageToolbarExtensions
{
public static PageToolbar AddComponent<TComponent>(
this PageToolbar toolbar,
Dictionary<string, object> arguments = null,
int order = 0,
string requiredPolicyName = null)
{
return toolbar.AddComponent(
typeof(TComponent),
arguments,
order,
requiredPolicyName
);
}
public static PageToolbar AddComponent(
this PageToolbar toolbar,
Type componentType,
Dictionary<string, object> arguments = null,
int order = 0,
string requiredPolicyName = null)
{
toolbar.Contributors.Add(
new SimplePageToolbarContributor(
componentType,
arguments,
order,
requiredPolicyName
)
);
return toolbar;
}
public static PageToolbar AddButton(
this PageToolbar toolbar,
string text,
Func<Task> clicked,
object icon = null,
Color color = Color.Primary,
bool disabled = false,
int order = 0,
string requiredPolicyName = null)
{
toolbar.AddComponent<ToolbarButton>(
new Dictionary<string, object>
{
{ nameof(ToolbarButton.Color), color},
{ nameof(ToolbarButton.Text), text},
{ nameof(ToolbarButton.Disabled), disabled},
{ nameof(ToolbarButton.Icon), icon},
{ nameof(ToolbarButton.Clicked),clicked},
},
order,
requiredPolicyName
);
return toolbar;
}
}
}

27
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/PageToolbarItem.cs

@ -0,0 +1,27 @@
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
namespace Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars
{
public class PageToolbarItem
{
[NotNull]
public Type ComponentType { get; }
[CanBeNull]
public Dictionary<string, object> Arguments { get; set; }
public int Order { get; set; }
public PageToolbarItem(
[NotNull] Type componentType,
[CanBeNull] Dictionary<string, object> arguments = null,
int order = 0)
{
ComponentType = Check.NotNull(componentType, nameof(componentType));
Arguments = arguments;
Order = order;
}
}
}

9
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/PageToolbarItemList.cs

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars
{
public class PageToolbarItemList : List<PageToolbarItem>
{
}
}

40
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/PageToolbarManager.cs

@ -0,0 +1,40 @@
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars
{
public class PageToolbarManager : IPageToolbarManager, ITransientDependency
{
protected IHybridServiceScopeFactory ServiceScopeFactory { get; }
public PageToolbarManager(
IHybridServiceScopeFactory serviceScopeFactory)
{
ServiceScopeFactory = serviceScopeFactory;
}
public virtual async Task<PageToolbarItem[]> GetItemsAsync(PageToolbar toolbar)
{
if (toolbar == null || !toolbar.Contributors.Any())
{
return Array.Empty<PageToolbarItem>();
}
using (var scope = ServiceScopeFactory.CreateScope())
{
var context = new PageToolbarContributionContext(scope.ServiceProvider);
foreach (var contributor in toolbar.Contributors)
{
await contributor.ContributeAsync(context);
}
return context.Items.OrderBy(i => i.Order).ToArray();
}
}
}
}

53
framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/PageToolbars/SimplePageToolbarContributor.cs

@ -0,0 +1,53 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars
{
public class SimplePageToolbarContributor : IPageToolbarContributor
{
public Type ComponentType { get; }
public Dictionary<string, object> Arguments { get; set; }
public int Order { get; }
public string RequiredPolicyName { get; }
public SimplePageToolbarContributor(
Type componentType,
Dictionary<string, object> arguments = null,
int order = 0,
string requiredPolicyName = null)
{
ComponentType = componentType;
Arguments = arguments;
Order = order;
RequiredPolicyName = requiredPolicyName;
}
public async Task ContributeAsync(PageToolbarContributionContext context)
{
if (await ShouldAddComponentAsync(context))
{
context.Items.Add(new PageToolbarItem(ComponentType, Arguments, Order));
}
}
protected virtual async Task<bool> ShouldAddComponentAsync(PageToolbarContributionContext context)
{
if (RequiredPolicyName != null)
{
var authorizationService = context.ServiceProvider.GetRequiredService<IAuthorizationService>();
if (!await authorizationService.IsGrantedAsync(RequiredPolicyName))
{
return false;
}
}
return true;
}
}
}

20
framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Extensibility/EntityActions/EntityAction.cs

@ -0,0 +1,20 @@
using System;
using System.Threading.Tasks;
namespace Volo.Abp.AspNetCore.Components.Web.Extensibility.EntityActions
{
public class EntityAction : IEquatable<EntityAction>
{
public string Text { get; set; }
public Func<object, Task> Clicked { get; set; }
public Func<object, string> ConfirmationMessage { get; set; }
public bool Primary { get; set; }
public object Color { get; set; }
public string Icon { get; set; }
public Func<object, bool> Visible { get; set; }
public bool Equals(EntityAction other)
{
return string.Equals(Text, other?.Text, StringComparison.OrdinalIgnoreCase);
}
}
}

12
framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Extensibility/EntityActions/EntityActionDictionary.cs

@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace Volo.Abp.AspNetCore.Components.Web.Extensibility.EntityActions
{
public class EntityActionDictionary : Dictionary<string, List<EntityAction>>
{
public List<EntityAction> Get<T>()
{
return this.GetOrAdd(typeof(T).FullName, () => new List<EntityAction>());
}
}
}

10
framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Extensibility/ILookupApiRequestService.cs

@ -0,0 +1,10 @@
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.AspNetCore.Components.Web.Extensibility
{
public interface ILookupApiRequestService
{
Task<string> SendAsync([NotNull]string url);
}
}

25
framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Extensibility/TableColumns/TableColumn.cs

@ -0,0 +1,25 @@
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using Volo.Abp.AspNetCore.Components.Web.Extensibility.EntityActions;
namespace Volo.Abp.AspNetCore.Components.Web.Extensibility.TableColumns
{
public class TableColumn
{
public string Title { get; set; }
public string Data { get; set; }
[CanBeNull]
public string DisplayFormat { get; set; }
[CanBeNull]
public Type Component { get; set; }
public List<EntityAction> Actions { get; set; }
[CanBeNull]
public Func<object,string> ValueConverter { get; set; }
public TableColumn()
{
Actions = new List<EntityAction>();
}
}
}

12
framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Extensibility/TableColumns/TableColumnDictionary.cs

@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace Volo.Abp.AspNetCore.Components.Web.Extensibility.TableColumns
{
public class TableColumnDictionary : Dictionary<string, List<TableColumn>>
{
public List<TableColumn> Get<T>()
{
return this.GetOrAdd(typeof(T).FullName, () => new List<TableColumn>());
}
}
}

69
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Extensibility/WebAssemblyLookupApiRequestService.cs

@ -0,0 +1,69 @@
using System;
using System.Globalization;
using System.Net.Http;
using System.Threading.Tasks;
using Castle.Components.DictionaryAdapter;
using Fody;
using Volo.Abp.AspNetCore.Components.Web.Extensibility;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.Authentication;
using Volo.Abp.Http.Client;
using Microsoft.Extensions.Options;
using Volo.Abp.MultiTenancy;
namespace Volo.Abp.AspNetCore.Components.WebAssembly.Extensibility
{
public class WebAssemblyLookupApiRequestService : ILookupApiRequestService, ITransientDependency
{
public IHttpClientFactory HttpClientFactory { get; }
public IRemoteServiceHttpClientAuthenticator HttpClientAuthenticator { get; }
public AbpRemoteServiceOptions RemoteServiceOptions { get; }
public ICurrentTenant CurrentTenant { get; }
public WebAssemblyLookupApiRequestService(IHttpClientFactory httpClientFactory,
IRemoteServiceHttpClientAuthenticator httpClientAuthenticator,
ICurrentTenant currentTenant,
IOptions<AbpRemoteServiceOptions> remoteServiceOptions)
{
HttpClientFactory = httpClientFactory;
HttpClientAuthenticator = httpClientAuthenticator;
RemoteServiceOptions = remoteServiceOptions.Value;
CurrentTenant = currentTenant;
}
public async Task<string> SendAsync(string url)
{
var client = HttpClientFactory.CreateClient();
var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
AddHeaders(requestMessage);
var uri = new Uri(url, UriKind.RelativeOrAbsolute);
if (!uri.IsAbsoluteUri)
{
var remoteServiceConfig = RemoteServiceOptions.RemoteServices.GetConfigurationOrDefault("Default");
client.BaseAddress = new Uri(remoteServiceConfig.BaseUrl);
await HttpClientAuthenticator.Authenticate(new RemoteServiceHttpClientAuthenticateContext(client, requestMessage, new RemoteServiceConfiguration(remoteServiceConfig.BaseUrl), string.Empty));
}
var response = await client.SendAsync(requestMessage);
return await response.Content.ReadAsStringAsync();
}
protected virtual void AddHeaders(HttpRequestMessage requestMessage)
{
if (CurrentTenant.Id.HasValue)
{
requestMessage.Headers.Add(TenantResolverConsts.DefaultTenantKey, CurrentTenant.Id.Value.ToString());
}
var currentCulture = CultureInfo.CurrentUICulture.Name ?? CultureInfo.CurrentCulture.Name;
if (!currentCulture.IsNullOrEmpty())
{
requestMessage.Headers.AcceptLanguage.Add(new (currentCulture));
}
}
}
}

85
framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs

@ -9,11 +9,18 @@ using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.AspNetCore.Components;
using Volo.Abp.Localization;
using Volo.Abp.Authorization;
using Volo.Abp.BlazoriseUI.Components;
using Volo.Abp.BlazoriseUI.Components.ObjectExtending;
using Volo.Abp.ObjectExtending.Modularity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.AspNetCore.Components.Web.Extensibility.EntityActions;
using Volo.Abp.AspNetCore.Components.Web.Extensibility.TableColumns;
namespace Volo.Abp.BlazoriseUI
{
@ -186,6 +193,8 @@ namespace Volo.Abp.BlazoriseUI
protected Validations EditValidationsRef;
protected List<BreadcrumbItem> BreadcrumbItems = new List<BreadcrumbItem>(2);
protected DataGridEntityActionsColumn<TListViewModel> EntityActionsColumn;
protected EntityActionDictionary EntityActions { get; set; }
protected TableColumnDictionary TableColumns { get; set; }
protected string CreatePolicyName { get; set; }
protected string UpdatePolicyName { get; set; }
@ -199,14 +208,30 @@ namespace Volo.Abp.BlazoriseUI
{
NewEntity = new TCreateViewModel();
EditingEntity = new TUpdateViewModel();
TableColumns = new TableColumnDictionary();
EntityActions = new EntityActionDictionary();
}
protected override async Task OnInitializedAsync()
{
await SetBreadcrumbItemsAsync();
await SetPermissionsAsync();
await SetEntityActionsAsync();
await SetTableColumnsAsync();
await InvokeAsync(StateHasChanged);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await base.OnAfterRenderAsync(firstRender);
await SetToolbarItemsAsync();
await SetBreadcrumbItemsAsync();
}
}
protected virtual async Task SetPermissionsAsync()
{
if (CreatePolicyName != null)
@ -463,6 +488,7 @@ namespace Volo.Abp.BlazoriseUI
await AppService.DeleteAsync(entity.Id);
await GetEntitiesAsync();
await InvokeAsync(StateHasChanged);
}
catch (Exception ex)
{
@ -511,5 +537,62 @@ namespace Volo.Abp.BlazoriseUI
{
return ValueTask.CompletedTask;
}
protected virtual ValueTask SetEntityActionsAsync()
{
return ValueTask.CompletedTask;
}
protected virtual ValueTask SetTableColumnsAsync()
{
return ValueTask.CompletedTask;
}
protected virtual ValueTask SetToolbarItemsAsync()
{
return ValueTask.CompletedTask;
}
protected virtual IEnumerable<TableColumn> GetExtensionTableColumns(string moduleName, string entityType)
{
var properties = ModuleExtensionConfigurationHelper.GetPropertyConfigurations(moduleName, entityType);
foreach (var propertyInfo in properties)
{
if (propertyInfo.IsAvailableToClients && propertyInfo.UI.OnTable.IsVisible)
{
if (propertyInfo.Name.EndsWith("_Text"))
{
var lookupPropertyName = propertyInfo.Name.RemovePostFix("_Text");
var lookupPropertyDefinition = properties.SingleOrDefault(t => t.Name == lookupPropertyName);
yield return new TableColumn
{
Title = lookupPropertyDefinition.GetLocalizedDisplayName(StringLocalizerFactory),
Data = $"ExtraProperties[{propertyInfo.Name}]"
};
}
else
{
var column = new TableColumn
{
Title = propertyInfo.GetLocalizedDisplayName(StringLocalizerFactory),
Data = $"ExtraProperties[{propertyInfo.Name}]"
};
if (propertyInfo.IsDate() || propertyInfo.IsDateTime())
{
column.DisplayFormat = propertyInfo.GetDateEditInputFormatOrNull();
}
if (propertyInfo.Type.IsEnum)
{
column.ValueConverter = (val) =>
EnumHelper.GetLocalizedMemberName(propertyInfo.Type, val, StringLocalizerFactory);
}
yield return column;
}
}
}
}
}
}

261
framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiObjectExtensionPropertyInfoExtensions.cs

@ -0,0 +1,261 @@
using Blazorise;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Volo.Abp.BlazoriseUI.Components.ObjectExtending;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Reflection;
namespace Volo.Abp.BlazoriseUI
{
public static class BlazoriseUiObjectExtensionPropertyInfoExtensions
{
private static readonly HashSet<Type> NumberTypes = new HashSet<Type> {
typeof(int),
typeof(long),
typeof(byte),
typeof(sbyte),
typeof(short),
typeof(ushort),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(float),
typeof(double),
typeof(decimal),
typeof(int?),
typeof(long?),
typeof(byte?),
typeof(sbyte?),
typeof(short?),
typeof(ushort?),
typeof(uint?),
typeof(long?),
typeof(ulong?),
typeof(float?),
typeof(double?),
typeof(decimal?)
};
private static readonly HashSet<Type> TextEditSupportedAttributeTypes = new HashSet<Type> {
typeof(EmailAddressAttribute),
typeof(UrlAttribute),
typeof(PhoneAttribute)
};
public static string GetDateEditInputFormatOrNull(this IBasicObjectExtensionPropertyInfo property)
{
if (property.IsDate())
{
return "{0:yyyy-MM-dd}";
}
if (property.IsDateTime())
{
return "{0:yyyy-MM-ddTHH:mm}";
}
return null;
}
public static string GetTextInputValueOrNull(this IBasicObjectExtensionPropertyInfo property, object value)
{
if (value == null)
{
return null;
}
if (TypeHelper.IsFloatingType(property.Type))
{
return value.ToString()?.Replace(',', '.');
}
return value.ToString();
}
public static T GetInputValueOrDefault<T>(this IBasicObjectExtensionPropertyInfo property, object value)
{
if (value == null)
{
return default;
}
return (T)value;
}
public static TextInputMode GetTextInputMode(this ObjectExtensionPropertyInfo propertyInfo)
{
foreach (var attribute in propertyInfo.Attributes)
{
var textRoleByAttribute = GetTextInputModeFromAttributeOrNull(attribute);
if (textRoleByAttribute != null)
{
return textRoleByAttribute.Value;
}
}
return GetTextInputModeFromTypeOrNull(propertyInfo.Type)
?? TextInputMode.None; //default
}
private static TextInputMode? GetTextInputModeFromTypeOrNull(Type type)
{
if (TypeHelper.IsFloatingType(type))
{
return TextInputMode.Decimal;
}
if (NumberTypes.Contains(type))
{
return TextInputMode.Numeric;
}
return null;
}
private static TextInputMode? GetTextInputModeFromAttributeOrNull(Attribute attribute)
{
if (attribute is EmailAddressAttribute)
{
return TextInputMode.Email;
}
if (attribute is UrlAttribute)
{
return TextInputMode.Url;
}
if (attribute is PhoneAttribute)
{
return TextInputMode.Tel;
}
if (attribute is DataTypeAttribute dataTypeAttribute)
{
switch (dataTypeAttribute.DataType)
{
case DataType.EmailAddress:
return TextInputMode.Email;
case DataType.Url:
return TextInputMode.Url;
case DataType.PhoneNumber:
return TextInputMode.Tel;
}
}
return null;
}
public static TextRole GetTextRole(this ObjectExtensionPropertyInfo propertyInfo)
{
foreach (var attribute in propertyInfo.Attributes)
{
var textRoleByAttribute = GetTextRoleFromAttributeOrNull(attribute);
if (textRoleByAttribute != null)
{
return textRoleByAttribute.Value;
}
}
return TextRole.Text; //default
}
private static TextRole? GetTextRoleFromAttributeOrNull(Attribute attribute)
{
if (attribute is EmailAddressAttribute)
{
return TextRole.Email;
}
if (attribute is UrlAttribute)
{
return TextRole.Url;
}
if (attribute is DataTypeAttribute dataTypeAttribute)
{
switch (dataTypeAttribute.DataType)
{
case DataType.Password:
return TextRole.Password;
case DataType.EmailAddress:
return TextRole.Email;
case DataType.Url:
return TextRole.Url;
}
}
return null;
}
public static Type GetInputType(this ObjectExtensionPropertyInfo propertyInfo)
{
foreach (var attribute in propertyInfo.Attributes)
{
var inputTypeByAttribute = GetInputTypeFromAttributeOrNull(attribute);
if (inputTypeByAttribute != null)
{
return inputTypeByAttribute;
}
}
return GetInputTypeFromTypeOrNull(propertyInfo.Type)
?? typeof(TextExtensionProperty<,>); //default
}
private static Type GetInputTypeFromAttributeOrNull(Attribute attribute)
{
var hasTextEditSupport = TextEditSupportedAttributeTypes.Any(t => t == attribute.GetType());
if (hasTextEditSupport)
{
return typeof(TextExtensionProperty<,>);
}
if (attribute is DataTypeAttribute dataTypeAttribute)
{
switch (dataTypeAttribute.DataType)
{
case DataType.Password:
return typeof(TextExtensionProperty<,>);
case DataType.Date:
return typeof(DateTimeExtensionProperty<,>);
case DataType.Time:
return typeof(TimeExtensionProperty<,>);
case DataType.EmailAddress:
return typeof(TextExtensionProperty<,>);
case DataType.Url:
return typeof(TextExtensionProperty<,>);
case DataType.PhoneNumber:
return typeof(TextExtensionProperty<,>);
case DataType.DateTime:
return typeof(DateTimeExtensionProperty<,>);
}
}
return null;
}
private static Type GetInputTypeFromTypeOrNull(Type type)
{
if (type == typeof(bool))
{
return typeof(CheckExtensionProperty<,>);
}
if (type == typeof(DateTime))
{
return typeof(DateTimeExtensionProperty<,>);
}
if (NumberTypes.Contains(type))
{
return typeof(TextExtensionProperty<,>);
}
return null;
}
}
}

121
framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor

@ -0,0 +1,121 @@
@typeparam TItem
@using Blazorise.DataGrid;
@using Volo.Abp.Data
@using Volo.Abp.BlazoriseUI.Components.ObjectExtending
<DataGrid TItem="TItem"
Data="@Data"
ReadData="@ReadData"
TotalItems="@TotalItems"
ShowPager="@ShowPager"
CurrentPage="@CurrentPage"
PageSize="@PageSize">
<DataGridColumns>
@if (Columns != null)
{
@foreach (var column in Columns)
{
if (column.Actions.Any())
{
<DataGridEntityActionsColumn TItem="TItem" @ref="ActionColumns[column.Title]" Caption="@column.Title">
<DisplayTemplate>
<EntityActions TItem="TItem" EntityActionsColumn="ActionColumns[column.Title]">
@foreach (var action in column.Actions)
{
if (action.ConfirmationMessage != null)
{
<EntityAction TItem="TItem"
Color="@(action.Color!=null ? (Blazorise.Color)action.Color : Blazorise.Color.Primary)"
Icon="@action.Icon"
Clicked="async () => await action.Clicked(context)"
ConfirmationMessage="() => action.ConfirmationMessage.Invoke(context)"
Visible="@(action.Visible!=null ? action.Visible(context) : true)"
Text="@action.Text">
</EntityAction>
}
else
{
<EntityAction TItem="TItem"
Clicked="async () => await action.Clicked(context)"
Color="@(action.Color!=null ? (Blazorise.Color)action.Color : Blazorise.Color.None)"
Icon="@action.Icon"
Visible="@(action.Visible!=null ? action.Visible(context) : true)"
Text="@action.Text">
</EntityAction>
}
}
</EntityActions>
</DisplayTemplate>
</DataGridEntityActionsColumn>
}
else
{
@if (column.Component != null)
{
<DataGridColumn TItem="TItem" Field="@typeof(TItem).GetProperties().First().Name" Caption="@column.Title">
<DisplayTemplate>
@RenderCustomTableColumnComponent(column.Component, context)
</DisplayTemplate>
</DataGridColumn>
}
else
{
if (!ExtensionPropertiesRegex.IsMatch(column.Data))
{
<DataGridColumn TItem="TItem" Field="@column.Data" Caption="@column.Title"/>
}
else
{
<DataGridColumn TItem="TItem" Field="@nameof(IHasExtraProperties.ExtraProperties)" Caption="@column.Title">
<DisplayTemplate>
@{
var entity = context as IHasExtraProperties;
var propertyName = ExtensionPropertiesRegex.Match(column.Data).Groups[1].Value;
var propertyValue = entity.GetProperty(propertyName);
if (propertyValue != null && propertyValue.GetType() == typeof(bool))
{
if ((bool) propertyValue)
{
<Icon class="text-success" Name="IconName.Check"/>
}
else
{
<Icon class="text-danger" Name="IconName.Times"/>
}
}
else
{
if (column.ValueConverter != null)
{
if (column.DisplayFormat == null)
{
@(column.ValueConverter(propertyValue))
}
else
{
@(string.Format(column.DisplayFormat, column.ValueConverter(propertyValue)))
}
}
else
{
if (column.DisplayFormat == null)
{
@(propertyValue)
}
else
{
@(string.Format(column.DisplayFormat, propertyValue))
}
}
}
}
</DisplayTemplate>
</DataGridColumn>
}
}
}
}
}
</DataGridColumns>
</DataGrid>

50
framework/src/Volo.Abp.BlazoriseUI/Components/AbpExtensibleDataGrid.razor.cs

@ -0,0 +1,50 @@
using System;
using Blazorise.Extensions;
using Blazorise.DataGrid;
using Microsoft.AspNetCore.Components;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Localization;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Components.Web.Extensibility.EntityActions;
using Volo.Abp.AspNetCore.Components.Web.Extensibility.TableColumns;
namespace Volo.Abp.BlazoriseUI.Components
{
public partial class AbpExtensibleDataGrid<TItem> : ComponentBase
{
protected const string DataFieldAttributeName = "Data";
protected Dictionary<string, DataGridEntityActionsColumn<TItem>> ActionColumns =
new Dictionary<string, DataGridEntityActionsColumn<TItem>>();
protected Regex ExtensionPropertiesRegex = new Regex(@"ExtraProperties\[(.*?)\]");
[Parameter] public IEnumerable<TItem> Data { get; set; }
[Parameter] public EventCallback<DataGridReadDataEventArgs<TItem>> ReadData { get; set; }
[Parameter] public int? TotalItems { get; set; }
[Parameter] public bool ShowPager { get; set; }
[Parameter] public int PageSize { get; set; }
[Parameter] public IEnumerable<TableColumn> Columns { get; set; }
[Parameter] public int CurrentPage { get; set; } = 1;
[Inject]
public IStringLocalizerFactory StringLocalizerFactory { get; set; }
protected virtual RenderFragment RenderCustomTableColumnComponent(Type type, object data)
{
return (builder) =>
{
builder.OpenComponent(type);
builder.AddAttribute(0, DataFieldAttributeName, data);
builder.CloseComponent();
};
}
}
}

6
framework/src/Volo.Abp.BlazoriseUI/Components/EntityAction.razor

@ -1,6 +1,6 @@
@typeparam TItem
@if (IsVisible && HasPermission)
@if (Visible && HasPermission)
{
if (ParentActions.Type == ActionType.Dropdown)
{
@ -14,6 +14,10 @@
<Button Block="true"
Color="@Color"
Clicked="@ActionClickedAsync">
@if(!string.IsNullOrEmpty(Icon))
{
<Icon Name="@Icon"/>
}
@Text
</Button>
}

6
framework/src/Volo.Abp.BlazoriseUI/Components/EntityAction.razor.cs

@ -10,7 +10,7 @@ namespace Volo.Abp.BlazoriseUI.Components
public partial class EntityAction<TItem> : ComponentBase
{
[Parameter]
public bool IsVisible { get; set; } = true;
public bool Visible { get; set; }
internal bool HasPermission { get; set; } = true;
@ -33,6 +33,9 @@ namespace Volo.Abp.BlazoriseUI.Components
[Parameter]
public Func<string> ConfirmationMessage { get; set; }
[Parameter]
public string Icon { get; set; }
[CascadingParameter]
public EntityActions<TItem> ParentActions { get; set; }
@ -46,6 +49,7 @@ namespace Volo.Abp.BlazoriseUI.Components
{
await base.OnInitializedAsync();
await SetDefaultValuesAsync();
if (!RequiredPolicy.IsNullOrEmpty())
{
HasPermission = await AuthorizationService.IsGrantedAsync(RequiredPolicy);

2
framework/src/Volo.Abp.BlazoriseUI/Components/EntityActions.razor

@ -14,7 +14,7 @@
}
else
{
<DropdownToggle Color="@ToggleColor">
<DropdownToggle Class="btn-block" Color="@ToggleColor">
@ToggleText
</DropdownToggle>
}

2
framework/src/Volo.Abp.BlazoriseUI/Components/EntityActions.razor.cs

@ -52,7 +52,7 @@ namespace Volo.Abp.BlazoriseUI.Components
{
if (ParentEntityActionsColumn != null)
{
ParentEntityActionsColumn.Displayable = Actions.Any(t => t.IsVisible && t.HasPermission);
ParentEntityActionsColumn.Displayable = Actions.Any(t => t.Visible && t.HasPermission);
}
await InvokeAsync(StateHasChanged);

11
framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/CheckExtensionProperty.razor

@ -0,0 +1,11 @@
@typeparam TEntity
@typeparam TResourceType
@using Volo.Abp.BlazoriseUI
@using Volo.Abp.Localization
@if (PropertyInfo != null && Entity != null)
{
<Field>
<Check TValue="bool" @bind-Checked="@Value">@PropertyInfo.GetLocalizedDisplayName(StringLocalizerFactory)</Check>
</Field>
}

32
framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/CheckExtensionProperty.razor.cs

@ -0,0 +1,32 @@
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Volo.Abp.Data;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.BlazoriseUI.Components.ObjectExtending
{
public partial class CheckExtensionProperty<TEntity, TResourceType> : ComponentBase
where TEntity : IHasExtraProperties
{
[Inject]
public IStringLocalizerFactory StringLocalizerFactory { get; set; }
[Parameter]
public TEntity Entity { get; set; }
[Parameter]
public ObjectExtensionPropertyInfo PropertyInfo { get; set; }
protected bool Value
{
get
{
return PropertyInfo.GetInputValueOrDefault<bool>(Entity.GetProperty(PropertyInfo.Name));
}
set
{
Entity.SetProperty(PropertyInfo.Name, value, false);
}
}
}
}

17
framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/DateTimeExtensionProperty.razor

@ -0,0 +1,17 @@
@typeparam TEntity
@typeparam TResourceType
@using Volo.Abp.BlazoriseUI
@using Volo.Abp.Localization
@using Volo.Abp.ObjectExtending
@if (PropertyInfo != null && Entity != null)
{
<Field>
<FieldLabel>@PropertyInfo.GetLocalizedDisplayName(StringLocalizerFactory)</FieldLabel>
<DateEdit TValue="DateTime?"
InputMode="@(PropertyInfo.IsDate() ? DateInputMode.Date : DateInputMode.DateTime)"
Pattern="@PropertyInfo.GetDateEditInputFormatOrNull()"
@bind-Date="@Value">
</DateEdit>
</Field>
}

37
framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/DateTimeExtensionProperty.razor.cs

@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.BlazoriseUI.Components.ObjectExtending
{
public partial class DateTimeExtensionProperty<TEntity, TResourceType> : ComponentBase
where TEntity : IHasExtraProperties
{
[Inject]
public IStringLocalizerFactory StringLocalizerFactory { get; set; }
[Parameter]
public TEntity Entity { get; set; }
[Parameter]
public ObjectExtensionPropertyInfo PropertyInfo { get; set; }
protected DateTime? Value
{
get
{
return PropertyInfo.GetInputValueOrDefault<DateTime?>(Entity.GetProperty(PropertyInfo.Name));
}
set
{
Entity.SetProperty(PropertyInfo.Name, value, false);
}
}
}
}

33
framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/EnumHelper.cs

@ -0,0 +1,33 @@
using Microsoft.Extensions.Localization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Localization;
namespace Volo.Abp.BlazoriseUI.Components.ObjectExtending
{
public static class EnumHelper
{
public static string GetLocalizedMemberName(Type enumType, object value, IStringLocalizerFactory stringLocalizerFactory)
{
var memberName = enumType.GetEnumName(value);
var localizedMemberName = AbpInternalLocalizationHelper.LocalizeWithFallback(
new[]
{
stringLocalizerFactory.CreateDefaultOrNull()
},
new[]
{
$"Enum:{enumType.Name}.{memberName}",
$"{enumType.Name}.{memberName}",
memberName
},
memberName
);
return localizedMemberName;
}
}
}

29
framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor

@ -0,0 +1,29 @@
@typeparam TEntityType
@typeparam TResourceType
@using Volo.Abp.ObjectExtending
@using Volo.Abp.Localization
@using Volo.Abp.Data
@foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties<TEntityType>())
{
if (!propertyInfo.Name.EndsWith("_Text"))
{
if (propertyInfo.Type.IsEnum)
{
<SelectExtensionProperty PropertyInfo="@propertyInfo" Entity="@Entity" TEntity="TEntityType" TResourceType="TResourceType" />
}
else if (!propertyInfo.Lookup.Url.IsNullOrEmpty())
{
<LookupExtensionProperty PropertyInfo="@propertyInfo" Entity="@Entity" TEntity="TEntityType" TResourceType="TResourceType" />
}
else
{
var inputType = propertyInfo.GetInputType();
__builder.OpenComponent(0, inputType.MakeGenericType(new[] { typeof(TEntityType), typeof(TResourceType) }));
__builder.AddAttribute(1, "PropertyInfo", propertyInfo);
__builder.AddAttribute(2, "Entity", Entity);
__builder.CloseComponent();
}
}
}

20
framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor.cs

@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Components.Web;
using Volo.Abp.Data;
namespace Volo.Abp.BlazoriseUI.Components.ObjectExtending
{
public partial class ExtensionProperties<TEntityType, TResourceType> : ComponentBase
where TEntityType : IHasExtraProperties
{
[Inject]
public IStringLocalizerFactory StringLocalizerFactory { get; set; }
[Parameter]
public AbpBlazorMessageLocalizerHelper<TResourceType> LH { get; set; }
[Parameter]
public TEntityType Entity { get; set; }
}
}

18
framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/LookupExtensionProperty.razor

@ -0,0 +1,18 @@
@typeparam TEntity
@typeparam TResourceType
@using Abp.Localization
@using Blazorise.Components
<Field>
<FieldLabel>@PropertyInfo.GetLocalizedDisplayName(StringLocalizerFactory)</FieldLabel>
<Autocomplete Data="@lookupItems"
TItem="SelectItem<object>"
TValue="object"
TextField="item=>item.Text"
ValueField="item=>item.Value"
SelectedValue="@SelectedValue"
SelectedValueChanged="@SelectedValueChanged"
SearchChanged="@SearchFilterChangedAsync">
</Autocomplete>
</Field>

109
framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/LookupExtensionProperty.razor.cs

@ -0,0 +1,109 @@
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Components.Web.Extensibility;
using Volo.Abp.Data;
using Volo.Abp.Http;
using Volo.Abp.Http.Client;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.BlazoriseUI.Components.ObjectExtending
{
public partial class LookupExtensionProperty<TEntity, TResourceType> : ComponentBase
where TEntity : IHasExtraProperties
{
protected List<SelectItem<object>> lookupItems;
[Inject] public IStringLocalizerFactory StringLocalizerFactory { get; set; }
[Parameter] public TEntity Entity { get; set; }
[Parameter] public ObjectExtensionPropertyInfo PropertyInfo { get; set; }
[Inject] public ILookupApiRequestService LookupApiService { get; set; }
public string TextPropertyName => PropertyInfo.Name + "_Text";
public object SelectedValue
{
get { return Entity.GetProperty(PropertyInfo.Name); }
set
{
Entity.SetProperty(PropertyInfo.Name, value, false);
UpdateLookupTextProperty(value);
}
}
public LookupExtensionProperty()
{
lookupItems = new List<SelectItem<object>>();
}
protected override void OnParametersSet()
{
var value = Entity.GetProperty(PropertyInfo.Name);
var text = Entity.GetProperty(TextPropertyName);
if (value != null && text != null)
{
lookupItems.Add(new SelectItem<object>
{
Text = Entity.GetProperty(TextPropertyName).ToString(),
Value = value
});
}
}
protected virtual void UpdateLookupTextProperty(object value)
{
var selectedItemText = lookupItems.SingleOrDefault(t => t.Value.Equals(value)).Text;
Entity.SetProperty(TextPropertyName, selectedItemText);
}
protected virtual async Task<List<SelectItem<object>>> GetLookupItemsAsync(string filter)
{
var selectItems = new List<SelectItem<object>>();
var url = PropertyInfo.Lookup.Url;
if (!filter.IsNullOrEmpty())
{
url += $"?{PropertyInfo.Lookup.FilterParamName}={filter.Trim()}";
}
var response = await LookupApiService.SendAsync(url);
var document = JsonDocument.Parse(response);
var itemsArrayProp = document.RootElement.GetProperty(PropertyInfo.Lookup.ResultListPropertyName);
foreach (var item in itemsArrayProp.EnumerateArray())
{
selectItems.Add(new SelectItem<object>
{
Text = item.GetProperty(PropertyInfo.Lookup.DisplayPropertyName).GetString(),
Value = JsonSerializer.Deserialize(
item.GetProperty(PropertyInfo.Lookup.ValuePropertyName).GetRawText(), PropertyInfo.Type)
});
}
return selectItems;
}
protected virtual void SelectedValueChanged(object selectedItem)
{
SelectedValue = selectedItem;
}
protected async Task SearchFilterChangedAsync(string filter)
{
lookupItems = await GetLookupItemsAsync(filter);
}
}
}

13
framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/SelectExtensionProperty.razor

@ -0,0 +1,13 @@
@typeparam TEntity
@typeparam TResourceType
@using Abp.Localization
<Field>
<FieldLabel>@PropertyInfo.GetLocalizedDisplayName(StringLocalizerFactory)</FieldLabel>
<Select @bind-SelectedValue="@SelectedValue" >
@foreach (var item in SelectItems)
{
<SelectItem Value="@item.Value">@item.Text</SelectItem>
}
</Select>
</Field>

61
framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/SelectExtensionProperty.razor.cs

@ -0,0 +1,61 @@
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using System;
using System.Collections.Generic;
using Volo.Abp.Data;
using Volo.Abp.Localization;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.BlazoriseUI.Components.ObjectExtending
{
public partial class SelectExtensionProperty<TEntity, TResourceType> : ComponentBase
where TEntity : IHasExtraProperties
{
protected List<SelectItem<int>> SelectItems = new ();
[Inject] public IStringLocalizerFactory StringLocalizerFactory { get; set; }
[Parameter] public TEntity Entity { get; set; }
[Parameter] public ObjectExtensionPropertyInfo PropertyInfo { get; set; }
public int SelectedValue
{
get { return Entity.GetProperty<int>(PropertyInfo.Name); }
set { Entity.SetProperty(PropertyInfo.Name, value, false); }
}
protected virtual List<SelectItem<int>> GetSelectItemsFromEnum()
{
var selectItems = new List<SelectItem<int>>();
foreach (var enumValue in PropertyInfo.Type.GetEnumValues())
{
selectItems.Add( new SelectItem<int>
{
Value = (int) enumValue,
Text = EnumHelper.GetLocalizedMemberName(PropertyInfo.Type, enumValue, StringLocalizerFactory)
});
}
return selectItems;
}
protected override void OnParametersSet()
{
SelectItems = GetSelectItemsFromEnum();
StateHasChanged();
if (!Entity.HasProperty(PropertyInfo.Name))
{
SelectedValue = (int)PropertyInfo.Type.GetEnumValues().GetValue(0);
}
}
}
public class SelectItem<TValue>
{
public string Text { get; set; }
public TValue Value { get; set; }
}
}

14
framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TextExtensionProperty.razor

@ -0,0 +1,14 @@
@typeparam TEntity
@typeparam TResourceType
@using Volo.Abp.BlazoriseUI
@using Volo.Abp.Localization
@if (PropertyInfo != null && Entity != null)
{
<Field>
<FieldLabel>@PropertyInfo.GetLocalizedDisplayName(StringLocalizerFactory)</FieldLabel>
<TextEdit @bind-Text="@Value" Role="@PropertyInfo.GetTextRole()" InputMode="@PropertyInfo.GetTextInputMode()">
</TextEdit>
</Field>
}

33
framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TextExtensionProperty.razor.cs

@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using Volo.Abp.Data;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.BlazoriseUI.Components.ObjectExtending
{
public partial class TextExtensionProperty<TEntity, TResourceType> : ComponentBase
where TEntity : IHasExtraProperties
{
[Inject]
public IStringLocalizerFactory StringLocalizerFactory { get; set; }
[Parameter]
public TEntity Entity { get; set; }
[Parameter]
public ObjectExtensionPropertyInfo PropertyInfo { get; set; }
protected string Value
{
get
{
return PropertyInfo.GetTextInputValueOrNull(Entity.GetProperty(PropertyInfo.Name));
}
set
{
Entity.SetProperty(PropertyInfo.Name, value, validate: false);
}
}
}
}

13
framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TimeExtensionProperty.razor

@ -0,0 +1,13 @@
@typeparam TEntity
@typeparam TResourceType
@using Volo.Abp.BlazoriseUI
@using Volo.Abp.Localization
@if (PropertyInfo != null && Entity != null)
{
<Field>
<FieldLabel>@PropertyInfo.GetLocalizedDisplayName(StringLocalizerFactory)</FieldLabel>-->
<TimeEdit TValue="TimeSpan?" @bind-Time="@Value">
</TimeEdit>
</Field>
}

37
framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/TimeExtensionProperty.razor.cs

@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.BlazoriseUI.Components.ObjectExtending
{
public partial class TimeExtensionProperty<TEntity, TResourceType> : ComponentBase
where TEntity : IHasExtraProperties
{
[Inject]
public IStringLocalizerFactory StringLocalizerFactory { get; set; }
[Parameter]
public TEntity Entity { get; set; }
[Parameter]
public ObjectExtensionPropertyInfo PropertyInfo { get; set; }
protected TimeSpan? Value
{
get
{
return PropertyInfo.GetInputValueOrDefault<TimeSpan?>(Entity.GetProperty(PropertyInfo.Name));
}
set
{
Entity.SetProperty(PropertyInfo.Name, value, false);
}
}
}
}

29
framework/src/Volo.Abp.BlazoriseUI/Components/PageHeader.razor.cs

@ -1,29 +0,0 @@
using System.Collections.Generic;
using Blazorise;
using Microsoft.AspNetCore.Components;
namespace Volo.Abp.BlazoriseUI.Components
{
public partial class PageHeader : ComponentBase
{
[Parameter]
public string Title { get; set; }
[Parameter]
public bool BreadcrumbShowHome { get; set; } = true;
[Parameter]
public bool BreadcrumbShowCurrent { get; set; } = true;
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public List<BreadcrumbItem> BreadcrumbItems { get; set; }
public PageHeader()
{
BreadcrumbItems = new List<BreadcrumbItem>();
}
}
}

7
framework/src/Volo.Abp.BlazoriseUI/Components/ToolbarButton.razor

@ -0,0 +1,7 @@
<Button Color="@Color" Clicked="@Clicked" Disabled="@Disabled">
@if (Icon != null)
{
<Icon Name="IconName.Add" Class="mr-1"></Icon>
}
@Text
</Button>

25
framework/src/Volo.Abp.BlazoriseUI/Components/ToolbarButton.razor.cs

@ -0,0 +1,25 @@
using Blazorise;
using Microsoft.AspNetCore.Components;
using System;
using System.Threading.Tasks;
namespace Volo.Abp.BlazoriseUI.Components
{
public partial class ToolbarButton : ComponentBase
{
[Parameter]
public Color Color { get; set; }
[Parameter]
public object Icon { get; set; }
[Parameter]
public string Text { get; set; }
[Parameter]
public Func<Task> Clicked { get; set; }
[Parameter]
public bool Disabled { get; set; }
}
}

40
framework/src/Volo.Abp.BlazoriseUI/ObjectExtensionPropertyInfoBlazorExtensions.cs

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Volo.Abp.ObjectExtending
{
public static class ObjectExtensionPropertyInfoBlazorExtensions
{
private static readonly Type[] DateTimeTypes =
{
typeof(DateTime),
typeof(DateTime?),
typeof(DateTimeOffset),
typeof(DateTimeOffset?)
};
public static bool IsDate(this IBasicObjectExtensionPropertyInfo property)
{
return DateTimeTypes.Contains(property.Type) &&
property.GetDataTypeOrNull() == DataType.Date;
}
public static bool IsDateTime(this IBasicObjectExtensionPropertyInfo property)
{
return DateTimeTypes.Contains(property.Type) &&
!property.IsDate();
}
public static DataType? GetDataTypeOrNull(this IBasicObjectExtensionPropertyInfo property)
{
return property
.Attributes
.OfType<DataTypeAttribute>()
.FirstOrDefault()?.DataType;
}
}
}

11
framework/src/Volo.Abp.BlazoriseUI/Volo.Abp.BlazoriseUI.csproj

@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.AspNetCore.Components.Web\Volo.Abp.AspNetCore.Components.Web.csproj" />
@ -17,6 +17,7 @@
<PackageReference Include="Blazorise" Version="0.9.3" />
<PackageReference Include="Blazorise.DataGrid" Version="0.9.3" />
<PackageReference Include="Blazorise.Snackbar" Version="0.9.3" />
<PackageReference Include="Blazorise.Components" Version="0.9.3" />
</ItemGroup>
</Project>

1
framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj

@ -30,5 +30,4 @@
<ProjectReference Include="..\Volo.Abp.IdentityModel\Volo.Abp.IdentityModel.csproj" />
<ProjectReference Include="..\Volo.Abp.Json\Volo.Abp.Json.csproj" />
</ItemGroup>
</Project>

12
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs

@ -35,11 +35,16 @@ namespace Volo.Abp.Cli.Commands
}
var version = commandLineArgs.Options.GetOrNull(Options.Version.Short, Options.Version.Long);
var withSourceCode =commandLineArgs.Options.ContainsKey(Options.SourceCode.Long);
var addSourceCodeToSolutionFile = withSourceCode && commandLineArgs.Options.ContainsKey("add-to-solution-file");
await ProjectNugetPackageAdder.AddAsync(
GetProjectFile(commandLineArgs),
commandLineArgs.Target,
version
version,
true,
withSourceCode,
addSourceCodeToSolutionFile
);
}
@ -128,6 +133,11 @@ namespace Volo.Abp.Cli.Commands
public const string Short = "v";
public const string Long = "version";
}
public static class SourceCode
{
public const string Long = "with-source-code";
}
}
}
}

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs

@ -59,7 +59,7 @@ namespace Volo.Abp.Cli.Commands
commandLineArgs.Options.Add(CliConsts.Command, commandLineArgs.Command);
await _sourceCodeDownloadService.DownloadAsync(
await _sourceCodeDownloadService.DownloadModuleAsync(
commandLineArgs.Target, outputFolder, version, gitHubAbpLocalRepositoryPath, gitHubVoloLocalRepositoryPath, commandLineArgs.Options);
}

62
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/SourceCodeDownloadService.cs

@ -17,15 +17,17 @@ namespace Volo.Abp.Cli.Commands.Services
public class SourceCodeDownloadService : ITransientDependency
{
public ModuleProjectBuilder ModuleProjectBuilder { get; }
public PackageProjectBuilder PackageProjectBuilder { get; }
public ILogger<SourceCodeDownloadService> Logger { get; set; }
public SourceCodeDownloadService(ModuleProjectBuilder moduleProjectBuilder)
public SourceCodeDownloadService(ModuleProjectBuilder moduleProjectBuilder, PackageProjectBuilder packageProjectBuilder)
{
ModuleProjectBuilder = moduleProjectBuilder;
PackageProjectBuilder = packageProjectBuilder;
Logger = NullLogger<SourceCodeDownloadService>.Instance;
}
public async Task DownloadAsync(string moduleName, string outputFolder, string version, string gitHubAbpLocalRepositoryPath, string gitHubVoloLocalRepositoryPath, AbpCommandLineOptions options)
public async Task DownloadModuleAsync(string moduleName, string outputFolder, string version, string gitHubAbpLocalRepositoryPath, string gitHubVoloLocalRepositoryPath, AbpCommandLineOptions options)
{
Logger.LogInformation("Downloading source code of " + moduleName);
Logger.LogInformation("Version: " + version);
@ -90,6 +92,62 @@ namespace Volo.Abp.Cli.Commands.Services
Logger.LogInformation($"'{moduleName}' has been successfully downloaded to '{outputFolder}'");
}
public async Task DownloadPackageAsync(string packageName, string outputFolder, string version)
{
Logger.LogInformation("Downloading source code of " + packageName);
Logger.LogInformation("Version: " + version);
Logger.LogInformation("Output folder: " + outputFolder);
var result = await PackageProjectBuilder.BuildAsync(
new ProjectBuildArgs(
SolutionName.Parse(packageName),
packageName,
version
)
);
using (var templateFileStream = new MemoryStream(result.ZipContent))
{
using (var zipInputStream = new ZipInputStream(templateFileStream))
{
var zipEntry = zipInputStream.GetNextEntry();
while (zipEntry != null)
{
if (IsAngularTestFile(zipEntry.Name))
{
zipEntry = zipInputStream.GetNextEntry();
continue;
}
var fullZipToPath = Path.Combine(outputFolder, zipEntry.Name);
var directoryName = Path.GetDirectoryName(fullZipToPath);
if (!string.IsNullOrEmpty(directoryName))
{
Directory.CreateDirectory(directoryName);
}
var fileName = Path.GetFileName(fullZipToPath);
if (fileName.Length == 0)
{
zipEntry = zipInputStream.GetNextEntry();
continue;
}
var buffer = new byte[4096]; // 4K is optimum
using (var streamWriter = File.Create(fullZipToPath))
{
StreamUtils.Copy(zipInputStream, streamWriter, buffer);
}
zipEntry = zipInputStream.GetNextEntry();
}
}
}
Logger.LogInformation($"'{packageName}' has been successfully downloaded to '{outputFolder}'");
}
private bool IsAngularTestFile(string zipEntryName)
{
if (string.IsNullOrEmpty(zipEntryName))

5
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs

@ -159,6 +159,11 @@ namespace Volo.Abp.Cli.ProjectBuilding
private async Task<string> GetTemplateNugetVersionAsync(string name, string type, string version)
{
if (type != SourceCodeTypes.Template)
{
return null;
}
try
{
var url = $"{CliUrls.WwwAbpIo}api/download/{type}/get-nuget-version/";

21
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/PackageProjectBuildPipelineBuilder.cs

@ -0,0 +1,21 @@
using Volo.Abp.Cli.ProjectBuilding.Building.Steps;
using Volo.Abp.Cli.ProjectBuilding.Templates;
namespace Volo.Abp.Cli.ProjectBuilding.Building
{
public static class PackageProjectBuildPipelineBuilder
{
public static ProjectBuildPipeline Build(ProjectBuildContext context)
{
var pipeline = new ProjectBuildPipeline(context);
pipeline.Steps.Add(new FileEntryListReadStep());
pipeline.Steps.Add(new ProjectReferenceReplaceStep());
pipeline.Steps.Add(new ReplaceCommonPropsStep());
pipeline.Steps.Add(new ReplaceConfigureAwaitPropsStep());
pipeline.Steps.Add(new CreateProjectResultZipStep());
return pipeline;
}
}
}

7
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildContext.cs

@ -1,6 +1,7 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Volo.Abp.Cli.ProjectBuilding.Files;
using Volo.Abp.Cli.ProjectModification;
namespace Volo.Abp.Cli.ProjectBuilding.Building
{
@ -16,6 +17,8 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building
public ModuleInfo Module { get; }
public NugetPackageInfo Package { get; }
public FileEntryList Files { get; set; }
public ProjectResult Result { get; set; }
@ -25,11 +28,13 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building
public ProjectBuildContext(
TemplateInfo template,
ModuleInfo module,
NugetPackageInfo package,
[NotNull] TemplateFile templateFile,
[NotNull] ProjectBuildArgs buildArgs)
{
Template = template;
Module = module;
Package = package;
TemplateFile = Check.NotNull(templateFile, nameof(templateFile));
BuildArgs = Check.NotNull(buildArgs, nameof(buildArgs));
Symbols = new List<string>();
@ -37,4 +42,4 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building
Result = new ProjectResult();
}
}
}
}

10
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/INugetPackageInfoProvider.cs

@ -0,0 +1,10 @@
using System.Threading.Tasks;
using Volo.Abp.Cli.ProjectModification;
namespace Volo.Abp.Cli.ProjectBuilding
{
public interface INugetPackageInfoProvider
{
Task<NugetPackageInfo> GetAsync(string name);
}
}

1
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleProjectBuilder.cs

@ -67,6 +67,7 @@ namespace Volo.Abp.Cli.ProjectBuilding
var context = new ProjectBuildContext(
null,
moduleInfo,
null,
templateFile,
args
);

62
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/NugetPackageInfoProvider.cs

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Json;
using Volo.Abp.Cli.Http;
using Volo.Abp.Cli.ProjectModification;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
namespace Volo.Abp.Cli.ProjectBuilding
{
public class NugetPackageInfoProvider : INugetPackageInfoProvider, ITransientDependency
{
public IJsonSerializer JsonSerializer { get; }
public ICancellationTokenProvider CancellationTokenProvider { get; }
public IRemoteServiceExceptionHandler RemoteServiceExceptionHandler { get; }
private readonly CliHttpClientFactory _cliHttpClientFactory;
public NugetPackageInfoProvider(
IJsonSerializer jsonSerializer,
ICancellationTokenProvider cancellationTokenProvider,
IRemoteServiceExceptionHandler remoteServiceExceptionHandler,
CliHttpClientFactory cliHttpClientFactory)
{
JsonSerializer = jsonSerializer;
CancellationTokenProvider = cancellationTokenProvider;
RemoteServiceExceptionHandler = remoteServiceExceptionHandler;
_cliHttpClientFactory = cliHttpClientFactory;
}
public async Task<NugetPackageInfo> GetAsync(string name)
{
var packageList = await GetPackageListInternalAsync();
var package = packageList.FirstOrDefault(m => m.Name == name);
if (package == null)
{
throw new Exception("Package is not found or downloadable!");
}
return package;
}
private async Task<List<NugetPackageInfo>> GetPackageListInternalAsync()
{
var client = _cliHttpClientFactory.CreateClient();
using (var responseMessage = await client.GetAsync(
$"{CliUrls.WwwAbpIo}api/download/packages/",
CancellationTokenProvider.Token
))
{
await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(responseMessage);
var result = await responseMessage.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<List<NugetPackageInfo>>(result);
}
}
}
}

109
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/PackageProjectBuilder.cs

@ -0,0 +1,109 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using System;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Cli.Commands;
using Volo.Abp.Cli.Licensing;
using Volo.Abp.Cli.ProjectBuilding.Analyticses;
using Volo.Abp.Cli.ProjectBuilding.Building;
using Volo.Abp.Cli.ProjectModification;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Json;
namespace Volo.Abp.Cli.ProjectBuilding
{
public class PackageProjectBuilder : IProjectBuilder, ITransientDependency
{
public ILogger<PackageProjectBuilder> Logger { get; set; }
protected ISourceCodeStore SourceCodeStore { get; }
protected INugetPackageInfoProvider NugetPackageInfoProvider { get; }
protected ICliAnalyticsCollect CliAnalyticsCollect { get; }
protected AbpCliOptions Options { get; }
protected IJsonSerializer JsonSerializer { get; }
protected IApiKeyService ApiKeyService { get; }
public PackageProjectBuilder(ISourceCodeStore sourceCodeStore,
INugetPackageInfoProvider nugetPackageInfoProvider,
ICliAnalyticsCollect cliAnalyticsCollect,
IOptions<AbpCliOptions> options,
IJsonSerializer jsonSerializer,
IApiKeyService apiKeyService)
{
SourceCodeStore = sourceCodeStore;
NugetPackageInfoProvider = nugetPackageInfoProvider;
CliAnalyticsCollect = cliAnalyticsCollect;
Options = options.Value;
JsonSerializer = jsonSerializer;
ApiKeyService = apiKeyService;
Logger = NullLogger<PackageProjectBuilder>.Instance;
}
public async Task<ProjectBuildResult> BuildAsync(ProjectBuildArgs args)
{
var packageInfo = await GetPackageInfoAsync(args);
var templateFile = await SourceCodeStore.GetAsync(
args.TemplateName,
SourceCodeTypes.Package,
args.Version,
null,
args.ExtraProperties.ContainsKey(GetSourceCommand.Options.Preview.Long)
);
var apiKeyResult = await ApiKeyService.GetApiKeyOrNullAsync();
if (apiKeyResult?.ApiKey != null)
{
args.ExtraProperties["api-key"] = apiKeyResult.ApiKey;
}
if (apiKeyResult?.LicenseCode != null)
{
args.ExtraProperties["license-code"] = apiKeyResult.LicenseCode;
}
var context = new ProjectBuildContext(
null,
null,
packageInfo,
templateFile,
args
);
PackageProjectBuildPipelineBuilder.Build(context).Execute();
// Exclude unwanted or known options.
var options = args.ExtraProperties
.Where(x => !x.Key.Equals(CliConsts.Command, StringComparison.InvariantCultureIgnoreCase))
.Where(x => !x.Key.Equals(NewCommand.Options.OutputFolder.Long, StringComparison.InvariantCultureIgnoreCase) &&
!x.Key.Equals(NewCommand.Options.OutputFolder.Short, StringComparison.InvariantCultureIgnoreCase))
.Where(x => !x.Key.Equals(NewCommand.Options.Version.Long, StringComparison.InvariantCultureIgnoreCase) &&
!x.Key.Equals(NewCommand.Options.Version.Short, StringComparison.InvariantCultureIgnoreCase))
.Where(x => !x.Key.Equals(NewCommand.Options.TemplateSource.Short, StringComparison.InvariantCultureIgnoreCase) &&
!x.Key.Equals(NewCommand.Options.TemplateSource.Long, StringComparison.InvariantCultureIgnoreCase))
.Select(x => x.Key).ToList();
await CliAnalyticsCollect.CollectAsync(new CliAnalyticsCollectInputDto
{
Tool = Options.ToolName,
Command = args.ExtraProperties.ContainsKey(CliConsts.Command) ? args.ExtraProperties[CliConsts.Command] : "",
DatabaseProvider = null,
IsTiered = null,
UiFramework = null,
Options = JsonSerializer.Serialize(options),
ProjectName = null,
TemplateName = args.TemplateName,
TemplateVersion = templateFile.Version
});
return new ProjectBuildResult(context.Result.ZipContent, args.TemplateName);
}
private async Task<NugetPackageInfo> GetPackageInfoAsync(ProjectBuildArgs args)
{
return await NugetPackageInfoProvider.GetAsync(args.TemplateName);
}
}
}

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/SourceCodeTypes.cs

@ -5,5 +5,7 @@
public const string Template = "template";
public const string Module = "module";
public const string Package = "package";
}
}

1
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateProjectBuilder.cs

@ -105,6 +105,7 @@ namespace Volo.Abp.Cli.ProjectBuilding
var context = new ProjectBuildContext(
templateInfo,
null,
null,
templateFile,
args
);

165
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs

@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Cli.Args;
using Volo.Abp.Cli.Commands;
using Volo.Abp.Cli.Commands.Services;
using Volo.Abp.Cli.Http;
using Volo.Abp.Cli.ProjectBuilding;
using Volo.Abp.Cli.Utils;
@ -22,6 +23,8 @@ namespace Volo.Abp.Cli.ProjectModification
{
public ILogger<ProjectNugetPackageAdder> Logger { get; set; }
public BundleCommand BundleCommand { get; }
public SourceCodeDownloadService SourceCodeDownloadService { get; }
public SolutionFileModifier SolutionFileModifier { get; }
protected IJsonSerializer JsonSerializer { get; }
protected ProjectNpmPackageAdder NpmPackageAdder { get; }
@ -38,7 +41,9 @@ namespace Volo.Abp.Cli.ProjectModification
ModuleClassDependcyAdder moduleClassDependcyAdder,
IRemoteServiceExceptionHandler remoteServiceExceptionHandler,
BundleCommand bundleCommand,
CliHttpClientFactory cliHttpClientFactory)
CliHttpClientFactory cliHttpClientFactory,
SourceCodeDownloadService sourceCodeDownloadService,
SolutionFileModifier solutionFileModifier)
{
JsonSerializer = jsonSerializer;
NpmPackageAdder = npmPackageAdder;
@ -46,32 +51,162 @@ namespace Volo.Abp.Cli.ProjectModification
ModuleClassDependcyAdder = moduleClassDependcyAdder;
RemoteServiceExceptionHandler = remoteServiceExceptionHandler;
BundleCommand = bundleCommand;
SourceCodeDownloadService = sourceCodeDownloadService;
SolutionFileModifier = solutionFileModifier;
_cliHttpClientFactory = cliHttpClientFactory;
Logger = NullLogger<ProjectNugetPackageAdder>.Instance;
}
public async Task AddAsync(string projectFile, string packageName, string version = null)
public async Task AddAsync(
string projectFile,
string packageName,
string version = null,
bool useDotnetCliToInstall = true,
bool withSourceCode = false,
bool addSourceCodeToSolutionFile = false)
{
await AddAsync(
projectFile,
await FindNugetPackageInfoAsync(packageName),
version
version,
useDotnetCliToInstall,
withSourceCode,
addSourceCodeToSolutionFile
);
}
public async Task AddAsync(string projectFile, NugetPackageInfo package, string version = null,
bool useDotnetCliToInstall = true)
public async Task AddAsync(
string projectFile,
NugetPackageInfo package,
string version = null,
bool useDotnetCliToInstall = true,
bool withSourceCode = false,
bool addSourceCodeToSolutionFile = false)
{
var projectFileContent = File.ReadAllText(projectFile);
if (version == null)
{
version = GetAbpVersionOrNull(projectFile);
}
if (projectFileContent.Contains($"\"{package.Name}\""))
await AddAsPackageReference(projectFile, package, version, useDotnetCliToInstall);
if (withSourceCode)
{
await AddSourceCode(projectFile, package, version);
await ConvertPackageReferenceToProjectReference(projectFile, package);
if (addSourceCodeToSolutionFile)
{
await SolutionFileModifier.AddPackageToSolutionFileAsync(package, FindSolutionFile(projectFile));
}
}
}
private async Task ConvertPackageReferenceToProjectReference(string projectFile, NugetPackageInfo package)
{
var content = File.ReadAllText(projectFile);
var doc = new XmlDocument() {PreserveWhitespace = true};
doc.Load(StreamHelper.GenerateStreamFromString(content));
var nodes = doc.SelectNodes(
$"/Project/ItemGroup/PackageReference[starts-with(@Include, '{package.Name}')]");
if (nodes == null)
{
return;
}
if (version == null)
var downloadedProjectPath = FindRelativeFolderToDownloadPackage(projectFile, package);
var oldNodeIncludeValue = nodes[0]?.Attributes?["Include"]?.Value;
if (package.Name == oldNodeIncludeValue)
{
var referenceProjectPath = $"{downloadedProjectPath}\\{package.Name}.csproj";
var newNode = doc.CreateElement("ProjectReference");
var includeAttr = doc.CreateAttribute("Include");
includeAttr.Value = referenceProjectPath;
newNode.Attributes.Append(includeAttr);
nodes[0]?.ParentNode?.ReplaceChild(newNode, nodes[0]);
}
File.WriteAllText(projectFile, doc.OuterXml);
}
private async Task AddSourceCode(string projectFile, NugetPackageInfo package, string version = null)
{
var targetFolder = FindFolderToDownloadPackage(projectFile, package);
if (Directory.Exists(targetFolder))
{
Directory.Delete(targetFolder, true);
}
await DownloadSourceCode(targetFolder, package, version);
}
private string FindFolderToDownloadPackage(string projectFile, NugetPackageInfo package)
{
return Path.Combine(FindSolutionFolder(projectFile), "packages", package.Name);
}
private string FindRelativeFolderToDownloadPackage(string projectFile, NugetPackageInfo package)
{
var folder = Path.Combine(FindSolutionFolder(projectFile), "packages", package.Name);
return new Uri(projectFile).MakeRelativeUri(new Uri(folder)).ToString().Replace("/", "\\");
}
private async Task DownloadSourceCode(string targetFolder, NugetPackageInfo package, string version = null)
{
await SourceCodeDownloadService.DownloadPackageAsync(
package.Name,
targetFolder,
version
);
}
private string FindSolutionFile(string projectFile)
{
var folder = FindSolutionFolder(projectFile);
return Directory.GetFiles(folder, "*.sln", SearchOption.TopDirectoryOnly).FirstOrDefault();
}
private string FindSolutionFolder(string projectFile)
{
var targetFolder = Path.GetDirectoryName(projectFile);
do
{
if (Directory.GetParent(targetFolder) != null)
{
targetFolder = Directory.GetParent(targetFolder).FullName;
}
else
{
return Path.GetDirectoryName(projectFile);
}
if (Directory.GetFiles(targetFolder, "*.sln", SearchOption.TopDirectoryOnly).Any())
{
break;
}
} while (targetFolder != null);
return targetFolder;
}
private async Task AddAsPackageReference(string projectFile, NugetPackageInfo package, string version,
bool useDotnetCliToInstall)
{
var projectFileContent = File.ReadAllText(projectFile);
if (projectFileContent.Contains($"\"{package.Name}\""))
{
version = GetAbpVersionOrNull(projectFileContent);
return;
}
using (DirectoryHelper.ChangeCurrentDirectory(Path.GetDirectoryName(projectFile)))
@ -105,7 +240,9 @@ namespace Volo.Abp.Cli.ProjectModification
ModuleClassDependcyAdder.Add(moduleFiles.First(), package.ModuleClass);
}
if (useDotnetCliToInstall && (package.Target == NuGetPackageTarget.Blazor || package.Target == NuGetPackageTarget.BlazorServer || package.Target == NuGetPackageTarget.BlazorWebAssembly))
if (useDotnetCliToInstall && (package.Target == NuGetPackageTarget.Blazor ||
package.Target == NuGetPackageTarget.BlazorServer ||
package.Target == NuGetPackageTarget.BlazorWebAssembly))
{
await RunBundleForBlazorAsync(projectFile);
}
@ -125,7 +262,7 @@ namespace Volo.Abp.Cli.ProjectModification
private Task AddToCsprojManuallyAsync(string projectFile, NugetPackageInfo package, string version = null)
{
var projectFileContent = File.ReadAllText(projectFile);
var doc = new XmlDocument() { PreserveWhitespace = true };
var doc = new XmlDocument() {PreserveWhitespace = true};
doc.Load(StreamHelper.GenerateStreamFromString(projectFileContent));
var itemGroupNodes = doc.SelectNodes("/Project/ItemGroup");
@ -164,9 +301,11 @@ namespace Volo.Abp.Cli.ProjectModification
return Task.CompletedTask;
}
private string GetAbpVersionOrNull(string projectFileContent)
private string GetAbpVersionOrNull(string projectFile)
{
var doc = new XmlDocument() { PreserveWhitespace = true };
var projectFileContent = File.ReadAllText(projectFile);
var doc = new XmlDocument() {PreserveWhitespace = true};
doc.Load(StreamHelper.GenerateStreamFromString(projectFileContent));

81
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionFileModifier.cs

@ -15,7 +15,8 @@ namespace Volo.Abp.Cli.ProjectModification
var solutionFileContent = File.ReadAllText(solutionFile);
solutionFileContent.NormalizeLineEndings();
var lines = solutionFileContent.Split(new[] {Environment.NewLine, "\n"}, StringSplitOptions.None);
File.WriteAllText(solutionFile, RemoveProject(lines.ToList(), projectName).JoinAsString(Environment.NewLine));
File.WriteAllText(solutionFile,
RemoveProject(lines.ToList(), projectName).JoinAsString(Environment.NewLine));
}
public async Task AddModuleToSolutionFileAsync(ModuleWithMastersInfo module, string solutionFile)
@ -23,6 +24,49 @@ namespace Volo.Abp.Cli.ProjectModification
await AddModuleAsync(module, solutionFile);
}
public async Task AddPackageToSolutionFileAsync(NugetPackageInfo package, string solutionFile)
{
await AddPackageAsync(package, solutionFile);
}
private async Task AddPackageAsync(NugetPackageInfo package, string solutionFile)
{
var srcFolderId = await AddNewFolderAndGetIdOrGetExistingIdAsync(solutionFile, "src");
var file = File.ReadAllText(solutionFile);
var lines = file.Split(Environment.NewLine).ToList();
if (lines.Any(l => l.Contains($"\"{package.Name}\"")))
{
return;
}
var projectGuid = Guid.NewGuid().ToString();
var newProjectLine = "Project(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"" + package.Name + "\"," +
" \"packages\\" + package.Name + "\\" +
"\\" + package.Name + ".csproj\", \"{" + projectGuid + "}\""
+ Environment.NewLine + "EndProject";
lines.InsertAfter(l => l.Trim().Equals("EndProject"), newProjectLine);
var newPostSolutionLine =
" {" + projectGuid + "}.Debug|Any CPU.ActiveCfg = Debug|Any CPU" + Environment.NewLine +
" {" + projectGuid + "}.Debug|Any CPU.Build.0 = Debug|Any CPU" + Environment.NewLine +
" {" + projectGuid + "}.Release|Any CPU.ActiveCfg = Release|Any CPU" + Environment.NewLine +
" {" + projectGuid + "}.Release|Any CPU.Build.0 = Release|Any CPU";
lines.InsertAfter(l => l.Contains("GlobalSection") && l.Contains("ProjectConfigurationPlatforms"),
newPostSolutionLine);
var newPreSolutionLine =
" {" + projectGuid + "} = {" + srcFolderId + "}";
lines.InsertAfter(l => l.Contains("GlobalSection") && l.Contains("NestedProjects"), newPreSolutionLine);
File.WriteAllText(solutionFile, string.Join(Environment.NewLine, lines));
}
private List<string> RemoveProject(List<string> solutionFileLines, string projectName)
{
var projectKey = FindProjectKey(solutionFileLines, projectName);
@ -63,7 +107,8 @@ namespace Volo.Abp.Cli.ProjectModification
{
var curlyBracketStartIndex = solutionFileLine.LastIndexOf("{", StringComparison.OrdinalIgnoreCase);
var curlyBracketEndIndex = solutionFileLine.LastIndexOf("}", StringComparison.OrdinalIgnoreCase);
return solutionFileLine.Substring(curlyBracketStartIndex + 1, curlyBracketEndIndex - curlyBracketStartIndex - 1);
return solutionFileLine.Substring(curlyBracketStartIndex + 1,
curlyBracketEndIndex - curlyBracketStartIndex - 1);
}
}
@ -72,13 +117,14 @@ namespace Volo.Abp.Cli.ProjectModification
private async Task AddModuleAsync(ModuleWithMastersInfo module, string solutionFile)
{
var srcModuleFolderId = await AddNewFolderAndGetIdOrGetExistingId(solutionFile, module.Name, await AddNewFolderAndGetIdOrGetExistingId(solutionFile, "modules"));
var testModuleFolderId = await AddNewFolderAndGetIdOrGetExistingId(solutionFile, module.Name + ".Tests", await AddNewFolderAndGetIdOrGetExistingId(solutionFile, "test"));
var srcModuleFolderId = await AddNewFolderAndGetIdOrGetExistingIdAsync(solutionFile, module.Name,
await AddNewFolderAndGetIdOrGetExistingIdAsync(solutionFile, "modules"));
var testModuleFolderId = await AddNewFolderAndGetIdOrGetExistingIdAsync(solutionFile, module.Name + ".Tests",
await AddNewFolderAndGetIdOrGetExistingIdAsync(solutionFile, "test"));
var file = File.ReadAllText(solutionFile);
var lines = file.Split(Environment.NewLine).ToList();
var projectsUnderModule = Directory.GetFiles(
Path.Combine(Path.GetDirectoryName(solutionFile), "modules", module.Name),
"*.csproj",
@ -92,7 +138,7 @@ namespace Volo.Abp.Cli.ProjectModification
foreach (var projectPath in projectsUnderModule)
{
var parentFolderId = projectsUnderTest.Contains(projectPath) ? testModuleFolderId : srcModuleFolderId;
var projectId = Path.GetFileName(projectPath).Replace(".csproj","");
var projectId = Path.GetFileName(projectPath).Replace(".csproj", "");
var projectParentFolderInModule = projectsUnderTest.Contains(projectPath) ? "test" : "src";
if (lines.Any(l => l.Contains($"\"{projectId}\"")))
@ -103,8 +149,9 @@ namespace Volo.Abp.Cli.ProjectModification
var projectGuid = Guid.NewGuid().ToString();
var newProjectLine = "Project(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"" + projectId + "\"," +
" \"modules\\" + module.Name + "\\"+ projectParentFolderInModule + "\\" + projectId + "\\" + projectId + ".csproj\", \"{" + projectGuid + "}\""
+ Environment.NewLine + "EndProject";
" \"modules\\" + module.Name + "\\" + projectParentFolderInModule + "\\" +
projectId + "\\" + projectId + ".csproj\", \"{" + projectGuid + "}\""
+ Environment.NewLine + "EndProject";
lines.InsertAfter(l => l.Trim().Equals("EndProject"), newProjectLine);
@ -114,12 +161,13 @@ namespace Volo.Abp.Cli.ProjectModification
" {" + projectGuid + "}.Release|Any CPU.ActiveCfg = Release|Any CPU" + Environment.NewLine +
" {" + projectGuid + "}.Release|Any CPU.Build.0 = Release|Any CPU";
lines.InsertAfter(l=>l.Contains("GlobalSection") && l.Contains("ProjectConfigurationPlatforms"), newPostSolutionLine);
lines.InsertAfter(l => l.Contains("GlobalSection") && l.Contains("ProjectConfigurationPlatforms"),
newPostSolutionLine);
var newPreSolutionLine =
" {"+ projectGuid + "} = {"+ parentFolderId + "}";
" {" + projectGuid + "} = {" + parentFolderId + "}";
lines.InsertAfter(l=>l.Contains("GlobalSection") && l.Contains("NestedProjects"), newPreSolutionLine);
lines.InsertAfter(l => l.Contains("GlobalSection") && l.Contains("NestedProjects"), newPreSolutionLine);
}
File.WriteAllText(solutionFile, string.Join(Environment.NewLine, lines));
@ -133,19 +181,21 @@ namespace Volo.Abp.Cli.ProjectModification
}
}
private async Task<string> AddNewFolderAndGetIdOrGetExistingId(string solutionFile, string folderName, string parentFolderId = null)
private async Task<string> AddNewFolderAndGetIdOrGetExistingIdAsync(string solutionFile, string folderName,
string parentFolderId = null)
{
var file = File.ReadAllText(solutionFile);
var lines = file.Split(Environment.NewLine).ToList();
string folderId;
var folderLineIndex = lines.FindIndex(l =>
l.Contains("2150E333-8FDC-42A3-9474-1A3956D46DE8") && l.Contains("\""+ folderName + "\""));
l.Contains("2150E333-8FDC-42A3-9474-1A3956D46DE8") && l.Contains("\"" + folderName + "\""));
if (folderLineIndex < 0)
{
folderId = Guid.NewGuid().ToString();
var newFolderLine = "Project(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \""+ folderName + "\", \""+ folderName + "\", \"{" + folderId + "}\""
var newFolderLine = "Project(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"" + folderName + "\", \"" +
folderName + "\", \"{" + folderId + "}\""
+ Environment.NewLine + "EndProject";
lines.InsertAfter(l => l.Trim().Equals("EndProject"), newFolderLine);
@ -155,7 +205,8 @@ namespace Volo.Abp.Cli.ProjectModification
var newPreSolutionLine =
" {" + folderId + "} = {" + parentFolderId + "}";
lines.InsertAfter(l => l.Contains("GlobalSection") && l.Contains("NestedProjects"), newPreSolutionLine);
lines.InsertAfter(l => l.Contains("GlobalSection") && l.Contains("NestedProjects"),
newPreSolutionLine);
}
}
else

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs

@ -365,7 +365,7 @@ namespace Volo.Abp.Cli.ProjectModification
}
else
{
await SourceCodeDownloadService.DownloadAsync(
await SourceCodeDownloadService.DownloadModuleAsync(
module.Name,
targetModuleFolder,
version,

16
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyLookupConfiguration.cs

@ -5,9 +5,25 @@ namespace Volo.Abp.ObjectExtending.Modularity
public class ExtensionPropertyLookupConfiguration
{
public string Url { get; set; }
/// <summary>
/// Default value: "items".
/// </summary>
public string ResultListPropertyName { get; set; } = "items";
/// <summary>
/// Default value: "text".
/// </summary>
public string DisplayPropertyName { get; set; } = "text";
/// <summary>
/// Default value: "id".
/// </summary>
public string ValuePropertyName { get; set; } = "id";
/// <summary>
/// Default value: "filter".
/// </summary>
public string FilterParamName { get; set; } = "filter";
}
}

15
modules/cms-kit/src/Volo.CmsKit.Admin.Web/Menus/CmsKitAdminMenuContributor.cs

@ -37,7 +37,8 @@ namespace Volo.CmsKit.Admin.Web.Menus
cmsMenus.Add(new ApplicationMenuItem(
CmsKitAdminMenus.Pages.PagesMenu,
l["Pages"].Value,
"/Cms/Pages"));
"/Cms/Pages",
"fa fa-file-alt"));
}
}
@ -48,7 +49,8 @@ namespace Volo.CmsKit.Admin.Web.Menus
cmsMenus.Add(new ApplicationMenuItem(
CmsKitAdminMenus.Blogs.BlogsMenu,
l["Blogs"],
"/Cms/Blogs"
"/Cms/Blogs",
"fa fa-blog"
));
}
@ -57,7 +59,8 @@ namespace Volo.CmsKit.Admin.Web.Menus
cmsMenus.Add(new ApplicationMenuItem(
CmsKitAdminMenus.BlogPosts.BlogPostsMenu,
l["BlogPosts"],
"/Cms/BlogPosts"
"/Cms/BlogPosts",
"fa fa-file-signature"
));
}
}
@ -69,7 +72,8 @@ namespace Volo.CmsKit.Admin.Web.Menus
cmsMenus.Add(new ApplicationMenuItem(
CmsKitAdminMenus.Tags.TagsMenu,
l["Tags"].Value,
"/Cms/Tags"));
"/Cms/Tags",
"fa fa-tags"));
}
}
@ -80,7 +84,8 @@ namespace Volo.CmsKit.Admin.Web.Menus
cmsMenus.Add(new ApplicationMenuItem(
CmsKitAdminMenus.Comments.CommentsMenu,
l["Comments"].Value,
"/Cms/Comments"
"/Cms/Comments",
"fa fa-comments"
)
);
}

2
modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Blogs/EfCoreBlogPostRepository.cs

@ -68,7 +68,7 @@ namespace Volo.CmsKit.Blogs
.WhereIf(blogId.HasValue, x => x.BlogId == blogId)
.WhereIf(!string.IsNullOrWhiteSpace(filter), x => x.Title.Contains(filter) || x.Slug.Contains(filter));
queryable = queryable.OrderBy(sorting ?? $"{nameof(BlogPost.CreationTime)} desc");
queryable = queryable.OrderBy(sorting.IsNullOrEmpty() ? $"{nameof(BlogPost.CreationTime)} desc" : sorting);
var combinedResult = await queryable
.Join(

2
modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Blogs/EfCoreBlogRepository.cs

@ -38,7 +38,7 @@ namespace Volo.CmsKit.Blogs
{
var query = await GetListQueryAsync(filter);
return await query.OrderBy(sorting ?? "creationTime desc")
return await query.OrderBy(sorting.IsNullOrEmpty() ? "creationTime desc" : sorting)
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}

2
modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Comments/EfCoreCommentRepository.cs

@ -74,7 +74,7 @@ namespace Volo.CmsKit.Comments
sorting = "comment." + sorting;
}
query = query.OrderBy(sorting ?? "comment.creationTime desc")
query = query.OrderBy(sorting.IsNullOrEmpty() ? "comment.creationTime desc" : sorting)
.PageBy(skipCount, maxResultCount);
return await query.ToListAsync(token);

2
modules/cms-kit/src/Volo.CmsKit.EntityFrameworkCore/Volo/CmsKit/Pages/EfCorePageRepository.cs

@ -40,7 +40,7 @@ namespace Volo.CmsKit.Pages
!filter.IsNullOrWhiteSpace(),
x =>
x.Title.Contains(filter))
.OrderBy(sorting ?? nameof(Page.Title))
.OrderBy(sorting.IsNullOrEmpty() ? nameof(Page.Title) : sorting)
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}

2
modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogPostRepository.cs

@ -71,7 +71,7 @@ namespace Volo.CmsKit.MongoDB.Blogs
.WhereIf(blogId.HasValue, x => x.BlogId == blogId)
.WhereIf(!string.IsNullOrWhiteSpace(filter), x => x.Title.Contains(filter) || x.Slug.Contains(filter));
queryable = queryable.OrderBy(sorting ?? $"{nameof(BlogPost.CreationTime)} desc");
queryable = queryable.OrderBy(sorting.IsNullOrEmpty() ? $"{nameof(BlogPost.CreationTime)} desc" : sorting);
var combinedQueryable = queryable
.Join(

2
modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Blogs/MongoBlogRepository.cs

@ -43,7 +43,7 @@ namespace Volo.CmsKit.MongoDB.Blogs
var query = await GetListQueryAsync(filter, token);
return await query.OrderBy(sorting ?? "creationTime desc")
return await query.OrderBy(sorting.IsNullOrEmpty() ? "creationTime desc" : sorting)
.As<IMongoQueryable<Blog>>()
.PageBy<Blog, IMongoQueryable<Blog>>(skipCount, maxResultCount)
.ToListAsync(token);

2
modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Comments/MongoCommentRepository.cs

@ -67,7 +67,7 @@ namespace Volo.CmsKit.MongoDB.Comments
creationEndDate,
token);
var comments = await query.OrderBy(sorting ?? "creationTime desc")
var comments = await query.OrderBy(sorting.IsNullOrEmpty() ? "creationTime desc" : sorting)
.As<IMongoQueryable<Comment>>()
.PageBy<Comment, IMongoQueryable<Comment>>(skipCount, maxResultCount)
.ToListAsync(token);

2
modules/cms-kit/src/Volo.CmsKit.MongoDB/Volo/CmsKit/MongoDB/Pages/MongoPageRepository.cs

@ -49,7 +49,7 @@ namespace Volo.CmsKit.MongoDB.Pages
u =>
u.Title.Contains(filter)
)
.OrderBy(sorting ?? nameof(Page.Title))
.OrderBy(sorting.IsNullOrEmpty() ? nameof(Page.Title) : sorting)
.As<IMongoQueryable<Page>>()
.PageBy<Page, IMongoQueryable<Page>>(skipCount, maxResultCount)
.ToListAsync(cancellation);

2
modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/Projects/EfCoreProjectRepository.cs

@ -21,7 +21,7 @@ namespace Volo.Docs.Projects
public async Task<List<Project>> GetListAsync(string sorting, int maxResultCount, int skipCount, CancellationToken cancellationToken = default)
{
var projects = await (await GetDbSetAsync()).OrderBy(sorting ?? "Id desc")
var projects = await (await GetDbSetAsync()).OrderBy(sorting.IsNullOrEmpty() ? "Id desc" : sorting)
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));

2
modules/docs/src/Volo.Docs.MongoDB/Volo/Docs/Projects/MongoProjectRepository.cs

@ -22,7 +22,7 @@ namespace Volo.Docs.Projects
public async Task<List<Project>> GetListAsync(string sorting, int maxResultCount, int skipCount, CancellationToken cancellationToken = default)
{
var projects = await (await GetMongoQueryableAsync(cancellationToken)).OrderBy(sorting ?? "Id desc").As<IMongoQueryable<Project>>()
var projects = await (await GetMongoQueryableAsync(cancellationToken)).OrderBy(sorting.IsNullOrEmpty() ? "Id desc" : sorting).As<IMongoQueryable<Project>>()
.PageBy<Project, IMongoQueryable<Project>>(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));

27
modules/identity/src/Volo.Abp.Identity.Blazor/AbpIdentityBlazorModule.cs

@ -3,7 +3,10 @@ using Volo.Abp.AspNetCore.Components.Web.Theming.Routing;
using Volo.Abp.AutoMapper;
using Volo.Abp.BlazoriseUI;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.ObjectExtending.Modularity;
using Volo.Abp.PermissionManagement.Blazor;
using Volo.Abp.Threading;
using Volo.Abp.UI.Navigation;
namespace Volo.Abp.Identity.Blazor
@ -16,6 +19,8 @@ namespace Volo.Abp.Identity.Blazor
)]
public class AbpIdentityBlazorModule : AbpModule
{
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAutoMapperObjectMapper<AbpIdentityBlazorModule>();
@ -35,5 +40,27 @@ namespace Volo.Abp.Identity.Blazor
options.AdditionalAssemblies.Add(typeof(AbpIdentityBlazorModule).Assembly);
});
}
public override void PostConfigureServices(ServiceConfigurationContext context)
{
OneTimeRunner.Run(() =>
{
ModuleExtensionConfigurationHelper
.ApplyEntityConfigurationToUi(
IdentityModuleExtensionConsts.ModuleName,
IdentityModuleExtensionConsts.EntityNames.Role,
createFormTypes: new[] { typeof(IdentityRoleCreateDto) },
editFormTypes: new[] { typeof(IdentityRoleUpdateDto) }
);
ModuleExtensionConfigurationHelper
.ApplyEntityConfigurationToUi(
IdentityModuleExtensionConsts.ModuleName,
IdentityModuleExtensionConsts.EntityNames.User,
createFormTypes: new[] { typeof(IdentityUserCreateDto) },
editFormTypes: new[] { typeof(IdentityUserUpdateDto) }
);
});
}
}
}

93
modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor

@ -1,74 +1,35 @@
@page "/identity/roles"
@attribute [Authorize( IdentityPermissions.Roles.Default )]
@attribute [Authorize(IdentityPermissions.Roles.Default)]
@using Volo.Abp.Identity
@using Microsoft.AspNetCore.Authorization
@using Volo.Abp.PermissionManagement.Blazor.Components
@using Volo.Abp.Identity.Localization
@using Volo.Abp.AspNetCore.Components.Web
@using Volo.Abp.AspNetCore.Components.Web.Theming
@using Volo.Abp.BlazoriseUI.Components.ObjectExtending
@using Volo.Abp.AspNetCore.Components.Web.Theming.Layout
@inject AbpBlazorMessageLocalizerHelper<IdentityResource> LH
@inherits AbpCrudPageBase<IIdentityRoleAppService, IdentityRoleDto, Guid, GetIdentityRolesInput, IdentityRoleCreateDto, IdentityRoleUpdateDto>
<Card>
<CardHeader>
@* ************************* PAGE HEADER ************************* *@
<Row Class="justify-content-between">
<Column ColumnSize="ColumnSize.IsAuto">
<Heading Size="HeadingSize.Is1">@L["Roles"]</Heading>
</Column>
<Column ColumnSize="ColumnSize.IsAuto">
@if ( HasCreatePermission )
{
<Button Color="Color.Primary" Clicked="OpenCreateModalAsync">
<Icon Margin="Margin.Is1.FromRight" Name="IconName.Add"></Icon>@L["NewRole"]
</Button>
}
</Column>
</Row>
<PageHeader Title="@L["Roles"]"
BreadcrumbItems="@BreadcrumbItems"
Toolbar="@Toolbar">
</PageHeader>
</CardHeader>
<CardBody>
@* ************************* DATA GRID ************************* *@
<DataGrid TItem="IdentityRoleDto"
Data="Entities"
ReadData="OnDataGridReadAsync"
TotalItems="TotalCount"
ShowPager="true"
PageSize="PageSize">
<DataGridColumns>
<DataGridEntityActionsColumn TItem="IdentityRoleDto" @ref="@EntityActionsColumn">
<DisplayTemplate>
<EntityActions TItem="IdentityRoleDto" EntityActionsColumn="@EntityActionsColumn">
<EntityAction TItem="IdentityRoleDto"
RequiredPolicy="@UpdatePolicyName"
Clicked="() => OpenEditModalAsync(context)"
Text="@L["Edit"]"></EntityAction>
<EntityAction TItem="IdentityRoleDto"
RequiredPolicy="@ManagePermissionsPolicyName"
Clicked="() => PermissionManagementModal.OpenAsync(PermissionProviderName, context.Name, context.Name)"
Text="@L["Permissions"]"></EntityAction>
<EntityAction TItem="IdentityRoleDto"
RequiredPolicy="@DeletePolicyName"
Clicked="() => DeleteEntityAsync(context)"
ConfirmationMessage="()=> GetDeleteConfirmationMessage(context)"
Text="@L["Delete"]"></EntityAction>
</EntityActions>
</DisplayTemplate>
</DataGridEntityActionsColumn>
<DataGridColumn TItem="IdentityRoleDto" Field="@nameof(IdentityRoleDto.Name)" Caption="@L["RoleName"].Value">
<DisplayTemplate>
@(context.Name)
@if (context.IsDefault)
{
<Badge Color="Color.Primary" Margin="Margin.Is1.FromLeft">@L["DisplayName:IsDefault"]</Badge>
}
@if (context.IsPublic)
{
<Badge Color="Color.Light" Margin="Margin.Is1.FromLeft">@L["DisplayName:IsPublic"]</Badge>
}
</DisplayTemplate>
</DataGridColumn>
</DataGridColumns>
</DataGrid>
<AbpExtensibleDataGrid TItem="IdentityRoleDto"
Data="@Entities"
ReadData="@OnDataGridReadAsync"
TotalItems="@TotalCount"
ShowPager="true"
PageSize="@PageSize"
CurrentPage="@CurrentPage"
Columns="@RoleManagementTableColumns">
</AbpExtensibleDataGrid>
</CardBody>
</Card>
@ -80,7 +41,7 @@
<Form>
<ModalHeader>
<ModalTitle>@L["NewRole"]</ModalTitle>
<CloseButton Clicked="CloseCreateModalAsync" />
<CloseButton Clicked="CloseCreateModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@CreateValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
@ -89,10 +50,11 @@
<FieldLabel>@L["DisplayName:RoleName"]</FieldLabel>
<TextEdit @bind-Text="@NewEntity.Name">
<Feedback>
<ValidationError />
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
<ExtensionProperties TEntityType="IdentityRoleCreateDto" TResourceType="IdentityResource" Entity="@NewEntity" LH="@LH"/>
</Validation>
<Field>
<Check TValue="bool" @bind-Checked="@NewEntity.IsDefault">@L["DisplayName:IsDefault"]</Check>
@ -102,7 +64,7 @@
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary" Clicked="CloseCreateModalAsync">@L["Cancel"]</Button>
<SubmitButton Clicked="@CreateEntityAsync" />
<SubmitButton Clicked="@CreateEntityAsync"/>
</ModalFooter>
</Form>
</ModalContent>
@ -116,20 +78,21 @@
<Form>
<ModalHeader>
<ModalTitle>@L["Edit"]</ModalTitle>
<CloseButton Clicked="CloseEditModalAsync" />
<CloseButton Clicked="CloseEditModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@EditValidationsRef" Model="@EditingEntity" ValidateOnLoad="false">
<input type="hidden" name="ConcurrencyStamp" @bind-value="EditingEntity.ConcurrencyStamp" />
<input type="hidden" name="ConcurrencyStamp" @bind-value="EditingEntity.ConcurrencyStamp"/>
<Validation MessageLocalizer="@LH.Localize">
<Field>
<FieldLabel>@L["DisplayName:RoleName"]</FieldLabel>
<TextEdit @bind-Text="EditingEntity.Name">
<Feedback>
<ValidationError />
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
<ExtensionProperties TEntityType="IdentityRoleUpdateDto" TResourceType="IdentityResource" Entity="@EditingEntity" LH="@LH"/>
</Validation>
<Field>
<Check TValue="bool" @bind-Checked="@EditingEntity.IsDefault">@L["DisplayName:IsDefault"]</Check>
@ -139,7 +102,7 @@
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary" Clicked="CloseEditModalAsync">@L["Cancel"]</Button>
<SubmitButton Clicked="@UpdateEntityAsync" />
<SubmitButton Clicked="@UpdateEntityAsync"/>
</ModalFooter>
</Form>
</ModalContent>
@ -148,5 +111,5 @@
@if (HasManagePermissionsPermission)
{
<PermissionManagementModal @ref="PermissionManagementModal" />
}
<PermissionManagementModal @ref="PermissionManagementModal"/>
}

90
modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs

@ -1,7 +1,16 @@
using System.Threading.Tasks;
using Blazorise;
using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Components.Web.Extensibility.EntityActions;
using Volo.Abp.AspNetCore.Components.Web.Extensibility.TableColumns;
using Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars;
using Volo.Abp.Identity.Localization;
using Volo.Abp.PermissionManagement.Blazor.Components;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Data;
namespace Volo.Abp.Identity.Blazor.Pages.Identity
{
@ -10,11 +19,15 @@ namespace Volo.Abp.Identity.Blazor.Pages.Identity
protected const string PermissionProviderName = "R";
protected PermissionManagementModal PermissionManagementModal;
protected string ManagePermissionsPolicyName;
protected bool HasManagePermissionsPermission { get; set; }
protected PageToolbar Toolbar { get; } = new();
protected List<TableColumn> RoleManagementTableColumns => TableColumns.Get<RoleManagement>();
public RoleManagement()
{
ObjectMapperContext = typeof(AbpIdentityBlazorModule);
@ -26,16 +39,85 @@ namespace Volo.Abp.Identity.Blazor.Pages.Identity
ManagePermissionsPolicyName = IdentityPermissions.Roles.ManagePermissions;
}
protected override ValueTask SetEntityActionsAsync()
{
EntityActions
.Get<RoleManagement>()
.AddRange(new EntityAction[]
{
new EntityAction
{
Text = L["Edit"],
Visible = (data) => HasUpdatePermission,
Clicked = async (data) => { await OpenEditModalAsync(data.As<IdentityRoleDto>()); }
},
new EntityAction
{
Text = L["Permissions"],
Visible = (data) => HasManagePermissionsPermission,
Clicked = async (data) =>
{
await PermissionManagementModal.OpenAsync(PermissionProviderName,
data.As<IdentityRoleDto>().Name);
}
},
new EntityAction
{
Text = L["Delete"],
Visible = (data) => HasDeletePermission,
Clicked = async (data) => await DeleteEntityAsync(data.As<IdentityRoleDto>()),
ConfirmationMessage = (data) => GetDeleteConfirmationMessage(data.As<IdentityRoleDto>())
}
});
return base.SetEntityActionsAsync();
}
protected override ValueTask SetTableColumnsAsync()
{
RoleManagementTableColumns
.AddRange(new TableColumn[]
{
new TableColumn
{
Title = L["Actions"],
Actions = EntityActions.Get<RoleManagement>()
},
new TableColumn
{
Title = L["RoleName"],
Data = nameof(IdentityRoleDto.Name),
Component = typeof(RoleNameComponent)
},
});
RoleManagementTableColumns.AddRange(GetExtensionTableColumns(IdentityModuleExtensionConsts.ModuleName,
IdentityModuleExtensionConsts.EntityNames.Role));
return base.SetTableColumnsAsync();
}
protected override async Task SetPermissionsAsync()
{
await base.SetPermissionsAsync();
HasManagePermissionsPermission = await AuthorizationService.IsGrantedAsync(IdentityPermissions.Roles.ManagePermissions);
HasManagePermissionsPermission =
await AuthorizationService.IsGrantedAsync(IdentityPermissions.Roles.ManagePermissions);
}
protected override string GetDeleteConfirmationMessage(IdentityRoleDto entity)
{
return string.Format(L["RoleDeletionConfirmationMessage"], entity.Name);
}
protected override ValueTask SetToolbarItemsAsync()
{
Toolbar.AddButton(L["NewRole"],
OpenCreateModalAsync,
IconName.Add,
requiredPolicyName: CreatePolicyName);
return base.SetToolbarItemsAsync();
}
}
}
}

16
modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleNameComponent.razor

@ -0,0 +1,16 @@
@using System;
@using Volo.Abp.Identity
@using Microsoft.Extensions.Localization
@using Volo.Abp.Identity.Localization
@inject IStringLocalizer<IdentityResource> L
@(Data.As<IdentityRoleDto>().Name)
@if (Data.As<IdentityRoleDto>().IsDefault)
{
<Badge Color="Color.Primary" Margin="Margin.Is1.FromLeft">@L["DisplayName:IsDefault"]</Badge>
}
@if (Data.As<IdentityRoleDto>().IsPublic)
{
<Badge Color="Color.Light" Margin="Margin.Is1.FromLeft">@L["DisplayName:IsPublic"]</Badge>
}

9
modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleNameComponent.razor.cs

@ -0,0 +1,9 @@
using Microsoft.AspNetCore.Components;
namespace Volo.Abp.Identity.Blazor.Pages.Identity
{
public partial class RoleNameComponent : ComponentBase
{
[Parameter] public object Data { get; set; }
}
}

115
modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor

@ -1,8 +1,10 @@
@page "/identity/users"
@attribute [Authorize( IdentityPermissions.Users.Default )]
@attribute [Authorize(IdentityPermissions.Users.Default)]
@using Microsoft.AspNetCore.Authorization
@using Volo.Abp.PermissionManagement.Blazor.Components
@using Volo.Abp.BlazoriseUI.Components.ObjectExtending
@using Volo.Abp.Identity.Localization
@using Volo.Abp.AspNetCore.Components.Web.Theming.Layout
@inject AbpBlazorMessageLocalizerHelper<IdentityResource> LH
@inherits AbpCrudPageBase<IIdentityUserAppService, IdentityUserDto, Guid, GetIdentityUsersInput, IdentityUserCreateDto, IdentityUserUpdateDto>
@ -10,65 +12,22 @@
<Card>
<CardHeader>
@* ************************* PAGE HEADER ************************* *@
<Row Class="justify-content-between">
<Column ColumnSize="ColumnSize.IsAuto">
<h1>@L["Users"]</h1>
</Column>
<Column ColumnSize="ColumnSize.IsAuto">
@if ( HasCreatePermission )
{
<Button Color="Color.Primary" Clicked="OpenCreateModalAsync">
<Icon Margin="Margin.Is1.FromRight" Name="IconName.Add"></Icon>@L["NewUser"]
</Button>
}
</Column>
</Row>
<PageHeader Title="@L["Users"]"
BreadcrumbItems="@BreadcrumbItems"
Toolbar="@Toolbar">
</PageHeader>
</CardHeader>
<CardBody>
@* ************************* DATA GRID ************************* *@
<DataGrid TItem="IdentityUserDto"
Data="Entities"
ReadData="OnDataGridReadAsync"
TotalItems="TotalCount"
ShowPager="true"
PageSize="PageSize">
<DataGridColumns>
<DataGridEntityActionsColumn TItem="IdentityUserDto" @ref="@EntityActionsColumn">
<DisplayTemplate>
<EntityActions TItem="IdentityUserDto" EntityActionsColumn="@EntityActionsColumn">
<EntityAction TItem="IdentityUserDto"
RequiredPolicy="@UpdatePolicyName"
Clicked="() => OpenEditModalAsync(context)"
Text="@L["Edit"]" />
<EntityAction TItem="IdentityUserDto"
RequiredPolicy="@ManagePermissionsPolicyName"
Clicked="() => PermissionManagementModal.OpenAsync(PermissionProviderName, context.Id.ToString(), context.Name)"
Text="@L["Permissions"]" />
<EntityAction TItem="IdentityUserDto"
RequiredPolicy="@DeletePolicyName"
Clicked="() => DeleteEntityAsync(context)"
ConfirmationMessage="()=>GetDeleteConfirmationMessage(context)"
Text="@L["Delete"]" />
</EntityActions>
</DisplayTemplate>
</DataGridEntityActionsColumn>
<DataGridColumn TItem="IdentityUserDto" Field="@nameof(IdentityUserDto.UserName)" Caption="@L["UserName"].Value">
<DisplayTemplate>
@(context.As<IdentityUserDto>().UserName)
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="IdentityUserDto" Field="@nameof(IdentityUserDto.Email)" Caption="@L["Email"].Value">
<DisplayTemplate>
@(context.As<IdentityUserDto>().Email)
</DisplayTemplate>
</DataGridColumn>
<DataGridColumn TItem="IdentityUserDto" Field="@nameof(IdentityUserDto.PhoneNumber)" Caption="@L["PhoneNumber"].Value">
<DisplayTemplate>
@(context.As<IdentityUserDto>().PhoneNumber)
</DisplayTemplate>
</DataGridColumn>
</DataGridColumns>
</DataGrid>
<AbpExtensibleDataGrid TItem="IdentityUserDto"
Data="Entities"
ReadData="OnDataGridReadAsync"
TotalItems="TotalCount"
ShowPager="true"
PageSize="PageSize"
CurrentPage="@CurrentPage"
Columns="@UserManagementTableColumns">
</AbpExtensibleDataGrid>
</CardBody>
</Card>
@ -80,7 +39,7 @@
<Form>
<ModalHeader>
<ModalTitle>@L["NewUser"]</ModalTitle>
<CloseButton Clicked="CloseCreateModalAsync" />
<CloseButton Clicked="CloseCreateModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@CreateValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
@ -96,7 +55,7 @@
<FieldLabel>@L["DisplayName:UserName"]</FieldLabel>
<TextEdit @bind-Text="NewEntity.UserName">
<Feedback>
<ValidationError />
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
@ -106,7 +65,7 @@
<FieldLabel>@L["DisplayName:Name"]</FieldLabel>
<TextEdit @bind-Text="NewEntity.Name">
<Feedback>
<ValidationError />
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
@ -116,7 +75,7 @@
<FieldLabel>@L["DisplayName:Surname"]</FieldLabel>
<TextEdit @bind-Text="NewEntity.Surname">
<Feedback>
<ValidationError />
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
@ -126,7 +85,7 @@
<FieldLabel>@L["DisplayName:Password"]</FieldLabel>
<TextEdit Role="TextRole.Password" @bind-Text="NewEntity.Password">
<Feedback>
<ValidationError />
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
@ -136,7 +95,7 @@
<FieldLabel>@L["DisplayName:Email"]</FieldLabel>
<TextEdit @bind-Text="NewEntity.Email">
<Feedback>
<ValidationError />
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
@ -146,7 +105,7 @@
<FieldLabel>@L["DisplayName:PhoneNumber"]</FieldLabel>
<TextEdit @bind-Text="NewEntity.PhoneNumber">
<Feedback>
<ValidationError />
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
@ -154,6 +113,7 @@
<Field>
<Check TValue="bool" @bind-Checked="@NewEntity.LockoutEnabled">@L["DisplayName:LockoutEnabled"]</Check>
</Field>
<ExtensionProperties TEntityType="IdentityUserCreateDto" TResourceType="IdentityResource" Entity="@NewEntity" LH="@LH"/>
</TabPanel>
<TabPanel Name="Roles">
@if ( NewUserRoles != null )
@ -161,7 +121,7 @@
@foreach ( var role in NewUserRoles )
{
<Field>
<input type="hidden" @bind-value="@role.Name" />
<input type="hidden" @bind-value="@role.Name"/>
<Check TValue="bool" @bind-Checked="@role.IsAssigned">@role.Name</Check>
</Field>
}
@ -173,7 +133,7 @@
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary" Clicked="CloseCreateModalAsync">@L["Cancel"]</Button>
<SubmitButton Clicked="@CreateEntityAsync" />
<SubmitButton Clicked="@CreateEntityAsync"/>
</ModalFooter>
</Form>
</ModalContent>
@ -188,11 +148,11 @@
<Form>
<ModalHeader>
<ModalTitle>@L["Edit"]</ModalTitle>
<CloseButton Clicked="CloseEditModalAsync" />
<CloseButton Clicked="CloseEditModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@EditValidationsRef" Model="@EditingEntity" ValidateOnLoad="false">
<input type="hidden" name="ConcurrencyStamp" @bind-value="EditingEntity.ConcurrencyStamp" />
<input type="hidden" name="ConcurrencyStamp" @bind-value="EditingEntity.ConcurrencyStamp"/>
<Tabs @bind-SelectedTab="@EditModalSelectedTab">
<Items>
@ -206,7 +166,7 @@
<FieldLabel>@L["DisplayName:UserName"]</FieldLabel>
<TextEdit @bind-Text="EditingEntity.UserName">
<Feedback>
<ValidationError />
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
@ -216,7 +176,7 @@
<FieldLabel>@L["DisplayName:Name"]</FieldLabel>
<TextEdit @bind-Text="EditingEntity.Name">
<Feedback>
<ValidationError />
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
@ -226,7 +186,7 @@
<FieldLabel>@L["DisplayName:Surname"]</FieldLabel>
<TextEdit @bind-Text="EditingEntity.Surname">
<Feedback>
<ValidationError />
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
@ -236,7 +196,7 @@
<FieldLabel>@L["DisplayName:Password"]</FieldLabel>
<TextEdit Role="TextRole.Password" @bind-Text="EditingEntity.Password">
<Feedback>
<ValidationError />
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
@ -246,7 +206,7 @@
<FieldLabel>@L["DisplayName:Email"]</FieldLabel>
<TextEdit @bind-Text="EditingEntity.Email">
<Feedback>
<ValidationError />
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
@ -256,7 +216,7 @@
<FieldLabel>@L["DisplayName:PhoneNumber"]</FieldLabel>
<TextEdit @bind-Text="EditingEntity.PhoneNumber">
<Feedback>
<ValidationError />
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
@ -264,6 +224,7 @@
<Field>
<Check TValue="bool" @bind-Checked="EditingEntity.LockoutEnabled">@L["DisplayName:LockoutEnabled"]</Check>
</Field>
<ExtensionProperties TEntityType="IdentityUserUpdateDto" TResourceType="IdentityResource" Entity="@EditingEntity" LH="@LH"/>
</TabPanel>
<TabPanel Name="Roles">
@if ( EditUserRoles != null )
@ -271,7 +232,7 @@
@foreach ( var role in EditUserRoles )
{
<Field>
<input type="hidden" @bind-value="@role.Name" />
<input type="hidden" @bind-value="@role.Name"/>
<Check TValue="bool" @bind-Checked="@role.IsAssigned">@role.Name</Check>
</Field>
}
@ -283,7 +244,7 @@
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary" Clicked="CloseEditModalAsync">@L["Cancel"]</Button>
<SubmitButton Clicked="@UpdateEntityAsync" />
<SubmitButton Clicked="@UpdateEntityAsync"/>
</ModalFooter>
</Form>
</ModalContent>
@ -292,5 +253,5 @@
@if ( HasManagePermissionsPermission )
{
<PermissionManagementModal @ref="PermissionManagementModal" />
<PermissionManagementModal @ref="PermissionManagementModal"/>
}

92
modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs

@ -4,7 +4,11 @@ using System.Linq;
using System.Threading.Tasks;
using Blazorise;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.AspNetCore.Components.Web.Extensibility.EntityActions;
using Volo.Abp.AspNetCore.Components.Web.Extensibility.TableColumns;
using Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars;
using Volo.Abp.Identity.Localization;
using Volo.Abp.ObjectExtending;
using Volo.Abp.PermissionManagement.Blazor.Components;
namespace Volo.Abp.Identity.Blazor.Pages.Identity
@ -24,13 +28,17 @@ namespace Volo.Abp.Identity.Blazor.Pages.Identity
protected AssignedRoleViewModel[] EditUserRoles;
protected string ManagePermissionsPolicyName;
protected bool HasManagePermissionsPermission { get; set; }
protected string CreateModalSelectedTab = DefaultSelectedTab;
protected string EditModalSelectedTab = DefaultSelectedTab;
protected PageToolbar Toolbar { get; } = new();
private List<TableColumn> UserManagementTableColumns => TableColumns.Get<UserManagement>();
public UserManagement()
{
ObjectMapperContext = typeof(AbpIdentityBlazorModule);
@ -55,12 +63,13 @@ namespace Volo.Abp.Identity.Blazor.Pages.Identity
await HandleErrorAsync(ex);
}
}
protected override async Task SetPermissionsAsync()
{
await base.SetPermissionsAsync();
HasManagePermissionsPermission = await AuthorizationService.IsGrantedAsync(IdentityPermissions.Users.ManagePermissions);
HasManagePermissionsPermission =
await AuthorizationService.IsGrantedAsync(IdentityPermissions.Users.ManagePermissions);
}
protected override Task OpenCreateModalAsync()
@ -118,6 +127,81 @@ namespace Volo.Abp.Identity.Blazor.Pages.Identity
{
return string.Format(L["UserDeletionConfirmationMessage"], entity.UserName);
}
protected override ValueTask SetEntityActionsAsync()
{
EntityActions
.Get<UserManagement>()
.AddRange(new EntityAction[]
{
new EntityAction
{
Text = L["Edit"],
Visible = (data) => HasUpdatePermission,
Clicked = async (data) => await OpenEditModalAsync(data.As<IdentityUserDto>())
},
new EntityAction
{
Text = L["Permissions"],
Visible = (data) => HasManagePermissionsPermission,
Clicked = async (data) =>
{
await PermissionManagementModal.OpenAsync(PermissionProviderName,
data.As<IdentityUserDto>().Id.ToString());
}
},
new EntityAction
{
Text = L["Delete"],
Visible = (data) => HasDeletePermission,
Clicked = async (data) => await DeleteEntityAsync(data.As<IdentityUserDto>()),
ConfirmationMessage = (data) => GetDeleteConfirmationMessage(data.As<IdentityUserDto>())
}
});
return base.SetEntityActionsAsync();
}
protected override ValueTask SetTableColumnsAsync()
{
UserManagementTableColumns
.AddRange(new TableColumn[]
{
new TableColumn
{
Title = L["Actions"],
Actions = EntityActions.Get<UserManagement>()
},
new TableColumn
{
Title = L["UserName"],
Data = nameof(IdentityUserDto.UserName),
},
new TableColumn
{
Title = L["Email"],
Data = nameof(IdentityUserDto.Email),
},
new TableColumn
{
Title = L["PhoneNumber"],
Data = nameof(IdentityUserDto.PhoneNumber),
}
});
UserManagementTableColumns.AddRange(GetExtensionTableColumns(IdentityModuleExtensionConsts.ModuleName,
IdentityModuleExtensionConsts.EntityNames.User));
return base.SetEntityActionsAsync();
}
protected override ValueTask SetToolbarItemsAsync()
{
Toolbar.AddButton(L["NewUser"], OpenCreateModalAsync,
IconName.Add,
requiredPolicyName: CreatePolicyName);
return base.SetToolbarItemsAsync();
}
}
public class AssignedRoleViewModel
@ -126,4 +210,4 @@ namespace Volo.Abp.Identity.Blazor.Pages.Identity
public bool IsAssigned { get; set; }
}
}
}

10
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/EfCoreOrganizationUnitRepository.cs

@ -53,7 +53,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
{
return await (await GetDbSetAsync())
.IncludeDetails(includeDetails)
.OrderBy(sorting ?? nameof(OrganizationUnit.DisplayName))
.OrderBy(sorting.IsNullOrEmpty() ? nameof(OrganizationUnit.DisplayName) : sorting)
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}
@ -99,7 +99,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
select role;
query = query
.OrderBy(sorting ?? nameof(IdentityRole.Name))
.OrderBy(sorting.IsNullOrEmpty() ? nameof(IdentityRole.Name) : sorting)
.PageBy(skipCount, maxResultCount);
return await query.ToListAsync(GetCancellationToken(cancellationToken));
@ -135,7 +135,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
.Where(r => !roleIds.Contains(r.Id))
.IncludeDetails(includeDetails)
.WhereIf(!filter.IsNullOrWhiteSpace(), r => r.Name.Contains(filter))
.OrderBy(sorting ?? nameof(IdentityRole.Name))
.OrderBy(sorting.IsNullOrEmpty() ? nameof(IdentityRole.Name) : sorting)
.PageBy(skipCount, maxResultCount)
.ToListAsync(cancellationToken);
}
@ -165,7 +165,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
{
var query = await CreateGetMembersFilteredQueryAsync(organizationUnit, filter);
return await query.IncludeDetails(includeDetails).OrderBy(sorting ?? nameof(IdentityUser.UserName))
return await query.IncludeDetails(includeDetails).OrderBy(sorting.IsNullOrEmpty() ? nameof(IdentityUser.UserName) : sorting)
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}
@ -209,7 +209,7 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
return await query
.IncludeDetails(includeDetails)
.OrderBy(sorting ?? nameof(IdentityUser.Name))
.OrderBy(sorting.IsNullOrEmpty() ? nameof(IdentityUser.Name) : sorting)
.PageBy(skipCount, maxResultCount)
.ToListAsync(cancellationToken);
}

10
modules/identity/src/Volo.Abp.Identity.MongoDB/Volo/Abp/Identity/MongoDB/MongoOrganizationUnitRepository.cs

@ -61,7 +61,7 @@ namespace Volo.Abp.Identity.MongoDB
CancellationToken cancellationToken = default)
{
return await (await GetMongoQueryableAsync(cancellationToken))
.OrderBy(sorting ?? nameof(OrganizationUnit.DisplayName))
.OrderBy(sorting.IsNullOrEmpty() ? nameof(OrganizationUnit.DisplayName) : sorting)
.As<IMongoQueryable<OrganizationUnit>>()
.PageBy<OrganizationUnit, IMongoQueryable<OrganizationUnit>>(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
@ -93,7 +93,7 @@ namespace Volo.Abp.Identity.MongoDB
return await ApplyDataFilters<IMongoQueryable<IdentityRole>, IdentityRole>(
dbContext.Roles.AsQueryable().Where(r => roleIds.Contains(r.Id))
)
.OrderBy(sorting ?? nameof(IdentityRole.Name))
.OrderBy(sorting.IsNullOrEmpty() ? nameof(IdentityRole.Name) : sorting)
.As<IMongoQueryable<IdentityRole>>()
.PageBy<IdentityRole, IMongoQueryable<IdentityRole>>(skipCount, maxResultCount)
.ToListAsync(cancellationToken);
@ -126,7 +126,7 @@ namespace Volo.Abp.Identity.MongoDB
return await ApplyDataFilters<IMongoQueryable<IdentityRole>, IdentityRole>(dbContext.Roles.AsQueryable())
.Where(r => !roleIds.Contains(r.Id))
.WhereIf(!filter.IsNullOrWhiteSpace(), r => r.Name.Contains(filter))
.OrderBy(sorting ?? nameof(IdentityRole.Name))
.OrderBy(sorting.IsNullOrEmpty() ? nameof(IdentityRole.Name) : sorting)
.As<IMongoQueryable<IdentityRole>>()
.PageBy<IdentityRole, IMongoQueryable<IdentityRole>>(skipCount, maxResultCount)
.ToListAsync(cancellationToken);
@ -158,7 +158,7 @@ namespace Volo.Abp.Identity.MongoDB
cancellationToken = GetCancellationToken(cancellationToken);
var query = await CreateGetMembersFilteredQueryAsync(organizationUnit, filter, cancellationToken);
return await query
.OrderBy(sorting ?? nameof(IdentityUser.UserName))
.OrderBy(sorting.IsNullOrEmpty() ? nameof(IdentityUser.UserName) : sorting)
.As<IMongoQueryable<IdentityUser>>()
.PageBy<IdentityUser, IMongoQueryable<IdentityUser>>(skipCount, maxResultCount)
.ToListAsync(cancellationToken);
@ -193,7 +193,7 @@ namespace Volo.Abp.Identity.MongoDB
u.Email.Contains(filter) ||
(u.PhoneNumber != null && u.PhoneNumber.Contains(filter))
)
.OrderBy(sorting ?? nameof(IdentityUser.UserName))
.OrderBy(sorting.IsNullOrEmpty() ? nameof(IdentityUser.UserName) : sorting)
.As<IMongoQueryable<IdentityUser>>()
.PageBy<IdentityUser, IMongoQueryable<IdentityUser>>(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));

3
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/LinkLoginExtensionGrantValidator.cs

@ -108,7 +108,8 @@ namespace Volo.Abp.IdentityServer.AspNetIdentity
var isLinked = await IdentityLinkUserManager.IsLinkedAsync(
new IdentityLinkUserInfo(CurrentUser.GetId(), CurrentTenant.Id),
new IdentityLinkUserInfo(linkUserId, linkTenantId));
new IdentityLinkUserInfo(linkUserId, linkTenantId),
true);
if (isLinked)
{

19
modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/AbpTenantManagementBlazorModule.cs

@ -3,7 +3,10 @@ using Volo.Abp.AspNetCore.Components.Web.Theming.Routing;
using Volo.Abp.AutoMapper;
using Volo.Abp.FeatureManagement.Blazor;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.ObjectExtending.Modularity;
using Volo.Abp.TenantManagement.Blazor.Navigation;
using Volo.Abp.Threading;
using Volo.Abp.UI.Navigation;
namespace Volo.Abp.TenantManagement.Blazor
@ -15,6 +18,8 @@ namespace Volo.Abp.TenantManagement.Blazor
)]
public class AbpTenantManagementBlazorModule : AbpModule
{
private static readonly OneTimeRunner OneTimeRunner = new();
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAutoMapperObjectMapper<AbpTenantManagementBlazorModule>();
@ -34,5 +39,19 @@ namespace Volo.Abp.TenantManagement.Blazor
options.AdditionalAssemblies.Add(typeof(AbpTenantManagementBlazorModule).Assembly);
});
}
public override void PostConfigureServices(ServiceConfigurationContext context)
{
OneTimeRunner.Run(() =>
{
ModuleExtensionConfigurationHelper
.ApplyEntityConfigurationToUi(
TenantManagementModuleExtensionConsts.ModuleName,
TenantManagementModuleExtensionConsts.EntityNames.Tenant,
createFormTypes: new[] { typeof(TenantCreateDto) },
editFormTypes: new[] { typeof(TenantUpdateDto) }
);
});
}
}
}

85
modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor

@ -1,9 +1,11 @@
@page "/tenant-management/tenants"
@attribute [Authorize( TenantManagementPermissions.Tenants.Default )]
@attribute [Authorize(TenantManagementPermissions.Tenants.Default)]
@using Microsoft.AspNetCore.Authorization
@using Volo.Abp.FeatureManagement.Blazor.Components
@using Microsoft.AspNetCore.Components.Forms
@using Volo.Abp.TenantManagement.Localization
@using Volo.Abp.AspNetCore.Components.Web.Theming.Layout
@using Volo.Abp.BlazoriseUI.Components.ObjectExtending
@using Volo.Abp.AspNetCore.Components.Web
@inject AbpBlazorMessageLocalizerHelper<AbpTenantManagementResource> LH
@inherits AbpCrudPageBase<ITenantAppService, TenantDto, Guid, GetTenantsInput, TenantCreateDto, TenantUpdateDto>
@ -11,55 +13,22 @@
<Card>
<CardHeader>
@* ************************* PAGE HEADER ************************* *@
<Row Class="justify-content-between">
<Column ColumnSize="ColumnSize.IsAuto">
<Heading Size="HeadingSize.Is1">@L["Tenants"]</Heading>
</Column>
<Column ColumnSize="ColumnSize.IsAuto">
@if ( HasCreatePermission )
{
<Button Color="Color.Primary" Clicked="OpenCreateModalAsync">
<Icon Margin="Margin.Is1.FromRight" Name="IconName.Add"></Icon>@L["NewTenant"]
</Button>
}
</Column>
</Row>
<PageHeader Title="@L["Tenants"]"
BreadcrumbItems="@BreadcrumbItems"
Toolbar="@Toolbar">
</PageHeader>
</CardHeader>
<CardBody>
@* ************************* DATA GRID ************************* *@
<DataGrid TItem="TenantDto"
Data="Entities"
ReadData="OnDataGridReadAsync"
TotalItems="TotalCount"
ShowPager="true"
PageSize="PageSize">
<DataGridColumns>
<DataGridEntityActionsColumn TItem="TenantDto" @ref="@EntityActionsColumn">
<DisplayTemplate>
<EntityActions TItem="TenantDto" EntityActionsColumn="@EntityActionsColumn">
<EntityAction TItem="TenantDto"
RequiredPolicy="@UpdatePolicyName"
Clicked="() => OpenEditModalAsync(context)"
Text="@L["Edit"]"></EntityAction>
<EntityAction TItem="TenantDto"
RequiredPolicy="@ManageFeaturesPolicyName"
Clicked="() => FeatureManagementModal.OpenAsync(FeatureProviderName, context.Id.ToString())"
Text="@L["Features"]"></EntityAction>
<EntityAction TItem="TenantDto"
RequiredPolicy="@DeletePolicyName"
Clicked="() => DeleteEntityAsync(context)"
ConfirmationMessage="()=>GetDeleteConfirmationMessage(context)"
Text="@L["Delete"]"></EntityAction>
</EntityActions>
</DisplayTemplate>
</DataGridEntityActionsColumn>
<DataGridColumn TItem="TenantDto" Field="@nameof(TenantDto.Name)" Caption="@L["TenantName"].Value">
<DisplayTemplate>
@(context.Name)
</DisplayTemplate>
</DataGridColumn>
</DataGridColumns>
</DataGrid>
<AbpExtensibleDataGrid TItem="TenantDto"
Data="@Entities"
ReadData="@OnDataGridReadAsync"
TotalItems="@TotalCount"
ShowPager="true"
PageSize="@PageSize"
CurrentPage="@CurrentPage"
Columns="@TenantManagementTableColumns">
</AbpExtensibleDataGrid>
</CardBody>
</Card>
@ -71,7 +40,7 @@
<Form>
<ModalHeader>
<ModalTitle>@L["NewTenant"]</ModalTitle>
<CloseButton Clicked="CloseCreateModalAsync" />
<CloseButton Clicked="CloseCreateModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@CreateValidationsRef" Model="@NewEntity" ValidateOnLoad="false">
@ -80,7 +49,7 @@
<FieldLabel>@L["TenantName"]</FieldLabel>
<TextEdit @bind-Text="@NewEntity.Name">
<Feedback>
<ValidationError />
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
@ -90,7 +59,7 @@
<FieldLabel>@L["DisplayName:AdminEmailAddress"]</FieldLabel>
<TextEdit Role="@TextRole.Email" @bind-Text="@NewEntity.AdminEmailAddress">
<Feedback>
<ValidationError />
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
@ -100,16 +69,17 @@
<FieldLabel>@L["DisplayName:AdminPassword"]</FieldLabel>
<TextEdit Role="@TextRole.Password" @bind-Text="@NewEntity.AdminPassword">
<Feedback>
<ValidationError />
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
<ExtensionProperties TEntityType="TenantCreateDto" TResourceType="AbpTenantManagementResource" Entity="@NewEntity" LH="@LH"/>
</Validations>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary" Clicked="CloseCreateModalAsync">@L["Cancel"]</Button>
<SubmitButton Clicked="@CreateEntityAsync" />
<SubmitButton Clicked="@CreateEntityAsync"/>
</ModalFooter>
</Form>
</ModalContent>
@ -124,7 +94,7 @@
<Form>
<ModalHeader>
<ModalTitle>@L["Edit"]</ModalTitle>
<CloseButton Clicked="CloseEditModalAsync" />
<CloseButton Clicked="CloseEditModalAsync"/>
</ModalHeader>
<ModalBody>
<Validations @ref="@EditValidationsRef" Model="@EditingEntity" ValidateOnLoad="false">
@ -133,16 +103,17 @@
<FieldLabel>@L["TenantName"]</FieldLabel>
<TextEdit @bind-Text="@EditingEntity.Name">
<Feedback>
<ValidationError />
<ValidationError/>
</Feedback>
</TextEdit>
</Field>
</Validation>
</Validations>
<ExtensionProperties TEntityType="TenantUpdateDto" TResourceType="AbpTenantManagementResource" Entity="@EditingEntity" LH="@LH"/>
</ModalBody>
<ModalFooter>
<Button Color="Color.Secondary" Clicked="CloseEditModalAsync">@L["Cancel"]</Button>
<SubmitButton Clicked="@UpdateEntityAsync" />
<SubmitButton Clicked="@UpdateEntityAsync"/>
</ModalFooter>
</Form>
</ModalContent>
@ -151,5 +122,5 @@
@if (HasManageFeaturesPermission)
{
<FeatureManagementModal @ref="FeatureManagementModal" />
}
<FeatureManagementModal @ref="FeatureManagementModal"/>
}

85
modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor.cs

@ -1,6 +1,15 @@
using System.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Blazorise;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.AspNetCore.Components.Web.Extensibility.EntityActions;
using Volo.Abp.AspNetCore.Components.Web.Extensibility.TableColumns;
using Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars;
using Volo.Abp.BlazoriseUI;
using Volo.Abp.FeatureManagement.Blazor.Components;
using Volo.Abp.ObjectExtending;
using Volo.Abp.TenantManagement.Localization;
namespace Volo.Abp.TenantManagement.Blazor.Pages.TenantManagement
@ -14,6 +23,10 @@ namespace Volo.Abp.TenantManagement.Blazor.Pages.TenantManagement
protected FeatureManagementModal FeatureManagementModal;
protected PageToolbar Toolbar { get; } = new();
protected List<TableColumn> TenantManagementTableColumns => TableColumns.Get<TenantManagement>();
public TenantManagement()
{
LocalizationResource = typeof(AbpTenantManagementResource);
@ -37,5 +50,73 @@ namespace Volo.Abp.TenantManagement.Blazor.Pages.TenantManagement
{
return string.Format(L["TenantDeletionConfirmationMessage"], entity.Name);
}
protected override ValueTask SetToolbarItemsAsync()
{
Toolbar.AddButton(L["NewTenant"],
OpenCreateModalAsync,
IconName.Add,
requiredPolicyName: CreatePolicyName);
return base.SetToolbarItemsAsync();
}
protected override ValueTask SetEntityActionsAsync()
{
EntityActions
.Get<TenantManagement>()
.AddRange(new EntityAction[]
{
new EntityAction
{
Text = L["Edit"],
Visible = (data) => HasUpdatePermission,
Clicked = async (data) => { await OpenEditModalAsync(data.As<TenantDto>()); }
},
new EntityAction
{
Text = L["Features"],
Visible = (data) => HasManageFeaturesPermission,
Clicked = async (data) =>
{
var tenant = data.As<TenantDto>();
await FeatureManagementModal.OpenAsync(FeatureProviderName, tenant.Id.ToString());
}
},
new EntityAction
{
Text = L["Delete"],
Visible = (data) => HasDeletePermission,
Clicked = async (data) => await DeleteEntityAsync(data.As<TenantDto>()),
ConfirmationMessage = (data) => GetDeleteConfirmationMessage(data.As<TenantDto>())
}
});
return base.SetEntityActionsAsync();
}
protected override ValueTask SetTableColumnsAsync()
{
TenantManagementTableColumns
.AddRange(new TableColumn[]
{
new TableColumn
{
Title = L["Actions"],
Actions = EntityActions.Get<TenantManagement>()
},
new TableColumn
{
Title = L["TenantName"],
Data = nameof(TenantDto.Name),
},
});
TenantManagementTableColumns.AddRange(GetExtensionTableColumns(
TenantManagementModuleExtensionConsts.ModuleName,
TenantManagementModuleExtensionConsts.EntityNames.Tenant));
return base.SetTableColumnsAsync();
}
}
}
}

2
modules/tenant-management/src/Volo.Abp.TenantManagement.EntityFrameworkCore/Volo/Abp/TenantManagement/EntityFrameworkCore/EfCoreTenantRepository.cs

@ -62,7 +62,7 @@ namespace Volo.Abp.TenantManagement.EntityFrameworkCore
u =>
u.Name.Contains(filter)
)
.OrderBy(sorting ?? nameof(Tenant.Name))
.OrderBy(sorting.IsNullOrEmpty() ? nameof(Tenant.Name) : sorting)
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}

2
modules/tenant-management/src/Volo.Abp.TenantManagement.MongoDB/Volo/Abp/TenantManagement/MongoDb/MongoTenantRepository.cs

@ -56,7 +56,7 @@ namespace Volo.Abp.TenantManagement.MongoDB
u =>
u.Name.Contains(filter)
)
.OrderBy(sorting ?? nameof(Tenant.Name))
.OrderBy(sorting.IsNullOrEmpty() ? nameof(Tenant.Name) : sorting)
.As<IMongoQueryable<Tenant>>()
.PageBy<Tenant, IMongoQueryable<Tenant>>(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));

2
modules/users/src/Volo.Abp.Users.EntityFrameworkCore/Volo/Abp/Users/EntityFrameworkCore/EfCoreAbpUserRepositoryBase.cs

@ -48,7 +48,7 @@ namespace Volo.Abp.Users.EntityFrameworkCore
u.Name.Contains(filter) ||
u.Surname.Contains(filter)
)
.OrderBy(sorting ?? nameof(IUser.UserName))
.OrderBy(sorting.IsNullOrEmpty() ? nameof(IUser.UserName) : sorting)
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}

2
modules/users/src/Volo.Abp.Users.MongoDB/Volo/Abp/Users/MongoDB/MongoUserRepositoryBase.cs

@ -54,7 +54,7 @@ namespace Volo.Abp.Users.MongoDB
u.Name.Contains(filter) ||
u.Surname.Contains(filter)
)
.OrderBy(sorting ?? nameof(IUserData.UserName))
.OrderBy(sorting.IsNullOrEmpty() ? nameof(IUserData.UserName) : sorting)
.As<IMongoQueryable<TUser>>()
.PageBy<TUser, IMongoQueryable<TUser>>(skipCount, maxResultCount)
.ToListAsync(cancellationToken);

5
npm/ng-packs/packages/core/src/lib/services/auth.service.ts

@ -1,4 +1,5 @@
import { Injectable, Injector } from '@angular/core';
import { Params } from '@angular/router';
import { from, Observable } from 'rxjs';
import { filter, map, switchMap, take, tap } from 'rxjs/operators';
import {
@ -51,8 +52,8 @@ export class AuthService {
this.strategy.navigateToLogin();
}
navigateToLogin() {
this.strategy.navigateToLogin();
navigateToLogin(queryParams?: Params) {
this.strategy.navigateToLogin(queryParams);
}
login(params: LoginParams) {

14
npm/ng-packs/packages/core/src/lib/strategies/auth-flow.strategy.ts

@ -1,6 +1,6 @@
import { HttpHeaders } from '@angular/common/http';
import { Injector } from '@angular/core';
import { Router } from '@angular/router';
import { Params, Router } from '@angular/router';
import { Store } from '@ngxs/store';
import { AuthConfig, OAuthInfoEvent, OAuthService, OAuthStorage } from 'angular-oauth2-oidc';
import { from, Observable, of } from 'rxjs';
@ -29,7 +29,7 @@ export abstract class AuthFlowStrategy {
protected oAuthService: OAuthService;
protected oAuthConfig: AuthConfig;
abstract checkIfInternalAuth(): boolean;
abstract navigateToLogin(): void;
abstract navigateToLogin(queryParams?: Params): void;
abstract logout(): Observable<any>;
abstract login(params?: LoginParams): Observable<any>;
@ -78,8 +78,8 @@ export class AuthCodeFlowStrategy extends AuthFlowStrategy {
.then(() => this.oAuthService.setupAutomaticSilentRefresh({}, 'access_token'));
}
navigateToLogin() {
this.oAuthService.initCodeFlow();
navigateToLogin(queryParams?: Params) {
this.oAuthService.initCodeFlow(null, queryParams);
}
checkIfInternalAuth() {
@ -147,9 +147,9 @@ export class AuthPasswordFlowStrategy extends AuthFlowStrategy {
return super.init().then(() => this.listenToTokenExpiration());
}
navigateToLogin() {
navigateToLogin(queryParams?: Params) {
const router = this.injector.get(Router);
router.navigateByUrl('/account/login');
router.navigate(['/account/login'], { queryParams });
}
checkIfInternalAuth() {
@ -172,7 +172,7 @@ export class AuthPasswordFlowStrategy extends AuthFlowStrategy {
tap(res => {
this.configState.setState(res);
this.setRememberMe(params.rememberMe);
router.navigate([params.redirectUrl || '/']);
if (params.redirectUrl) router.navigate([params.redirectUrl]);
}),
);
}

4
npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts

@ -193,10 +193,10 @@ export class ModalComponent implements OnDestroy {
fromEvent(window, 'beforeunload')
.pipe(takeUntil(this.destroy$))
.subscribe(event => {
if (this.isFormDirty) {
event.preventDefault();
if (this.isFormDirty && !this.suppressUnsavedChangesWarning) {
event.returnValue = true;
} else {
event.returnValue = false;
delete event.returnValue;
}
});

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save