Browse Source

Merge branch 'dev' into Shared-User-Account

pull/24456/head
maliming 3 months ago
parent
commit
87b1aa49bb
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 20
      .github/workflows/auto-pr.yml
  2. 3
      Directory.Packages.props
  3. 4
      common.props
  4. 4
      docs/en/studio/installation.md
  5. 3
      framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiCurrentApplicationConfigurationCacheResetService.cs
  6. 9
      framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/Configuration/BlazorServerCurrentApplicationConfigurationCacheResetService.cs
  7. 5
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Configuration/ICurrentApplicationConfigurationCacheResetService.cs
  8. 3
      framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Configuration/NullCurrentApplicationConfigurationCacheResetService.cs
  9. 3
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Configuration/BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService.cs
  10. 25
      framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClientHelper.cs
  11. 13
      framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationVersionCacheItem.cs
  12. 7
      framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs
  13. 42
      framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs
  14. 27
      framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCurrentApplicationConfigurationCacheResetEventHandler.cs
  15. 14
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentApplicationConfigurationCacheResetEventData.cs
  16. 31
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs
  17. 6
      framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/ObjectExtending/MvcUiObjectExtensionPropertyInfoExtensions.cs
  18. 8
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQOptions.cs
  19. 11
      framework/src/Volo.Abp.BlazoriseUI/BlazoriseUiObjectExtensionPropertyInfoExtensions.cs
  20. 7
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs
  21. 4
      framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLoggerFactory.cs
  22. 9
      framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs
  23. 242
      framework/src/Volo.Abp.Core/Volo/Abp/Threading/KeyedLock.cs
  24. 1
      framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo.Abp.DistributedLocking.Abstractions.csproj
  25. 18
      framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/LocalAbpDistributedLock.cs
  26. 17
      framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/NullAbpDistributedLock.cs
  27. 98
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs
  28. 33
      framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/PostConfigureAbpRabbitMqEventBusOptions.cs
  29. 7
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureChecker.cs
  30. 6
      framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs
  31. 2
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceDictionary.cs
  32. 2
      framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabaseCollection.cs
  33. 2
      framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs
  34. 7
      framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingProvider.cs
  35. 2
      framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ParameterRebinder.cs
  36. 2
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs
  37. 27
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithJsonProperty.cs
  38. 21
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs
  39. 98
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs
  40. 179
      framework/test/Volo.Abp.Core.Tests/Volo/Abp/Threading/KeyedLock_Tests.cs
  41. 165
      framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs
  42. 5
      modules/docs/app/VoloDocs.Web/Pages/Error.cshtml.cs
  43. 7
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs
  44. 12
      modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDynamicInitializer.cs
  45. 14
      modules/feature-management/test/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests/Volo/Abp/FeatureManagement/EntityFrameworkCore/AbpFeatureManagementEntityFrameworkCoreTestModule.cs
  46. 9
      modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/PermissionManagementModal.razor.cs
  47. 11
      modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/AbpPermissionManagementDomainModule.cs
  48. 12
      modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDynamicInitializer.cs
  49. 14
      modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/PermissionManagementModal.cshtml.cs
  50. 2
      modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml
  51. 15
      modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementEntityFrameworkCoreTestModule.cs
  52. 7
      modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/AbpSettingManagementDomainModule.cs
  53. 12
      modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingDynamicInitializer.cs
  54. 13
      modules/setting-management/test/Volo.Abp.SettingManagement.Tests/Volo/Abp/SettingManagement/SettingManager_Basic_Tests.cs
  55. 32
      text-template-management/src/Volo.Abp.TextTemplateManagement.Domain/Volo/Abp/TextTemplateManagement/StaticTemplateDefinitionChangedEventHandler.cs

20
.github/workflows/auto-pr.yml

@ -1,13 +1,13 @@
name: Merge branch dev with rel-10.0 name: Merge branch dev with rel-10.1
on: on:
push: push:
branches: branches:
- rel-10.0 - rel-10.1
permissions: permissions:
contents: read contents: read
jobs: jobs:
merge-dev-with-rel-10-0: merge-dev-with-rel-10-1:
permissions: permissions:
contents: write # for peter-evans/create-pull-request to create branch contents: write # for peter-evans/create-pull-request to create branch
pull-requests: write # for peter-evans/create-pull-request to create a PR pull-requests: write # for peter-evans/create-pull-request to create a PR
@ -18,14 +18,14 @@ jobs:
ref: dev ref: dev
- name: Reset promotion branch - name: Reset promotion branch
run: | run: |
git fetch origin rel-10.0:rel-10.0 git fetch origin rel-10.1:rel-10.1
git reset --hard rel-10.0 git reset --hard rel-10.1
- name: Create Pull Request - name: Create Pull Request
uses: peter-evans/create-pull-request@v3 uses: peter-evans/create-pull-request@v3
with: with:
branch: auto-merge/rel-10-0/${{github.run_number}} branch: auto-merge/rel-10-1/${{github.run_number}}
title: Merge branch dev with rel-10.0 title: Merge branch dev with rel-10.1
body: This PR generated automatically to merge dev with rel-10.0. Please review the changed files before merging to prevent any errors that may occur. body: This PR generated automatically to merge dev with rel-10.1. Please review the changed files before merging to prevent any errors that may occur.
reviewers: maliming reviewers: maliming
draft: true draft: true
token: ${{ github.token }} token: ${{ github.token }}
@ -34,5 +34,5 @@ jobs:
GH_TOKEN: ${{ secrets.BOT_SECRET }} GH_TOKEN: ${{ secrets.BOT_SECRET }}
run: | run: |
gh pr ready gh pr ready
gh pr review auto-merge/rel-10-0/${{github.run_number}} --approve gh pr review auto-merge/rel-10-1/${{github.run_number}} --approve
gh pr merge auto-merge/rel-10-0/${{github.run_number}} --merge --auto --delete-branch gh pr merge auto-merge/rel-10-1/${{github.run_number}} --merge --auto --delete-branch

3
Directory.Packages.props

@ -7,7 +7,6 @@
<PackageVersion Include="AlibabaCloud.SDK.Dysmsapi20170525" Version="4.0.0" /> <PackageVersion Include="AlibabaCloud.SDK.Dysmsapi20170525" Version="4.0.0" />
<PackageVersion Include="aliyun-net-sdk-sts" Version="3.1.3" /> <PackageVersion Include="aliyun-net-sdk-sts" Version="3.1.3" />
<PackageVersion Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" /> <PackageVersion Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
<PackageVersion Include="AsyncKeyedLock" Version="7.1.8" />
<PackageVersion Include="Autofac" Version="8.4.0" /> <PackageVersion Include="Autofac" Version="8.4.0" />
<PackageVersion Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" /> <PackageVersion Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
<PackageVersion Include="Autofac.Extras.DynamicProxy" Version="7.1.0" /> <PackageVersion Include="Autofac.Extras.DynamicProxy" Version="7.1.0" />
@ -141,7 +140,7 @@
<PackageVersion Include="Polly" Version="8.6.3" /> <PackageVersion Include="Polly" Version="8.6.3" />
<PackageVersion Include="Polly.Extensions.Http" Version="3.0.0" /> <PackageVersion Include="Polly.Extensions.Http" Version="3.0.0" />
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" /> <PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
<PackageVersion Include="MySql.EntityFrameworkCore" Version="10.0.0-preview" /> <PackageVersion Include="MySql.EntityFrameworkCore" Version="10.0.0-rc" />
<PackageVersion Include="Quartz" Version="3.15.0" /> <PackageVersion Include="Quartz" Version="3.15.0" />
<PackageVersion Include="Quartz.Extensions.DependencyInjection" Version="3.15.0" /> <PackageVersion Include="Quartz.Extensions.DependencyInjection" Version="3.15.0" />
<PackageVersion Include="Quartz.Plugins.TimeZoneConverter" Version="3.15.0" /> <PackageVersion Include="Quartz.Plugins.TimeZoneConverter" Version="3.15.0" />

4
common.props

@ -1,8 +1,8 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Version>10.1.0-preview</Version> <Version>10.2.0-preview</Version>
<LeptonXVersion>5.1.0-preview</LeptonXVersion> <LeptonXVersion>5.2.0-preview</LeptonXVersion>
<NoWarn>$(NoWarn);CS1591;CS0436</NoWarn> <NoWarn>$(NoWarn);CS1591;CS0436</NoWarn>
<PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl> <PackageIconUrl>https://abp.io/assets/abp_nupkg.png</PackageIconUrl>
<PackageProjectUrl>https://abp.io/</PackageProjectUrl> <PackageProjectUrl>https://abp.io/</PackageProjectUrl>

4
docs/en/studio/installation.md

@ -65,3 +65,7 @@ When you see the "New Version Available" window, follow these steps to upgrade A
2. A progress indicator will display the download status. 2. A progress indicator will display the download status.
3. Once the download is complete, a new modal will appear with the "Install and Relaunch" buttons. 3. Once the download is complete, a new modal will appear with the "Install and Relaunch" buttons.
4. Click on the "Install and Relaunch" button to complete the installation process. 4. Click on the "Install and Relaunch" button to complete the installation process.
## Installing a Specific Version
There is no official support for installing an older version of ABP Studio yet. But, if you want to install an older version of ABP Studio, you can use approach explanined here [https://github.com/enisn/AbpDevTools?tab=readme-ov-file#switch-abp-studio-version](https://github.com/enisn/AbpDevTools?tab=readme-ov-file#switch-abp-studio-version)

3
framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiCurrentApplicationConfigurationCacheResetService.cs

@ -1,3 +1,4 @@
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Components.Web.Configuration; using Volo.Abp.AspNetCore.Components.Web.Configuration;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -16,7 +17,7 @@ public class MauiCurrentApplicationConfigurationCacheResetService :
_mauiBlazorCachedApplicationConfigurationClient = mauiBlazorCachedApplicationConfigurationClient; _mauiBlazorCachedApplicationConfigurationClient = mauiBlazorCachedApplicationConfigurationClient;
} }
public async Task ResetAsync() public async Task ResetAsync(Guid? userId = null)
{ {
await _mauiBlazorCachedApplicationConfigurationClient.InitializeAsync(); await _mauiBlazorCachedApplicationConfigurationClient.InitializeAsync();
} }

9
framework/src/Volo.Abp.AspNetCore.Components.Server/Volo/Abp/AspNetCore/Components/Server/Configuration/BlazorServerCurrentApplicationConfigurationCacheResetService.cs

@ -1,4 +1,5 @@
using System.Threading.Tasks; using System;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Components.Web.Configuration; using Volo.Abp.AspNetCore.Components.Web.Configuration;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -19,10 +20,8 @@ public class BlazorServerCurrentApplicationConfigurationCacheResetService :
_localEventBus = localEventBus; _localEventBus = localEventBus;
} }
public async Task ResetAsync() public async Task ResetAsync(Guid? userId = null)
{ {
await _localEventBus.PublishAsync( await _localEventBus.PublishAsync(new CurrentApplicationConfigurationCacheResetEventData(userId));
new CurrentApplicationConfigurationCacheResetEventData()
);
} }
} }

5
framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Configuration/ICurrentApplicationConfigurationCacheResetService.cs

@ -1,8 +1,9 @@
using System.Threading.Tasks; using System;
using System.Threading.Tasks;
namespace Volo.Abp.AspNetCore.Components.Web.Configuration; namespace Volo.Abp.AspNetCore.Components.Web.Configuration;
public interface ICurrentApplicationConfigurationCacheResetService public interface ICurrentApplicationConfigurationCacheResetService
{ {
Task ResetAsync(); Task ResetAsync(Guid? userId = null);
} }

3
framework/src/Volo.Abp.AspNetCore.Components.Web/Volo/Abp/AspNetCore/Components/Web/Configuration/NullCurrentApplicationConfigurationCacheResetService.cs

@ -1,3 +1,4 @@
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -5,7 +6,7 @@ namespace Volo.Abp.AspNetCore.Components.Web.Configuration;
public class NullCurrentApplicationConfigurationCacheResetService : ICurrentApplicationConfigurationCacheResetService, ISingletonDependency public class NullCurrentApplicationConfigurationCacheResetService : ICurrentApplicationConfigurationCacheResetService, ISingletonDependency
{ {
public Task ResetAsync() public Task ResetAsync(Guid? userId = null)
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }

3
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/Configuration/BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService.cs

@ -1,3 +1,4 @@
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Components.Web.Configuration; using Volo.Abp.AspNetCore.Components.Web.Configuration;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -16,7 +17,7 @@ public class BlazorWebAssemblyCurrentApplicationConfigurationCacheResetService :
_webAssemblyCachedApplicationConfigurationClient = webAssemblyCachedApplicationConfigurationClient; _webAssemblyCachedApplicationConfigurationClient = webAssemblyCachedApplicationConfigurationClient;
} }
public async Task ResetAsync() public async Task ResetAsync(Guid? userId = null)
{ {
await _webAssemblyCachedApplicationConfigurationClient.InitializeAsync(); await _webAssemblyCachedApplicationConfigurationClient.InitializeAsync();
} }

25
framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClientHelper.cs

@ -1,13 +1,26 @@
using System.Globalization; using System;
using Volo.Abp.Users; using System.Globalization;
using System.Threading.Tasks;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.Client; namespace Volo.Abp.AspNetCore.Mvc.Client;
public static class MvcCachedApplicationConfigurationClientHelper public class MvcCachedApplicationConfigurationClientHelper : ITransientDependency
{ {
public static string CreateCacheKey(ICurrentUser currentUser) protected IDistributedCache<MvcCachedApplicationVersionCacheItem> ApplicationVersionCache { get; }
public MvcCachedApplicationConfigurationClientHelper(IDistributedCache<MvcCachedApplicationVersionCacheItem> applicationVersionCache)
{
ApplicationVersionCache = applicationVersionCache;
}
public virtual async Task<string> CreateCacheKeyAsync(Guid? userId)
{ {
var userKey = currentUser.Id?.ToString("N") ?? "Anonymous"; var appVersion = await ApplicationVersionCache.GetOrAddAsync(MvcCachedApplicationVersionCacheItem.CacheKey,
return $"ApplicationConfiguration_{userKey}_{CultureInfo.CurrentUICulture.Name}"; () => Task.FromResult(new MvcCachedApplicationVersionCacheItem(Guid.NewGuid().ToString("N")))) ??
new MvcCachedApplicationVersionCacheItem(Guid.NewGuid().ToString("N"));
var userKey = userId?.ToString("N") ?? "Anonymous";
return $"ApplicationConfiguration_{appVersion.Version}_{userKey}_{CultureInfo.CurrentUICulture.Name}";
} }
} }

13
framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationVersionCacheItem.cs

@ -0,0 +1,13 @@
namespace Volo.Abp.AspNetCore.Mvc.Client;
public class MvcCachedApplicationVersionCacheItem
{
public const string CacheKey = "Mvc_Application_Version";
public string Version { get; set; }
public MvcCachedApplicationVersionCacheItem(string version)
{
Version = version;
}
}

7
framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common/Volo/Abp/AspNetCore/Mvc/Client/RemoteDynamicClaimsPrincipalContributorCache.cs

@ -20,6 +20,7 @@ public class RemoteDynamicClaimsPrincipalContributorCache : RemoteDynamicClaimsP
protected IHttpClientFactory HttpClientFactory { get; } protected IHttpClientFactory HttpClientFactory { get; }
protected IRemoteServiceHttpClientAuthenticator HttpClientAuthenticator { get; } protected IRemoteServiceHttpClientAuthenticator HttpClientAuthenticator { get; }
protected IDistributedCache<ApplicationConfigurationDto> ApplicationConfigurationDtoCache { get; } protected IDistributedCache<ApplicationConfigurationDto> ApplicationConfigurationDtoCache { get; }
protected MvcCachedApplicationConfigurationClientHelper CacheHelper { get; }
protected ICurrentUser CurrentUser { get; } protected ICurrentUser CurrentUser { get; }
public RemoteDynamicClaimsPrincipalContributorCache( public RemoteDynamicClaimsPrincipalContributorCache(
@ -28,7 +29,8 @@ public class RemoteDynamicClaimsPrincipalContributorCache : RemoteDynamicClaimsP
IOptions<AbpClaimsPrincipalFactoryOptions> abpClaimsPrincipalFactoryOptions, IOptions<AbpClaimsPrincipalFactoryOptions> abpClaimsPrincipalFactoryOptions,
IRemoteServiceHttpClientAuthenticator httpClientAuthenticator, IRemoteServiceHttpClientAuthenticator httpClientAuthenticator,
IDistributedCache<ApplicationConfigurationDto> applicationConfigurationDtoCache, IDistributedCache<ApplicationConfigurationDto> applicationConfigurationDtoCache,
ICurrentUser currentUser) ICurrentUser currentUser,
MvcCachedApplicationConfigurationClientHelper cacheHelper)
: base(abpClaimsPrincipalFactoryOptions) : base(abpClaimsPrincipalFactoryOptions)
{ {
Cache = cache; Cache = cache;
@ -36,6 +38,7 @@ public class RemoteDynamicClaimsPrincipalContributorCache : RemoteDynamicClaimsP
HttpClientAuthenticator = httpClientAuthenticator; HttpClientAuthenticator = httpClientAuthenticator;
ApplicationConfigurationDtoCache = applicationConfigurationDtoCache; ApplicationConfigurationDtoCache = applicationConfigurationDtoCache;
CurrentUser = currentUser; CurrentUser = currentUser;
CacheHelper = cacheHelper;
} }
protected async override Task<AbpDynamicClaimCacheItem?> GetCacheAsync(Guid userId, Guid? tenantId = null) protected async override Task<AbpDynamicClaimCacheItem?> GetCacheAsync(Guid userId, Guid? tenantId = null)
@ -56,7 +59,7 @@ public class RemoteDynamicClaimsPrincipalContributorCache : RemoteDynamicClaimsP
catch (Exception e) catch (Exception e)
{ {
Logger.LogWarning(e, $"Failed to refresh remote claims for user: {userId}"); Logger.LogWarning(e, $"Failed to refresh remote claims for user: {userId}");
await ApplicationConfigurationDtoCache.RemoveAsync(MvcCachedApplicationConfigurationClientHelper.CreateCacheKey(CurrentUser)); await ApplicationConfigurationDtoCache.RemoveAsync(await CacheHelper.CreateCacheKeyAsync(CurrentUser.Id));
throw; throw;
} }
} }

42
framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs

@ -1,3 +1,4 @@
using System;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Distributed;
@ -13,14 +14,18 @@ namespace Volo.Abp.AspNetCore.Mvc.Client;
public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigurationClient, ITransientDependency public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigurationClient, ITransientDependency
{ {
private const string ApplicationConfigurationDtoCacheKey = "ApplicationConfigurationDto_CacheKey";
protected IHttpContextAccessor HttpContextAccessor { get; } protected IHttpContextAccessor HttpContextAccessor { get; }
protected AbpApplicationConfigurationClientProxy ApplicationConfigurationAppService { get; } protected AbpApplicationConfigurationClientProxy ApplicationConfigurationAppService { get; }
protected AbpApplicationLocalizationClientProxy ApplicationLocalizationClientProxy { get; } protected AbpApplicationLocalizationClientProxy ApplicationLocalizationClientProxy { get; }
protected ICurrentUser CurrentUser { get; } protected ICurrentUser CurrentUser { get; }
protected MvcCachedApplicationConfigurationClientHelper CacheHelper { get; }
protected IDistributedCache<ApplicationConfigurationDto> Cache { get; } protected IDistributedCache<ApplicationConfigurationDto> Cache { get; }
protected AbpAspNetCoreMvcClientCacheOptions Options { get; } protected AbpAspNetCoreMvcClientCacheOptions Options { get; }
public MvcCachedApplicationConfigurationClient( public MvcCachedApplicationConfigurationClient(
MvcCachedApplicationConfigurationClientHelper cacheHelper,
IDistributedCache<ApplicationConfigurationDto> cache, IDistributedCache<ApplicationConfigurationDto> cache,
AbpApplicationConfigurationClientProxy applicationConfigurationAppService, AbpApplicationConfigurationClientProxy applicationConfigurationAppService,
ICurrentUser currentUser, ICurrentUser currentUser,
@ -33,13 +38,27 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu
HttpContextAccessor = httpContextAccessor; HttpContextAccessor = httpContextAccessor;
ApplicationLocalizationClientProxy = applicationLocalizationClientProxy; ApplicationLocalizationClientProxy = applicationLocalizationClientProxy;
Options = options.Value; Options = options.Value;
CacheHelper = cacheHelper;
Cache = cache; Cache = cache;
} }
public async Task<ApplicationConfigurationDto> GetAsync() public virtual async Task<ApplicationConfigurationDto> GetAsync()
{ {
var cacheKey = CreateCacheKey(); string? cacheKey = null;
var httpContext = HttpContextAccessor?.HttpContext; var httpContext = HttpContextAccessor?.HttpContext;
if (httpContext != null && httpContext.Items[ApplicationConfigurationDtoCacheKey] is string key)
{
cacheKey = key;
}
if (cacheKey.IsNullOrWhiteSpace())
{
cacheKey = await CreateCacheKeyAsync();
if (httpContext != null)
{
httpContext.Items[ApplicationConfigurationDtoCacheKey] = cacheKey;
}
}
if (httpContext != null && httpContext.Items[cacheKey] is ApplicationConfigurationDto configuration) if (httpContext != null && httpContext.Items[cacheKey] is ApplicationConfigurationDto configuration)
{ {
@ -86,8 +105,21 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu
public ApplicationConfigurationDto Get() public ApplicationConfigurationDto Get()
{ {
var cacheKey = CreateCacheKey(); string? cacheKey = null;
var httpContext = HttpContextAccessor?.HttpContext; var httpContext = HttpContextAccessor?.HttpContext;
if (httpContext != null && httpContext.Items[ApplicationConfigurationDtoCacheKey] is string key)
{
cacheKey = key;
}
if (cacheKey.IsNullOrWhiteSpace())
{
cacheKey = AsyncHelper.RunSync(CreateCacheKeyAsync);
if (httpContext != null)
{
httpContext.Items[ApplicationConfigurationDtoCacheKey] = cacheKey;
}
}
if (httpContext != null && httpContext.Items[cacheKey] is ApplicationConfigurationDto configuration) if (httpContext != null && httpContext.Items[cacheKey] is ApplicationConfigurationDto configuration)
{ {
@ -97,8 +129,8 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu
return AsyncHelper.RunSync(GetAsync); return AsyncHelper.RunSync(GetAsync);
} }
protected virtual string CreateCacheKey() protected virtual async Task<string> CreateCacheKeyAsync()
{ {
return MvcCachedApplicationConfigurationClientHelper.CreateCacheKey(CurrentUser); return await CacheHelper.CreateCacheKeyAsync(CurrentUser.Id);
} }
} }

27
framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCurrentApplicationConfigurationCacheResetEventHandler.cs

@ -3,7 +3,6 @@ using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
using Volo.Abp.Caching; using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus; using Volo.Abp.EventBus;
using Volo.Abp.Users;
namespace Volo.Abp.AspNetCore.Mvc.Client; namespace Volo.Abp.AspNetCore.Mvc.Client;
@ -11,23 +10,29 @@ public class MvcCurrentApplicationConfigurationCacheResetEventHandler :
ILocalEventHandler<CurrentApplicationConfigurationCacheResetEventData>, ILocalEventHandler<CurrentApplicationConfigurationCacheResetEventData>,
ITransientDependency ITransientDependency
{ {
protected ICurrentUser CurrentUser { get; }
protected IDistributedCache<ApplicationConfigurationDto> Cache { get; } protected IDistributedCache<ApplicationConfigurationDto> Cache { get; }
protected IDistributedCache<MvcCachedApplicationVersionCacheItem> ApplicationVersionCache { get; }
protected MvcCachedApplicationConfigurationClientHelper CacheHelper { get; }
public MvcCurrentApplicationConfigurationCacheResetEventHandler(ICurrentUser currentUser, public MvcCurrentApplicationConfigurationCacheResetEventHandler(
IDistributedCache<ApplicationConfigurationDto> cache) IDistributedCache<ApplicationConfigurationDto> cache,
IDistributedCache<MvcCachedApplicationVersionCacheItem> applicationVersionCache,
MvcCachedApplicationConfigurationClientHelper cacheHelper)
{ {
CurrentUser = currentUser;
Cache = cache; Cache = cache;
ApplicationVersionCache = applicationVersionCache;
CacheHelper = cacheHelper;
} }
public virtual async Task HandleEventAsync(CurrentApplicationConfigurationCacheResetEventData eventData) public virtual async Task HandleEventAsync(CurrentApplicationConfigurationCacheResetEventData eventData)
{ {
await Cache.RemoveAsync(CreateCacheKey()); if (eventData.UserId.HasValue)
} {
await Cache.RemoveAsync(await CacheHelper.CreateCacheKeyAsync(eventData.UserId));
protected virtual string CreateCacheKey() }
{ else
return MvcCachedApplicationConfigurationClientHelper.CreateCacheKey(CurrentUser); {
await ApplicationVersionCache.RemoveAsync(MvcCachedApplicationVersionCacheItem.CacheKey);
}
} }
} }

14
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentApplicationConfigurationCacheResetEventData.cs

@ -1,9 +1,21 @@
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; using System;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
/// <summary> /// <summary>
/// This event is used to invalidate current user's cached configuration. /// This event is used to invalidate current user's cached configuration.
/// </summary> /// </summary>
public class CurrentApplicationConfigurationCacheResetEventData public class CurrentApplicationConfigurationCacheResetEventData
{ {
public Guid? UserId { get; set; }
public CurrentApplicationConfigurationCacheResetEventData()
{
}
public CurrentApplicationConfigurationCacheResetEventData(Guid? userId)
{
UserId = userId;
}
} }

31
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Frozen;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
@ -23,7 +24,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker;
public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelperService<TTagHelper> public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelperService<TTagHelper>
where TTagHelper : AbpDatePickerBaseTagHelper<TTagHelper> where TTagHelper : AbpDatePickerBaseTagHelper<TTagHelper>
{ {
protected readonly Dictionary<Type, Func<object, string>> SupportedInputTypes; protected readonly FrozenDictionary<Type, Func<object, string>> SupportedInputTypes;
protected readonly IJsonSerializer JsonSerializer; protected readonly IJsonSerializer JsonSerializer;
protected readonly IHtmlGenerator Generator; protected readonly IHtmlGenerator Generator;
@ -103,7 +104,7 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
return string.Empty; return string.Empty;
} }
} }
}; }.ToFrozenDictionary();
} }
protected virtual T? GetAttribute<T>() where T : Attribute protected virtual T? GetAttribute<T>() where T : Attribute
@ -136,7 +137,7 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
? await ProcessButtonAndGetContentAsync(context, output, "calendar", "open") ? await ProcessButtonAndGetContentAsync(context, output, "calendar", "open")
: ""; : "";
var clearButtonContent = TagHelper.ClearButton == true || (!TagHelper.ClearButton.HasValue && TagHelper.AutoUpdateInput != true) var clearButtonContent = TagHelper.ClearButton == true || (!TagHelper.ClearButton.HasValue && TagHelper.AutoUpdateInput != true)
? await ProcessButtonAndGetContentAsync(context, output, "times", "clear", visible:!TagHelper.SingleOpenAndClearButton) ? await ProcessButtonAndGetContentAsync(context, output, "times", "clear", visible: !TagHelper.SingleOpenAndClearButton)
: ""; : "";
var labelContent = await GetLabelAsHtmlAsync(context, output, TagHelperOutput); var labelContent = await GetLabelAsHtmlAsync(context, output, TagHelperOutput);
@ -269,7 +270,7 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
{ {
var attrList = new TagHelperAttributeList(); var attrList = new TagHelperAttributeList();
if(options == null) if (options == null)
{ {
return attrList; return attrList;
} }
@ -401,29 +402,29 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
attrList.Add("data-visible-date-format", options.VisibleDateFormat); attrList.Add("data-visible-date-format", options.VisibleDateFormat);
} }
if(!options.InputDateFormat.IsNullOrEmpty()) if (!options.InputDateFormat.IsNullOrEmpty())
{ {
attrList.Add("data-input-date-format", options.InputDateFormat); attrList.Add("data-input-date-format", options.InputDateFormat);
} }
if(options.Ranges != null && options.Ranges.Any()) if (options.Ranges != null && options.Ranges.Any())
{ {
var ranges = options.Ranges.ToDictionary(r => r.Label, r => r.Dates); var ranges = options.Ranges.ToDictionary(r => r.Label, r => r.Dates);
attrList.Add("data-ranges", JsonSerializer.Serialize(ranges)); attrList.Add("data-ranges", JsonSerializer.Serialize(ranges));
} }
if(options.AlwaysShowCalendars != null) if (options.AlwaysShowCalendars != null)
{ {
attrList.Add("data-always-show-calendars", options.AlwaysShowCalendars.ToString()!.ToLowerInvariant()); attrList.Add("data-always-show-calendars", options.AlwaysShowCalendars.ToString()!.ToLowerInvariant());
} }
if(options.ShowCustomRangeLabel == false) if (options.ShowCustomRangeLabel == false)
{ {
attrList.Add("data-show-custom-range-label", options.ShowCustomRangeLabel.ToString()!.ToLowerInvariant()); attrList.Add("data-show-custom-range-label", options.ShowCustomRangeLabel.ToString()!.ToLowerInvariant());
} }
if(options.Options != null) if (options.Options != null)
{ {
attrList.Add("data-options", JsonSerializer.Serialize(options.Options)); attrList.Add("data-options", JsonSerializer.Serialize(options.Options));
} }
@ -443,7 +444,7 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
attrList.Add("id", options.PickerId); attrList.Add("id", options.PickerId);
} }
if(!options.SingleOpenAndClearButton) if (!options.SingleOpenAndClearButton)
{ {
attrList.Add("data-single-open-and-clear-button", options.SingleOpenAndClearButton.ToString().ToLowerInvariant()); attrList.Add("data-single-open-and-clear-button", options.SingleOpenAndClearButton.ToString().ToLowerInvariant());
} }
@ -614,7 +615,8 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
{ {
return string.Empty; return string.Empty;
} }
var labelTagHelper = new LabelTagHelper(Generator) { var labelTagHelper = new LabelTagHelper(Generator)
{
ViewContext = TagHelper.ViewContext, ViewContext = TagHelper.ViewContext,
For = modelExpression For = modelExpression
}; };
@ -764,7 +766,8 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
TagHelper.Size = attribute.Size; TagHelper.Size = attribute.Size;
} }
return TagHelper.Size switch { return TagHelper.Size switch
{
AbpFormControlSize.Small => "form-control-sm", AbpFormControlSize.Small => "form-control-sm",
AbpFormControlSize.Medium => "form-control-md", AbpFormControlSize.Medium => "form-control-md",
AbpFormControlSize.Large => "form-control-lg", AbpFormControlSize.Large => "form-control-lg",
@ -785,14 +788,14 @@ public abstract class AbpDatePickerBaseTagHelperService<TTagHelper> : AbpTagHelp
protected virtual async Task<string> GetValidationAsHtmlByInputAsync(TagHelperContext context, protected virtual async Task<string> GetValidationAsHtmlByInputAsync(TagHelperContext context,
TagHelperOutput output, TagHelperOutput output,
[NotNull]ModelExpression @for) [NotNull] ModelExpression @for)
{ {
var validationMessageTagHelper = var validationMessageTagHelper =
new ValidationMessageTagHelper(Generator) { For = @for, ViewContext = TagHelper.ViewContext }; new ValidationMessageTagHelper(Generator) { For = @for, ViewContext = TagHelper.ViewContext };
var attributeList = new TagHelperAttributeList { { "class", "text-danger" } }; var attributeList = new TagHelperAttributeList { { "class", "text-danger" } };
if(!output.Attributes.TryGetAttribute("name", out var nameAttribute) || nameAttribute == null || nameAttribute.Value == null) if (!output.Attributes.TryGetAttribute("name", out var nameAttribute) || nameAttribute == null || nameAttribute.Value == null)
{ {
if (nameAttribute != null) if (nameAttribute != null)
{ {

6
framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/ObjectExtending/MvcUiObjectExtensionPropertyInfoExtensions.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Frozen;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -8,7 +9,8 @@ namespace Volo.Abp.ObjectExtending;
public static class MvcUiObjectExtensionPropertyInfoExtensions public static class MvcUiObjectExtensionPropertyInfoExtensions
{ {
private static readonly HashSet<Type> NumberTypes = new HashSet<Type> { private static readonly FrozenSet<Type> NumberTypes = new HashSet<Type>
{
typeof(int), typeof(int),
typeof(long), typeof(long),
typeof(byte), typeof(byte),
@ -33,7 +35,7 @@ public static class MvcUiObjectExtensionPropertyInfoExtensions
typeof(float?), typeof(float?),
typeof(double?), typeof(double?),
typeof(decimal?) typeof(decimal?)
}; }.ToFrozenSet();
public static string? GetInputFormatOrNull(this IBasicObjectExtensionPropertyInfo property) public static string? GetInputFormatOrNull(this IBasicObjectExtensionPropertyInfo property)
{ {

8
framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQOptions.cs

@ -5,11 +5,11 @@ namespace Volo.Abp.BackgroundWorkers.TickerQ;
public class AbpBackgroundWorkersTickerQOptions public class AbpBackgroundWorkersTickerQOptions
{ {
private readonly Dictionary<Type, AbpBackgroundWorkersCronTickerConfiguration> _onfigurations; private readonly Dictionary<Type, AbpBackgroundWorkersCronTickerConfiguration> _configurations;
public AbpBackgroundWorkersTickerQOptions() public AbpBackgroundWorkersTickerQOptions()
{ {
_onfigurations = new Dictionary<Type, AbpBackgroundWorkersCronTickerConfiguration>(); _configurations = new Dictionary<Type, AbpBackgroundWorkersCronTickerConfiguration>();
} }
public void AddConfiguration<TWorker>(AbpBackgroundWorkersCronTickerConfiguration configuration) public void AddConfiguration<TWorker>(AbpBackgroundWorkersCronTickerConfiguration configuration)
@ -19,7 +19,7 @@ public class AbpBackgroundWorkersTickerQOptions
public void AddConfiguration(Type workerType, AbpBackgroundWorkersCronTickerConfiguration configuration) public void AddConfiguration(Type workerType, AbpBackgroundWorkersCronTickerConfiguration configuration)
{ {
_onfigurations[workerType] = configuration; _configurations[workerType] = configuration;
} }
public AbpBackgroundWorkersCronTickerConfiguration? GetConfigurationOrNull<TJob>() public AbpBackgroundWorkersCronTickerConfiguration? GetConfigurationOrNull<TJob>()
@ -29,6 +29,6 @@ public class AbpBackgroundWorkersTickerQOptions
public AbpBackgroundWorkersCronTickerConfiguration? GetConfigurationOrNull(Type workerType) public AbpBackgroundWorkersCronTickerConfiguration? GetConfigurationOrNull(Type workerType)
{ {
return _onfigurations.GetValueOrDefault(workerType); return _configurations.GetValueOrDefault(workerType);
} }
} }

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

@ -1,5 +1,6 @@
using Blazorise; using Blazorise;
using System; using System;
using System.Collections.Frozen;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
@ -11,7 +12,8 @@ namespace Volo.Abp.BlazoriseUI;
public static class BlazoriseUiObjectExtensionPropertyInfoExtensions public static class BlazoriseUiObjectExtensionPropertyInfoExtensions
{ {
private static readonly HashSet<Type> NumberTypes = new HashSet<Type> { private static readonly FrozenSet<Type> NumberTypes = new HashSet<Type>
{
typeof(int), typeof(int),
typeof(long), typeof(long),
typeof(byte), typeof(byte),
@ -36,13 +38,14 @@ public static class BlazoriseUiObjectExtensionPropertyInfoExtensions
typeof(float?), typeof(float?),
typeof(double?), typeof(double?),
typeof(decimal?) typeof(decimal?)
}; }.ToFrozenSet();
private static readonly HashSet<Type> TextEditSupportedAttributeTypes = new HashSet<Type> { private static readonly FrozenSet<Type> TextEditSupportedAttributeTypes = new HashSet<Type>
{
typeof(EmailAddressAttribute), typeof(EmailAddressAttribute),
typeof(UrlAttribute), typeof(UrlAttribute),
typeof(PhoneAttribute) typeof(PhoneAttribute)
}; }.ToFrozenSet();
public static string? GetDateEditInputFormatOrNull(this IBasicObjectExtensionPropertyInfo property) public static string? GetDateEditInputFormatOrNull(this IBasicObjectExtensionPropertyInfo property)
{ {

7
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/NpmPackagesUpdater.cs

@ -3,14 +3,12 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NuGet.Versioning; using NuGet.Versioning;
using Volo.Abp.Cli.Http;
using Volo.Abp.Cli.LIbs; using Volo.Abp.Cli.LIbs;
using Volo.Abp.Cli.Utils; using Volo.Abp.Cli.Utils;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -28,14 +26,12 @@ public class NpmPackagesUpdater : ITransientDependency
private readonly PackageJsonFileFinder _packageJsonFileFinder; private readonly PackageJsonFileFinder _packageJsonFileFinder;
private readonly NpmGlobalPackagesChecker _npmGlobalPackagesChecker; private readonly NpmGlobalPackagesChecker _npmGlobalPackagesChecker;
private readonly Dictionary<string, string> _fileVersionStorage = new Dictionary<string, string>(); private readonly Dictionary<string, string> _fileVersionStorage = [];
private readonly CliHttpClientFactory _cliHttpClientFactory;
public NpmPackagesUpdater( public NpmPackagesUpdater(
PackageJsonFileFinder packageJsonFileFinder, PackageJsonFileFinder packageJsonFileFinder,
NpmGlobalPackagesChecker npmGlobalPackagesChecker, NpmGlobalPackagesChecker npmGlobalPackagesChecker,
ICancellationTokenProvider cancellationTokenProvider, ICancellationTokenProvider cancellationTokenProvider,
CliHttpClientFactory cliHttpClientFactory,
IInstallLibsService installLibsService, IInstallLibsService installLibsService,
ICmdHelper cmdHelper) ICmdHelper cmdHelper)
{ {
@ -44,7 +40,6 @@ public class NpmPackagesUpdater : ITransientDependency
CancellationTokenProvider = cancellationTokenProvider; CancellationTokenProvider = cancellationTokenProvider;
InstallLibsService = installLibsService; InstallLibsService = installLibsService;
CmdHelper = cmdHelper; CmdHelper = cmdHelper;
_cliHttpClientFactory = cliHttpClientFactory;
Logger = NullLogger<NpmPackagesUpdater>.Instance; Logger = NullLogger<NpmPackagesUpdater>.Instance;
} }

4
framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLoggerFactory.cs

@ -5,10 +5,10 @@ namespace Volo.Abp.Logging;
public class DefaultInitLoggerFactory : IInitLoggerFactory public class DefaultInitLoggerFactory : IInitLoggerFactory
{ {
private readonly Dictionary<Type, object> _cache = new Dictionary<Type, object>(); private readonly Dictionary<Type, object> _cache = [];
public virtual IInitLogger<T> Create<T>() public virtual IInitLogger<T> Create<T>()
{ {
return (IInitLogger<T>)_cache.GetOrAdd(typeof(T), () => new DefaultInitLogger<T>()); ; return (IInitLogger<T>)_cache.GetOrAdd(typeof(T), () => new DefaultInitLogger<T>());
} }
} }

9
framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Frozen;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.ComponentModel; using System.ComponentModel;
@ -13,14 +14,14 @@ namespace Volo.Abp.Reflection;
public static class TypeHelper public static class TypeHelper
{ {
private static readonly HashSet<Type> FloatingTypes = new HashSet<Type> private static readonly FrozenSet<Type> FloatingTypes = new HashSet<Type>
{ {
typeof(float), typeof(float),
typeof(double), typeof(double),
typeof(decimal) typeof(decimal)
}; }.ToFrozenSet();
private static readonly HashSet<Type> NonNullablePrimitiveTypes = new HashSet<Type> private static readonly FrozenSet<Type> NonNullablePrimitiveTypes = new HashSet<Type>
{ {
typeof(byte), typeof(byte),
typeof(short), typeof(short),
@ -37,7 +38,7 @@ public static class TypeHelper
typeof(DateTimeOffset), typeof(DateTimeOffset),
typeof(TimeSpan), typeof(TimeSpan),
typeof(Guid) typeof(Guid)
}; }.ToFrozenSet();
public static bool IsNonNullablePrimitiveType(Type type) public static bool IsNonNullablePrimitiveType(Type type)
{ {

242
framework/src/Volo.Abp.Core/Volo/Abp/Threading/KeyedLock.cs

@ -0,0 +1,242 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Volo.Abp.Threading;
/// <summary>
/// Per-key asynchronous lock for coordinating concurrent flows.
/// </summary>
/// <remarks>
/// Based on the pattern described in https://stackoverflow.com/a/31194647.
/// Use within a <c>using</c> scope to ensure the lock is released via <c>IDisposable.Dispose()</c>.
/// </remarks>
public static class KeyedLock
{
private static readonly Dictionary<object, RefCounted<SemaphoreSlim>> SemaphoreSlims = new();
/// <summary>
/// Acquires an exclusive asynchronous lock for the specified <paramref name="key"/>.
/// This method waits until the lock becomes available.
/// </summary>
/// <param name="key">A non-null object that identifies the lock. Objects considered equal by dictionary semantics will share the same lock.</param>
/// <returns>An <see cref="IDisposable"/> handle that must be disposed to release the lock.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="key"/> is <see langword="null"/>.</exception>
/// <example>
/// <code>
/// var key = "my-critical-section";
/// using (await KeyedLock.LockAsync(key))
/// {
/// // protected work
/// }
/// </code>
/// </example>
public static async Task<IDisposable> LockAsync(object key)
{
Check.NotNull(key, nameof(key));
return await LockAsync(key, CancellationToken.None);
}
/// <summary>
/// Acquires an exclusive asynchronous lock for the specified <paramref name="key"/>, observing a <paramref name="cancellationToken"/>.
/// </summary>
/// <param name="key">A non-null object that identifies the lock. Objects considered equal by dictionary semantics will share the same lock.</param>
/// <param name="cancellationToken">A token to cancel the wait for the lock.</param>
/// <returns>An <see cref="IDisposable"/> handle that must be disposed to release the lock.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="key"/> is <see langword="null"/>.</exception>
/// <exception cref="OperationCanceledException">Thrown if the wait is canceled via <paramref name="cancellationToken"/>.</exception>
/// <example>
/// <code>
/// var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
/// using (await KeyedLock.LockAsync("db-update", cts.Token))
/// {
/// // protected work
/// }
/// </code>
/// </example>
public static async Task<IDisposable> LockAsync(object key, CancellationToken cancellationToken)
{
Check.NotNull(key, nameof(key));
var semaphore = GetOrCreate(key);
try
{
await semaphore.WaitAsync(cancellationToken);
}
catch (OperationCanceledException)
{
var toDispose = DecrementRefAndMaybeRemove(key);
toDispose?.Dispose();
throw;
}
return new Releaser(key);
}
/// <summary>
/// Attempts to acquire an exclusive lock for the specified <paramref name="key"/> without waiting.
/// </summary>
/// <param name="key">A non-null object that identifies the lock.</param>
/// <returns>
/// An <see cref="IDisposable"/> handle if the lock was immediately acquired; otherwise <see langword="null"/>.
/// </returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="key"/> is <see langword="null"/>.</exception>
/// <example>
/// <code>
/// var handle = await KeyedLock.TryLockAsync("cache-key");
/// if (handle != null)
/// {
/// using (handle)
/// {
/// // protected work
/// }
/// }
/// </code>
/// </example>
public static async Task<IDisposable?> TryLockAsync(object key)
{
Check.NotNull(key, nameof(key));
return await TryLockAsync(key, default, CancellationToken.None);
}
/// <summary>
/// Attempts to acquire an exclusive lock for the specified <paramref name="key"/>, waiting up to <paramref name="timeout"/>.
/// </summary>
/// <param name="key">A non-null object that identifies the lock.</param>
/// <param name="timeout">Maximum time to wait for the lock. If set to <see cref="TimeSpan.Zero"/>, the method performs an immediate, non-blocking attempt.</param>
/// <param name="cancellationToken">A token to cancel the wait.</param>
/// <returns>
/// An <see cref="IDisposable"/> handle if the lock was acquired within the timeout; otherwise <see langword="null"/>.
/// </returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="key"/> is <see langword="null"/>.</exception>
/// <exception cref="OperationCanceledException">Thrown if the wait is canceled via <paramref name="cancellationToken"/>.</exception>
/// <example>
/// <code>
/// var handle = await KeyedLock.TryLockAsync("send-mail", TimeSpan.FromSeconds(1));
/// if (handle != null)
/// {
/// using (handle)
/// {
/// // protected work
/// }
/// }
/// else
/// {
/// // lock not acquired within timeout
/// }
/// </code>
/// </example>
public static async Task<IDisposable?> TryLockAsync(object key, TimeSpan timeout, CancellationToken cancellationToken = default)
{
Check.NotNull(key, nameof(key));
var semaphore = GetOrCreate(key);
bool acquired;
try
{
if (timeout == default)
{
acquired = await semaphore.WaitAsync(0, cancellationToken);
}
else
{
acquired = await semaphore.WaitAsync(timeout, cancellationToken);
}
}
catch (OperationCanceledException)
{
var toDispose = DecrementRefAndMaybeRemove(key);
toDispose?.Dispose();
throw;
}
if (acquired)
{
return new Releaser(key);
}
var toDisposeOnFail = DecrementRefAndMaybeRemove(key);
toDisposeOnFail?.Dispose();
return null;
}
private static SemaphoreSlim GetOrCreate(object key)
{
RefCounted<SemaphoreSlim> item;
lock (SemaphoreSlims)
{
if (SemaphoreSlims.TryGetValue(key, out item!))
{
++item.RefCount;
}
else
{
item = new RefCounted<SemaphoreSlim>(new SemaphoreSlim(1, 1));
SemaphoreSlims[key] = item;
}
}
return item.Value;
}
private sealed class RefCounted<T>(T value)
{
public int RefCount { get; set; } = 1;
public T Value { get; } = value;
}
private sealed class Releaser(object key) : IDisposable
{
private int _disposed;
public void Dispose()
{
if (Interlocked.Exchange(ref _disposed, 1) == 1)
{
return;
}
RefCounted<SemaphoreSlim> item;
var shouldDispose = false;
lock (SemaphoreSlims)
{
if (!SemaphoreSlims.TryGetValue(key, out item!))
{
return;
}
--item.RefCount;
if (item.RefCount == 0)
{
SemaphoreSlims.Remove(key);
shouldDispose = true;
}
}
if (shouldDispose)
{
item.Value.Dispose();
}
else
{
item.Value.Release();
}
}
}
private static SemaphoreSlim? DecrementRefAndMaybeRemove(object key)
{
RefCounted<SemaphoreSlim>? itemToDispose = null;
lock (SemaphoreSlims)
{
if (SemaphoreSlims.TryGetValue(key, out var item))
{
--item.RefCount;
if (item.RefCount == 0)
{
SemaphoreSlims.Remove(key);
itemToDispose = item;
}
}
}
return itemToDispose?.Value;
}
}

1
framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo.Abp.DistributedLocking.Abstractions.csproj

@ -18,7 +18,6 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" /> <ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" />
<PackageReference Include="AsyncKeyedLock" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" /> <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
</ItemGroup> </ItemGroup>

18
framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/LocalAbpDistributedLock.cs

@ -1,19 +1,13 @@
using System; using System;
using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsyncKeyedLock;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
namespace Volo.Abp.DistributedLocking; namespace Volo.Abp.DistributedLocking;
public class LocalAbpDistributedLock : IAbpDistributedLock, ISingletonDependency public class LocalAbpDistributedLock : IAbpDistributedLock, ISingletonDependency
{ {
private readonly AsyncKeyedLocker<string> _localSyncObjects = new(o =>
{
o.PoolSize = 20;
o.PoolInitialFill = 1;
});
protected IDistributedLockKeyNormalizer DistributedLockKeyNormalizer { get; } protected IDistributedLockKeyNormalizer DistributedLockKeyNormalizer { get; }
public LocalAbpDistributedLock(IDistributedLockKeyNormalizer distributedLockKeyNormalizer) public LocalAbpDistributedLock(IDistributedLockKeyNormalizer distributedLockKeyNormalizer)
@ -21,7 +15,6 @@ public class LocalAbpDistributedLock : IAbpDistributedLock, ISingletonDependency
DistributedLockKeyNormalizer = distributedLockKeyNormalizer; DistributedLockKeyNormalizer = distributedLockKeyNormalizer;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public async Task<IAbpDistributedLockHandle?> TryAcquireAsync( public async Task<IAbpDistributedLockHandle?> TryAcquireAsync(
string name, string name,
TimeSpan timeout = default, TimeSpan timeout = default,
@ -29,12 +22,11 @@ public class LocalAbpDistributedLock : IAbpDistributedLock, ISingletonDependency
{ {
Check.NotNullOrWhiteSpace(name, nameof(name)); Check.NotNullOrWhiteSpace(name, nameof(name));
var key = DistributedLockKeyNormalizer.NormalizeKey(name); var key = DistributedLockKeyNormalizer.NormalizeKey(name);
var disposable = await KeyedLock.TryLockAsync(key, timeout, cancellationToken);
var timeoutReleaser = await _localSyncObjects.LockOrNullAsync(key, timeout, cancellationToken); if (disposable == null)
if (timeoutReleaser is not null)
{ {
return new LocalAbpDistributedLockHandle(timeoutReleaser); return null;
} }
return null; return new LocalAbpDistributedLockHandle(disposable);
} }
} }

17
framework/src/Volo.Abp.DistributedLocking.Abstractions/Volo/Abp/DistributedLocking/NullAbpDistributedLock.cs

@ -0,0 +1,17 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Volo.Abp.DistributedLocking;
/// <summary>
/// This implementation of <see cref="IAbpDistributedLock"/> does not provide any distributed locking functionality.
/// Useful in scenarios where distributed locking is not required or during testing.
/// </summary>
public class NullAbpDistributedLock : IAbpDistributedLock
{
public Task<IAbpDistributedLockHandle?> TryAcquireAsync(string name, TimeSpan timeout = default, CancellationToken cancellationToken = default)
{
return Task.FromResult<IAbpDistributedLockHandle?>(new LocalAbpDistributedLockHandle(NullDisposable.Instance));
}
}

98
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs

@ -5,6 +5,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -108,12 +109,17 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
} }
var entityType = entity.GetType(); var entityType = entity.GetType();
var entityFullName = entityType.FullName!;
if (entityEntry.Metadata.HasSharedClrType && !entityEntry.Metadata.IsOwned())
{
entityFullName = entityEntry.Metadata.Name;
}
var entityChange = new EntityChangeInfo var entityChange = new EntityChangeInfo
{ {
ChangeType = changeType, ChangeType = changeType,
EntityEntry = entityEntry, EntityEntry = entityEntry,
EntityId = entityId, EntityId = entityId,
EntityTypeFullName = entityType.FullName, EntityTypeFullName = entityFullName,
PropertyChanges = GetPropertyChanges(entityEntry), PropertyChanges = GetPropertyChanges(entityEntry),
EntityTenantId = GetTenantId(entity) EntityTenantId = GetTenantId(entity)
}; };
@ -181,48 +187,102 @@ public class EntityHistoryHelper : IEntityHistoryHelper, ITransientDependency
foreach (var property in properties) foreach (var property in properties)
{ {
if (entityEntry.Metadata.IsMappedToJson() && property.GetJsonPropertyName() == null)
{
continue;
}
var propertyEntry = entityEntry.Property(property.Name); var propertyEntry = entityEntry.Property(property.Name);
if (ShouldSavePropertyHistory(propertyEntry, isCreated || isDeleted) && !IsSoftDeleted(entityEntry)) if (ShouldSavePropertyHistory(propertyEntry, isCreated || isDeleted) && !IsSoftDeleted(entityEntry))
{ {
var propertyType = DeterminePropertyTypeFromEntry(property, propertyEntry);
propertyChanges.Add(new EntityPropertyChangeInfo propertyChanges.Add(new EntityPropertyChangeInfo
{ {
NewValue = isDeleted ? null : JsonSerializer.Serialize(propertyEntry.CurrentValue!).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength), NewValue = isDeleted ? null : JsonSerializer.Serialize(propertyEntry.CurrentValue!).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength),
OriginalValue = isCreated ? null : JsonSerializer.Serialize(propertyEntry.OriginalValue!).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength), OriginalValue = isCreated ? null : JsonSerializer.Serialize(propertyEntry.OriginalValue!).TruncateWithPostfix(EntityPropertyChangeInfo.MaxValueLength),
PropertyName = property.Name, PropertyName = property.Name,
PropertyTypeFullName = property.ClrType.GetFirstGenericArgumentIfNullable().FullName! PropertyTypeFullName = propertyType.FullName!
}); });
} }
} }
if (AbpEfCoreNavigationHelper != null) if (AbpEfCoreNavigationHelper == null)
{
return propertyChanges;
}
foreach (var (navigationEntry, index) in entityEntry.Navigations.Select((value, i) => ( value, i )))
{ {
foreach (var (navigationEntry, index) in entityEntry.Navigations.Select((value, i) => ( value, i ))) var propertyInfo = navigationEntry.Metadata.PropertyInfo;
if (propertyInfo != null &&
propertyInfo.IsDefined(typeof(DisableAuditingAttribute), true))
{ {
var propertyInfo = navigationEntry.Metadata.PropertyInfo; continue;
if (propertyInfo != null && }
propertyInfo.IsDefined(typeof(DisableAuditingAttribute), true))
if (navigationEntry.Metadata.TargetEntityType.IsMappedToJson() && navigationEntry is ReferenceEntry referenceEntry && referenceEntry.TargetEntry != null)
{
foreach (var propertyChange in GetPropertyChanges(referenceEntry.TargetEntry))
{ {
continue; propertyChange.PropertyName = $"{referenceEntry.Metadata.Name}.{propertyChange.PropertyName}";
propertyChanges.Add(propertyChange);
} }
if (AbpEfCoreNavigationHelper.IsNavigationEntryModified(entityEntry, index)) continue;
}
if (AbpEfCoreNavigationHelper.IsNavigationEntryModified(entityEntry, index))
{
var abpNavigationEntry = AbpEfCoreNavigationHelper.GetNavigationEntry(entityEntry, index);
var isCollection = navigationEntry.Metadata.IsCollection;
propertyChanges.Add(new EntityPropertyChangeInfo
{ {
var abpNavigationEntry = AbpEfCoreNavigationHelper.GetNavigationEntry(entityEntry, index); PropertyName = navigationEntry.Metadata.Name,
var isCollection = navigationEntry.Metadata.IsCollection; PropertyTypeFullName = navigationEntry.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName!,
propertyChanges.Add(new EntityPropertyChangeInfo OriginalValue = GetNavigationPropertyValue(abpNavigationEntry?.OriginalValue, isCollection),
{ NewValue = GetNavigationPropertyValue(abpNavigationEntry?.CurrentValue, isCollection)
PropertyName = navigationEntry.Metadata.Name, });
PropertyTypeFullName = navigationEntry.Metadata.ClrType.GetFirstGenericArgumentIfNullable().FullName!,
OriginalValue = GetNavigationPropertyValue(abpNavigationEntry?.OriginalValue, isCollection),
NewValue = GetNavigationPropertyValue(abpNavigationEntry?.CurrentValue, isCollection)
});
}
} }
} }
return propertyChanges; return propertyChanges;
} }
/// <summary>
/// Determines the CLR type of a property based on its EF Core metadata and the values in the given <see cref="PropertyEntry"/>.
/// </summary>
/// <param name="property">The EF Core property metadata that provides the declared CLR type.</param>
/// <param name="propertyEntry">The property entry that contains the current and original values for the property.</param>
/// <returns>
/// The most specific CLR type inferred for the property. This is normally the property's declared CLR type (with
/// nullable wrappers removed). If the declared type is <see cref="object"/>, the type is inferred from the
/// runtime type of <see cref="PropertyEntry.CurrentValue"/> or, if that is <c>null</c>, from
/// <see cref="PropertyEntry.OriginalValue"/>. If both values are <c>null</c>, the declared CLR type
/// (which may remain <see cref="object"/>) is returned.
/// </returns>
protected virtual Type DeterminePropertyTypeFromEntry(IProperty property, PropertyEntry propertyEntry)
{
var propertyType = property.ClrType.GetFirstGenericArgumentIfNullable();
if (propertyType != typeof(object))
{
return propertyType;
}
if (propertyEntry.CurrentValue != null)
{
propertyType = propertyEntry.CurrentValue.GetType().GetFirstGenericArgumentIfNullable();
}
else if (propertyEntry.OriginalValue != null)
{
propertyType = propertyEntry.OriginalValue.GetType().GetFirstGenericArgumentIfNullable();
}
return propertyType;
}
protected virtual string? GetNavigationPropertyValue(object? entity, bool isCollection) protected virtual string? GetNavigationPropertyValue(object? entity, bool isCollection)
{ {
switch (entity) switch (entity)

33
framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/PostConfigureAbpRabbitMqEventBusOptions.cs

@ -1,3 +1,4 @@
using System.Collections.Frozen;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -5,23 +6,23 @@ namespace Volo.Abp.EventBus.RabbitMq;
public class PostConfigureAbpRabbitMqEventBusOptions : IPostConfigureOptions<AbpRabbitMqEventBusOptions> public class PostConfigureAbpRabbitMqEventBusOptions : IPostConfigureOptions<AbpRabbitMqEventBusOptions>
{ {
private readonly HashSet<string> _uint64QueueArguments = private readonly FrozenSet<string> _uint64QueueArguments = new HashSet<string>
[ {
"x-delivery-limit", "x-delivery-limit",
"x-expires", "x-expires",
"x-message-ttl", "x-message-ttl",
"x-max-length", "x-max-length",
"x-max-length-bytes", "x-max-length-bytes",
"x-quorum-initial-group-size", "x-quorum-initial-group-size",
"x-quorum-target-group-size", "x-quorum-target-group-size",
"x-stream-filter-size-bytes", "x-stream-filter-size-bytes",
"x-stream-max-segment-size-bytes", "x-stream-max-segment-size-bytes",
]; }.ToFrozenSet();
private readonly HashSet<string> _boolQueueArguments = private readonly FrozenSet<string> _boolQueueArguments = new HashSet<string>
[ {
"x-single-active-consumer" "x-single-active-consumer"
]; }.ToFrozenSet();
public virtual void PostConfigure(string? name, AbpRabbitMqEventBusOptions options) public virtual void PostConfigure(string? name, AbpRabbitMqEventBusOptions options)
{ {

7
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureChecker.cs

@ -28,7 +28,12 @@ public class FeatureChecker : FeatureCheckerBase
public override async Task<string?> GetOrNullAsync(string name) public override async Task<string?> GetOrNullAsync(string name)
{ {
var featureDefinition = await FeatureDefinitionManager.GetAsync(name); var featureDefinition = await FeatureDefinitionManager.GetOrNullAsync(name);
if (featureDefinition == null)
{
return null;
}
var providers = FeatureValueProviderManager.ValueProviders var providers = FeatureValueProviderManager.ValueProviders
.Reverse(); .Reverse();

6
framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Frozen;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -10,7 +11,8 @@ internal static class ProxyScriptingJsFuncHelper
{ {
private const string ValidJsVariableNameChars = "abcdefghijklmnopqrstuxwvyzABCDEFGHIJKLMNOPQRSTUXWVYZ0123456789_"; private const string ValidJsVariableNameChars = "abcdefghijklmnopqrstuxwvyzABCDEFGHIJKLMNOPQRSTUXWVYZ0123456789_";
private static readonly HashSet<string> ReservedWords = new HashSet<string> { private static readonly FrozenSet<string> ReservedWords = new HashSet<string>
{
"abstract", "abstract",
"else", "else",
"instanceof", "instanceof",
@ -71,7 +73,7 @@ internal static class ProxyScriptingJsFuncHelper
"in", "in",
"static", "static",
"with" "with"
}; }.ToFrozenSet();
public static string NormalizeJsVariableName(string name, string additionalChars = "") public static string NormalizeJsVariableName(string name, string additionalChars = "")
{ {

2
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/LocalizationResourceDictionary.cs

@ -6,7 +6,7 @@ namespace Volo.Abp.Localization;
public class LocalizationResourceDictionary : Dictionary<string, LocalizationResourceBase> public class LocalizationResourceDictionary : Dictionary<string, LocalizationResourceBase>
{ {
private readonly Dictionary<Type, LocalizationResourceBase> _resourcesByTypes = new(); private readonly Dictionary<Type, LocalizationResourceBase> _resourcesByTypes = [];
public LocalizationResource Add<TResouce>(string? defaultCultureName = null) public LocalizationResource Add<TResouce>(string? defaultCultureName = null)
{ {

2
framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDatabaseCollection.cs

@ -9,7 +9,7 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb;
public class MemoryDatabaseCollection<TEntity> : IMemoryDatabaseCollection<TEntity> public class MemoryDatabaseCollection<TEntity> : IMemoryDatabaseCollection<TEntity>
where TEntity : class, IEntity where TEntity : class, IEntity
{ {
private readonly Dictionary<string, byte[]> _dictionary = new Dictionary<string, byte[]>(); private readonly Dictionary<string, byte[]> _dictionary = [];
private readonly IMemoryDbSerializer _memoryDbSerializer; private readonly IMemoryDbSerializer _memoryDbSerializer;

2
framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/MongoModelBuilder.cs

@ -17,7 +17,7 @@ public class MongoModelBuilder : IMongoModelBuilder
{ {
private readonly Dictionary<Type, object> _entityModelBuilders; private readonly Dictionary<Type, object> _entityModelBuilders;
private static readonly object SyncObj = new object(); private static readonly object SyncObj = new();
public MongoModelBuilder() public MongoModelBuilder()
{ {

7
framework/src/Volo.Abp.Settings/Volo/Abp/Settings/SettingProvider.cs

@ -23,7 +23,12 @@ public class SettingProvider : ISettingProvider, ITransientDependency
public virtual async Task<string?> GetOrNullAsync(string name) public virtual async Task<string?> GetOrNullAsync(string name)
{ {
var setting = await SettingDefinitionManager.GetAsync(name); var setting = await SettingDefinitionManager.GetOrNullAsync(name);
if (setting == null)
{
return null;
}
var providers = Enumerable var providers = Enumerable
.Reverse(SettingValueProviderManager.Providers); .Reverse(SettingValueProviderManager.Providers);

2
framework/src/Volo.Abp.Specifications/Volo/Abp/Specifications/ParameterRebinder.cs

@ -15,7 +15,7 @@ internal class ParameterRebinder : ExpressionVisitor
internal ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map) internal ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
{ {
_map = map ?? new Dictionary<ParameterExpression, ParameterExpression>(); _map = map ?? [];
} }
internal static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, internal static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map,

2
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs

@ -59,6 +59,8 @@ public class AbpAuditingTestModule : AbpModule
"AppEntityWithValueObject", "AppEntityWithValueObject",
type => type == typeof(AppEntityWithValueObject) || type == typeof(AppEntityWithValueObjectAddress)) type => type == typeof(AppEntityWithValueObject) || type == typeof(AppEntityWithValueObjectAddress))
); );
options.EntityHistorySelectors.Add(new NamedTypeSelector(nameof(AppEntityWithJsonProperty), type => type == typeof(AppEntityWithJsonProperty)));
}); });
context.Services.AddType<Auditing_Tests.MyAuditedObject1>(); context.Services.AddType<Auditing_Tests.MyAuditedObject1>();

27
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/Entities/AppEntityWithJsonProperty.cs

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Domain.Entities.Auditing;
namespace Volo.Abp.Auditing.App.Entities;
public class AppEntityWithJsonProperty : FullAuditedAggregateRoot<Guid>
{
public string Name { get; set; }
public JsonPropertyObject Data { get; set; }
public int Count { get; set; }
public AppEntityWithJsonProperty()
{
}
public AppEntityWithJsonProperty(Guid id, string name) : base(id)
{
Name = name;
}
}
public class JsonPropertyObject : Dictionary<string, object>
{
}

21
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/App/EntityFrameworkCore/AbpAuditingTestDbContext.cs

@ -30,6 +30,7 @@ public class AbpAuditingTestDbContext : AbpDbContext<AbpAuditingTestDbContext>
public DbSet<AppEntityWithNavigations> AppEntityWithNavigations { get; set; } public DbSet<AppEntityWithNavigations> AppEntityWithNavigations { get; set; }
public DbSet<AppEntityWithNavigationChildOneToMany> AppEntityWithNavigationChildOneToMany { get; set; } public DbSet<AppEntityWithNavigationChildOneToMany> AppEntityWithNavigationChildOneToMany { get; set; }
public DbSet<AppEntityWithNavigationsAndDisableAuditing> AppEntityWithNavigationsAndDisableAuditing { get; set; } public DbSet<AppEntityWithNavigationsAndDisableAuditing> AppEntityWithNavigationsAndDisableAuditing { get; set; }
public DbSet<AppEntityWithJsonProperty> EntitiesWithObjectProperty { get; set; }
public AbpAuditingTestDbContext(DbContextOptions<AbpAuditingTestDbContext> options) public AbpAuditingTestDbContext(DbContextOptions<AbpAuditingTestDbContext> options)
: base(options) : base(options)
@ -56,5 +57,25 @@ public class AbpAuditingTestDbContext : AbpDbContext<AbpAuditingTestDbContext>
b.HasMany(x => x.ManyToMany).WithMany(x => x.ManyToMany).UsingEntity<AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany>(); b.HasMany(x => x.ManyToMany).WithMany(x => x.ManyToMany).UsingEntity<AppEntityWithNavigationsAndAppEntityWithNavigationChildManyToMany>();
}); });
modelBuilder.Entity<AppEntityWithJsonProperty>(b =>
{
b.ConfigureByConvention();
b.OwnsOne(x => x.Data, b2 =>
{
b2.ToJson();
b2.Property<object>("Name")
.HasConversion<string>(
v => v.ToString(),
v => v
);
b2.Property<object>("Value")
.HasConversion<string>(
v => v.ToString(),
v => v
);
});
});
} }
} }

98
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/Auditing_Tests.cs

@ -720,6 +720,104 @@ public class Auditing_Tests : AbpAuditingTestBase
x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigationChildManyToMany.ManyToMany) && x.EntityChanges[1].PropertyChanges[0].PropertyName == nameof(AppEntityWithNavigationChildManyToMany.ManyToMany) &&
x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigations>).FullName)); x.EntityChanges[1].PropertyChanges[0].PropertyTypeFullName == typeof(List<AppEntityWithNavigations>).FullName));
#pragma warning restore 4014
}
[Fact]
public async Task Should_Write_AuditLog_For_Json_Property_Changes()
{
var entityId = Guid.NewGuid();
var repository = ServiceProvider.GetRequiredService<IBasicRepository<AppEntityWithJsonProperty, Guid>>();
using (var scope = _auditingManager.BeginScope())
{
using (var uow = _unitOfWorkManager.Begin())
{
var entity = new AppEntityWithJsonProperty(entityId, "Test Entity")
{
Data = new JsonPropertyObject()
{
{ "Name", "String Name" },
{ "Value", "String Value"}
},
Count = 10
};
await repository.InsertAsync(entity);
await uow.CompleteAsync();
await scope.SaveAsync();
}
}
#pragma warning disable 4014
AuditingStore.Received().SaveAsync(Arg.Is<AuditLogInfo>(x => x.EntityChanges.Count == 1 &&
x.EntityChanges[0].ChangeType == EntityChangeType.Created &&
x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithJsonProperty).FullName &&
x.EntityChanges[0].PropertyChanges.Count == 4 &&
x.EntityChanges[0].PropertyChanges[0].OriginalValue == null &&
x.EntityChanges[0].PropertyChanges[0].NewValue == "10" &&
x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithJsonProperty.Count) &&
x.EntityChanges[0].PropertyChanges[0].PropertyTypeFullName == typeof(int).FullName &&
x.EntityChanges[0].PropertyChanges[1].OriginalValue == null &&
x.EntityChanges[0].PropertyChanges[1].NewValue == "\"Test Entity\"" &&
x.EntityChanges[0].PropertyChanges[1].PropertyName == nameof(AppEntityWithJsonProperty.Name) &&
x.EntityChanges[0].PropertyChanges[1].PropertyTypeFullName == typeof(string).FullName &&
x.EntityChanges[0].PropertyChanges[2].OriginalValue == null &&
x.EntityChanges[0].PropertyChanges[2].NewValue == "\"String Name\"" &&
x.EntityChanges[0].PropertyChanges[2].PropertyName == "Data.Name" &&
x.EntityChanges[0].PropertyChanges[2].PropertyTypeFullName == typeof(string).FullName &&
x.EntityChanges[0].PropertyChanges[3].OriginalValue == null &&
x.EntityChanges[0].PropertyChanges[3].NewValue == "\"String Value\"" &&
x.EntityChanges[0].PropertyChanges[3].PropertyName == "Data.Value" &&
x.EntityChanges[0].PropertyChanges[3].PropertyTypeFullName == typeof(string).FullName));
AuditingStore.ClearReceivedCalls();
#pragma warning restore 4014
using (var scope = _auditingManager.BeginScope())
{
using (var uow = _unitOfWorkManager.Begin())
{
var entity = await repository.GetAsync(entityId);
entity.Name = "Updated Test Entity";
entity.Data["Name"] = "Updated String Name";
entity.Data["Value"] = "Updated String Value";
await repository.UpdateAsync(entity);
await uow.CompleteAsync();
await scope.SaveAsync();
}
}
#pragma warning disable 4014
AuditingStore.Received().SaveAsync(Arg.Is<AuditLogInfo>(x => x.EntityChanges.Count == 1 &&
x.EntityChanges[0].ChangeType == EntityChangeType.Updated &&
x.EntityChanges[0].EntityTypeFullName == typeof(AppEntityWithJsonProperty).FullName &&
x.EntityChanges[0].PropertyChanges.Count == 3 &&
x.EntityChanges[0].PropertyChanges[0].OriginalValue == "\"Test Entity\"" &&
x.EntityChanges[0].PropertyChanges[0].NewValue == "\"Updated Test Entity\"" &&
x.EntityChanges[0].PropertyChanges[0].PropertyName == nameof(AppEntityWithJsonProperty.Name) &&
x.EntityChanges[0].PropertyChanges[0].PropertyTypeFullName == typeof(string).FullName &&
x.EntityChanges[0].PropertyChanges[1].OriginalValue == "\"String Name\"" &&
x.EntityChanges[0].PropertyChanges[1].NewValue == "\"Updated String Name\"" &&
x.EntityChanges[0].PropertyChanges[1].PropertyName == "Data.Name" &&
x.EntityChanges[0].PropertyChanges[1].PropertyTypeFullName == typeof(string).FullName &&
x.EntityChanges[0].PropertyChanges[2].OriginalValue == "\"String Value\"" &&
x.EntityChanges[0].PropertyChanges[2].NewValue == "\"Updated String Value\"" &&
x.EntityChanges[0].PropertyChanges[2].PropertyName == "Data.Value" &&
x.EntityChanges[0].PropertyChanges[2].PropertyTypeFullName == typeof(string).FullName));
AuditingStore.ClearReceivedCalls();
#pragma warning restore 4014 #pragma warning restore 4014
} }
} }

179
framework/test/Volo.Abp.Core.Tests/Volo/Abp/Threading/KeyedLock_Tests.cs

@ -0,0 +1,179 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Linq;
using System.Threading.Tasks;
using Shouldly;
using Xunit;
namespace Volo.Abp.Threading;
public class KeyedLock_Tests
{
[Fact]
public async Task TryLock_Should_Acquire_Immediately_When_Free()
{
var key = "key-try-1";
var handle = await KeyedLock.TryLockAsync(key);
handle.ShouldNotBeNull();
handle!.Dispose();
var handle2 = await KeyedLock.TryLockAsync(key);
handle2.ShouldNotBeNull();
handle2!.Dispose();
}
[Fact]
public async Task TryLock_Should_Return_Null_When_Already_Locked()
{
var key = "key-try-2";
using (await KeyedLock.LockAsync(key))
{
var handle2 = await KeyedLock.TryLockAsync(key);
handle2.ShouldBeNull();
}
var handle3 = await KeyedLock.TryLockAsync(key);
handle3.ShouldNotBeNull();
handle3!.Dispose();
}
[Fact]
public async Task LockAsync_Should_Block_Until_Released()
{
var key = "key-block-1";
var sw = Stopwatch.StartNew();
Task inner;
using (await KeyedLock.LockAsync(key))
{
inner = Task.Run(async () =>
{
using (await KeyedLock.LockAsync(key))
{
// Acquired only after outer lock is released
}
});
// While holding the outer lock, inner waiter should not complete
await Task.Delay(200);
inner.IsCompleted.ShouldBeFalse();
}
// After releasing, inner should complete; elapsed >= hold time
await inner;
sw.ElapsedMilliseconds.ShouldBeGreaterThanOrEqualTo(180);
}
[Fact]
public async Task TryLock_With_Timeout_Should_Return_Null_When_Busy()
{
var key = "key-timeout-1";
using (await KeyedLock.LockAsync(key))
{
var handle = await KeyedLock.TryLockAsync(key, TimeSpan.FromMilliseconds(50));
handle.ShouldBeNull();
}
}
[Fact]
public async Task TryLock_With_Timeout_Should_Succeed_If_Released_In_Time()
{
var key = "key-timeout-2";
// Hold the lock manually
var outer = await KeyedLock.LockAsync(key);
var tryTask = KeyedLock.TryLockAsync(key, TimeSpan.FromMilliseconds(200));
await Task.Delay(50);
// Release within the timeout window
outer.Dispose();
var handle2 = await tryTask;
handle2.ShouldNotBeNull();
handle2!.Dispose();
}
[Fact]
public async Task LockAsync_With_Cancellation_Should_Rollback_RefCount()
{
var key = "key-cancel-1";
var cts = new CancellationTokenSource();
await cts.CancelAsync();
await Should.ThrowAsync<OperationCanceledException>(async () =>
{
await KeyedLock.LockAsync(key, cts.Token);
});
// After cancellation, we should still be able to acquire the key
var handle = await KeyedLock.TryLockAsync(key);
handle.ShouldNotBeNull();
handle!.Dispose();
}
[Fact]
public async Task TryLock_With_Cancellation_Should_Rollback()
{
var key = "key-cancel-2";
// Ensure it's initially free
var h0 = await KeyedLock.TryLockAsync(key);
h0?.Dispose();
var cts = new CancellationTokenSource();
await cts.CancelAsync();
await Should.ThrowAsync<OperationCanceledException>(async () =>
{
await KeyedLock.TryLockAsync(key, TimeSpan.FromMilliseconds(200), cts.Token);
});
// After cancellation, the key should be acquirable
var handle = await KeyedLock.TryLockAsync(key);
handle.ShouldNotBeNull();
handle!.Dispose();
}
[Fact]
public async Task Serializes_Access_For_Same_Key()
{
var key = "key-serial-1";
int counter = 0;
var tasks = Enumerable.Range(0, 10).Select(async _ =>
{
using (await KeyedLock.LockAsync(key))
{
var current = counter;
await Task.Delay(10);
counter = current + 1;
}
});
await Task.WhenAll(tasks);
counter.ShouldBe(10);
}
[Fact]
public async Task Multiple_Keys_Should_Not_Block_Each_Other()
{
var key1 = "key-multi-1";
var key2 = "key-multi-2";
using (await KeyedLock.LockAsync(key1))
{
var handle2 = await KeyedLock.TryLockAsync(key2);
handle2.ShouldNotBeNull();
handle2!.Dispose();
}
}
[Fact]
public async Task TryLock_Default_Overload_Delegates_To_Full_Overload()
{
var key = "key-default-1";
using (await KeyedLock.LockAsync(key))
{
var h1 = await KeyedLock.TryLockAsync(key);
h1.ShouldBeNull();
}
var h2 = await KeyedLock.TryLockAsync(key);
h2.ShouldNotBeNull();
h2!.Dispose();
}
}

165
framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs

@ -1,37 +1,38 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Frozen;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Shouldly; using Shouldly;
using Volo.Abp.AutoMapper; using Volo.Abp.AutoMapper;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.MultiLingualObjects.TestObjects; using Volo.Abp.MultiLingualObjects.TestObjects;
using Volo.Abp.Testing; using Volo.Abp.Testing;
using Xunit; using Xunit;
namespace Volo.Abp.MultiLingualObjects; namespace Volo.Abp.MultiLingualObjects;
public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingualObjectsTestModule> public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingualObjectsTestModule>
{ {
private readonly IMultiLingualObjectManager _multiLingualObjectManager; private readonly IMultiLingualObjectManager _multiLingualObjectManager;
private readonly MultiLingualBook _book; private readonly MultiLingualBook _book;
private readonly List<MultiLingualBook> _books; private readonly List<MultiLingualBook> _books;
private readonly IMapperAccessor _mapperAccessor; private readonly IMapperAccessor _mapperAccessor;
private readonly Dictionary<string, string> _testTranslations = new() private readonly FrozenDictionary<string, string> _testTranslations = new Dictionary<string, string>
{ {
["ar"] = "C# التعمق في", ["ar"] = "C# التعمق في",
["zh-Hans"] = "深入理解C#", ["zh-Hans"] = "深入理解C#",
["en"] = "C# in Depth" ["en"] = "C# in Depth"
}; }.ToFrozenDictionary();
public MultiLingualObjectManager_Tests() public MultiLingualObjectManager_Tests()
{ {
_multiLingualObjectManager = ServiceProvider.GetRequiredService<IMultiLingualObjectManager>(); _multiLingualObjectManager = ServiceProvider.GetRequiredService<IMultiLingualObjectManager>();
//Single Lookup //Single Lookup
_book = GetTestBook("en", "zh-Hans"); _book = GetTestBook("en", "zh-Hans");
//Bulk lookup //Bulk lookup
_books = new List<MultiLingualBook> _books = new List<MultiLingualBook>
{ {
//has no translations //has no translations
@ -45,14 +46,14 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingual
//arabic + english + chineese //arabic + english + chineese
GetTestBook("en", "ar", "zh-Hans") GetTestBook("en", "ar", "zh-Hans")
}; };
_mapperAccessor = ServiceProvider.GetRequiredService<IMapperAccessor>(); _mapperAccessor = ServiceProvider.GetRequiredService<IMapperAccessor>();
} }
MultiLingualBook GetTestBook(params string[] included) MultiLingualBook GetTestBook(params string[] included)
{ {
var id = Guid.NewGuid(); var id = Guid.NewGuid();
//Single book //Single book
var res = new MultiLingualBook(id, 100); var res = new MultiLingualBook(id, 100);
foreach (var language in included) foreach (var language in included)
{ {
res.Translations.Add(new MultiLingualBookTranslation res.Translations.Add(new MultiLingualBookTranslation
@ -65,45 +66,45 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingual
return res; return res;
} }
[Fact] [Fact]
public async Task GetTranslationAsync() public async Task GetTranslationAsync()
{ {
using (CultureHelper.Use("en-us")) using (CultureHelper.Use("en-us"))
{ {
var translation = await _multiLingualObjectManager.GetTranslationAsync<MultiLingualBook, MultiLingualBookTranslation>(_book); var translation = await _multiLingualObjectManager.GetTranslationAsync<MultiLingualBook, MultiLingualBookTranslation>(_book);
translation.ShouldNotBeNull(); translation.ShouldNotBeNull();
translation.Name.ShouldBe(_testTranslations["en"]); translation.Name.ShouldBe(_testTranslations["en"]);
} }
} }
[Fact] [Fact]
public async Task GetTranslationFromListAsync() public async Task GetTranslationFromListAsync()
{ {
using (CultureHelper.Use("en-us")) using (CultureHelper.Use("en-us"))
{ {
var translation = await _multiLingualObjectManager.GetTranslationAsync(_book.Translations); var translation = await _multiLingualObjectManager.GetTranslationAsync(_book.Translations);
translation.ShouldNotBeNull(); translation.ShouldNotBeNull();
translation.Name.ShouldBe(_testTranslations["en"]); translation.Name.ShouldBe(_testTranslations["en"]);
} }
} }
[Fact] [Fact]
public async Task Should_Get_Specified_Language() public async Task Should_Get_Specified_Language()
{ {
using (CultureHelper.Use("zh-Hans")) using (CultureHelper.Use("zh-Hans"))
{ {
var translation = await _multiLingualObjectManager.GetTranslationAsync<MultiLingualBook, MultiLingualBookTranslation>(_book, culture: "en"); var translation = await _multiLingualObjectManager.GetTranslationAsync<MultiLingualBook, MultiLingualBookTranslation>(_book, culture: "en");
translation.ShouldNotBeNull(); translation.ShouldNotBeNull();
translation.Name.ShouldBe(_testTranslations["en"]); translation.Name.ShouldBe(_testTranslations["en"]);
} }
} }
[Fact] [Fact]
public async Task GetBulkTranslationsAsync() public async Task GetBulkTranslationsAsync()
{ {
using (CultureHelper.Use("en-us")) using (CultureHelper.Use("en-us"))
{ {
var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync<MultiLingualBook, MultiLingualBookTranslation>(_books); var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync<MultiLingualBook, MultiLingualBookTranslation>(_books);
foreach (var (entity, translation) in translations) foreach (var (entity, translation) in translations)
{ {
@ -117,26 +118,26 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingual
translation.ShouldBeNull(); translation.ShouldBeNull();
} }
} }
} }
} }
[Fact] [Fact]
public async Task GetBulkTranslationsFromListAsync() public async Task GetBulkTranslationsFromListAsync()
{ {
using (CultureHelper.Use("en-us")) using (CultureHelper.Use("en-us"))
{ {
var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync(_books.Select(x => x.Translations)); var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync(_books.Select(x => x.Translations));
foreach (var translation in translations) foreach (var translation in translations)
{ {
translation?.Name.ShouldBe(_testTranslations["en"]); translation?.Name.ShouldBe(_testTranslations["en"]);
} }
} }
} }
[Fact] [Fact]
public async Task TestBulkMapping() public async Task TestBulkMapping()
{ {
using (CultureHelper.Use("en-us")) using (CultureHelper.Use("en-us"))
{ {
var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync<MultiLingualBook, MultiLingualBookTranslation>(_books); var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync<MultiLingualBook, MultiLingualBookTranslation>(_books);
var translationsDict = translations.ToDictionary(x => x.entity.Id, x => x.translation); var translationsDict = translations.ToDictionary(x => x.entity.Id, x => x.translation);
@ -152,5 +153,5 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingual
Assert.Equal(og.Translations.FirstOrDefault(x => x.Language == "en")?.Name, m.Name); Assert.Equal(og.Translations.FirstOrDefault(x => x.Language == "en")?.Name, m.Name);
} }
} }
} }
} }

5
modules/docs/app/VoloDocs.Web/Pages/Error.cshtml.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Frozen;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Diagnostics;
@ -48,7 +49,7 @@ namespace VoloDocs.Web.Pages
#region Error Messages #region Error Messages
/*For more ASCII arts http://patorjk.com/software/taag/#p=display&h=0&f=Big&t=400*/ /*For more ASCII arts http://patorjk.com/software/taag/#p=display&h=0&f=Big&t=400*/
private readonly Dictionary<int, string> _errorMessages = new Dictionary<int, string> private readonly FrozenDictionary<int, string> _errorMessages = new Dictionary<int, string>
{ {
{ {
400, @" 400, @"
@ -131,7 +132,7 @@ Ooops! Our server is experiencing a mild case of the hiccups."
Looks like we're having some server issues." Looks like we're having some server issues."
} }
}; }.ToFrozenDictionary();
#endregion #endregion
} }
} }

7
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs

@ -25,7 +25,6 @@ namespace Volo.Abp.FeatureManagement;
public class AbpFeatureManagementDomainModule : AbpModule public class AbpFeatureManagementDomainModule : AbpModule
{ {
private readonly CancellationTokenSource _cancellationTokenSource = new(); private readonly CancellationTokenSource _cancellationTokenSource = new();
private Task _initializeDynamicFeaturesTask;
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
@ -65,7 +64,6 @@ public class AbpFeatureManagementDomainModule : AbpModule
var rootServiceProvider = context.ServiceProvider.GetRequiredService<IRootServiceProvider>(); var rootServiceProvider = context.ServiceProvider.GetRequiredService<IRootServiceProvider>();
var initializer = rootServiceProvider.GetRequiredService<FeatureDynamicInitializer>(); var initializer = rootServiceProvider.GetRequiredService<FeatureDynamicInitializer>();
await initializer.InitializeAsync(true, _cancellationTokenSource.Token); await initializer.InitializeAsync(true, _cancellationTokenSource.Token);
_initializeDynamicFeaturesTask = initializer.GetInitializationTask();
} }
public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context) public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context)
@ -73,9 +71,4 @@ public class AbpFeatureManagementDomainModule : AbpModule
_cancellationTokenSource.Cancel(); _cancellationTokenSource.Cancel();
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task GetInitializeDynamicFeaturesTask()
{
return _initializeDynamicFeaturesTask ?? Task.CompletedTask;
}
} }

12
modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDynamicInitializer.cs

@ -16,8 +16,6 @@ namespace Volo.Abp.FeatureManagement;
public class FeatureDynamicInitializer : ITransientDependency public class FeatureDynamicInitializer : ITransientDependency
{ {
private Task _initializeDynamicFeaturesTask;
public ILogger<FeatureDynamicInitializer> Logger { get; set; } public ILogger<FeatureDynamicInitializer> Logger { get; set; }
protected IServiceProvider ServiceProvider { get; } protected IServiceProvider ServiceProvider { get; }
@ -56,7 +54,7 @@ public class FeatureDynamicInitializer : ITransientDependency
if (runInBackground) if (runInBackground)
{ {
_initializeDynamicFeaturesTask = Task.Run(async () => Task.Run(async () =>
{ {
if (cancellationToken == default && ApplicationLifetime?.ApplicationStopping != null) if (cancellationToken == default && ApplicationLifetime?.ApplicationStopping != null)
{ {
@ -67,13 +65,7 @@ public class FeatureDynamicInitializer : ITransientDependency
return Task.CompletedTask; return Task.CompletedTask;
} }
_initializeDynamicFeaturesTask = ExecuteInitializationAsync(options, cancellationToken); return ExecuteInitializationAsync(options, cancellationToken);
return _initializeDynamicFeaturesTask;
}
public virtual Task GetInitializationTask()
{
return _initializeDynamicFeaturesTask ?? Task.CompletedTask;
} }
protected virtual async Task ExecuteInitializationAsync(FeatureManagementOptions options, CancellationToken cancellationToken) protected virtual async Task ExecuteInitializationAsync(FeatureManagementOptions options, CancellationToken cancellationToken)

14
modules/feature-management/test/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests/Volo/Abp/FeatureManagement/EntityFrameworkCore/AbpFeatureManagementEntityFrameworkCoreTestModule.cs

@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Sqlite; using Volo.Abp.EntityFrameworkCore.Sqlite;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -53,15 +54,8 @@ public class AbpFeatureManagementEntityFrameworkCoreTestModule : AbpModule
public override void OnApplicationInitialization(ApplicationInitializationContext context) public override void OnApplicationInitialization(ApplicationInitializationContext context)
{ {
var task = context.ServiceProvider.GetRequiredService<AbpFeatureManagementDomainModule>().GetInitializeDynamicFeaturesTask(); var rootServiceProvider = context.ServiceProvider.GetRequiredService<IRootServiceProvider>();
if (!task.IsCompleted) var initializer = rootServiceProvider.GetRequiredService<FeatureDynamicInitializer>();
{ AsyncHelper.RunSync(() => initializer.InitializeAsync(false));
AsyncHelper.RunSync(() => Awaited(task));
}
}
private async static Task Awaited(Task task)
{
await task;
} }
} }

9
modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/PermissionManagementModal.razor.cs

@ -6,6 +6,7 @@ using Blazorise;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Components.Web.Configuration; using Volo.Abp.AspNetCore.Components.Web.Configuration;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.PermissionManagement.Localization; using Volo.Abp.PermissionManagement.Localization;
@ -153,7 +154,13 @@ public partial class PermissionManagementModal
await PermissionAppService.UpdateAsync(_providerName, _providerKey, updateDto); await PermissionAppService.UpdateAsync(_providerName, _providerKey, updateDto);
await CurrentApplicationConfigurationCacheResetService.ResetAsync(); Guid? userId = null;
if (_providerName == UserPermissionValueProvider.ProviderName && Guid.TryParse(_providerKey, out var parsedUserId))
{
userId = parsedUserId;
}
await CurrentApplicationConfigurationCacheResetService.ResetAsync(userId);
await InvokeAsync(_modal.Hide); await InvokeAsync(_modal.Hide);
await Notify.Success(L["SavedSuccessfully"]); await Notify.Success(L["SavedSuccessfully"]);

11
modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/AbpPermissionManagementDomainModule.cs

@ -2,6 +2,7 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -11,6 +12,7 @@ using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Caching; using Volo.Abp.Caching;
using Volo.Abp.Data; using Volo.Abp.Data;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.DistributedLocking;
using Volo.Abp.Domain; using Volo.Abp.Domain;
using Volo.Abp.Json; using Volo.Abp.Json;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
@ -26,10 +28,11 @@ namespace Volo.Abp.PermissionManagement;
public class AbpPermissionManagementDomainModule : AbpModule public class AbpPermissionManagementDomainModule : AbpModule
{ {
private readonly CancellationTokenSource _cancellationTokenSource = new(); private readonly CancellationTokenSource _cancellationTokenSource = new();
private Task _initializeDynamicPermissionsTask;
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
context.Services.Replace(ServiceDescriptor.Singleton<IAbpDistributedLock, NullAbpDistributedLock>());
if (context.Services.IsDataMigrationEnvironment()) if (context.Services.IsDataMigrationEnvironment())
{ {
Configure<PermissionManagementOptions>(options => Configure<PermissionManagementOptions>(options =>
@ -50,7 +53,6 @@ public class AbpPermissionManagementDomainModule : AbpModule
var rootServiceProvider = context.ServiceProvider.GetRequiredService<IRootServiceProvider>(); var rootServiceProvider = context.ServiceProvider.GetRequiredService<IRootServiceProvider>();
var initializer = rootServiceProvider.GetRequiredService<PermissionDynamicInitializer>(); var initializer = rootServiceProvider.GetRequiredService<PermissionDynamicInitializer>();
await initializer.InitializeAsync(true, _cancellationTokenSource.Token); await initializer.InitializeAsync(true, _cancellationTokenSource.Token);
_initializeDynamicPermissionsTask = initializer.GetInitializationTask();
} }
public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context) public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context)
@ -58,9 +60,4 @@ public class AbpPermissionManagementDomainModule : AbpModule
_cancellationTokenSource.Cancel(); _cancellationTokenSource.Cancel();
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task GetInitializeDynamicPermissionsTask()
{
return _initializeDynamicPermissionsTask ?? Task.CompletedTask;
}
} }

12
modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDynamicInitializer.cs

@ -16,8 +16,6 @@ namespace Volo.Abp.PermissionManagement;
public class PermissionDynamicInitializer : ITransientDependency public class PermissionDynamicInitializer : ITransientDependency
{ {
private Task _initializeDynamicPermissionsTask;
public ILogger<PermissionDynamicInitializer> Logger { get; set; } public ILogger<PermissionDynamicInitializer> Logger { get; set; }
protected IServiceProvider ServiceProvider { get; } protected IServiceProvider ServiceProvider { get; }
@ -56,7 +54,7 @@ public class PermissionDynamicInitializer : ITransientDependency
if (runInBackground) if (runInBackground)
{ {
_initializeDynamicPermissionsTask = Task.Run(async () => Task.Run(async () =>
{ {
if (cancellationToken == default && ApplicationLifetime?.ApplicationStopping != null) if (cancellationToken == default && ApplicationLifetime?.ApplicationStopping != null)
{ {
@ -67,13 +65,7 @@ public class PermissionDynamicInitializer : ITransientDependency
return Task.CompletedTask; return Task.CompletedTask;
} }
_initializeDynamicPermissionsTask = ExecuteInitializationAsync(options, cancellationToken); return ExecuteInitializationAsync(options, cancellationToken);
return _initializeDynamicPermissionsTask;
}
public virtual Task GetInitializationTask()
{
return _initializeDynamicPermissionsTask ?? Task.CompletedTask;
} }
protected virtual async Task ExecuteInitializationAsync(PermissionManagementOptions options, CancellationToken cancellationToken) protected virtual async Task ExecuteInitializationAsync(PermissionManagementOptions options, CancellationToken cancellationToken)

14
modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/PermissionManagementModal.cshtml.cs

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
@ -6,6 +7,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.EventBus.Local; using Volo.Abp.EventBus.Local;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.PermissionManagement.Web.Utils; using Volo.Abp.PermissionManagement.Web.Utils;
@ -105,9 +107,13 @@ public class PermissionManagementModal : AbpPageModel
} }
); );
await LocalEventBus.PublishAsync( Guid? userId = null;
new CurrentApplicationConfigurationCacheResetEventData() if (ProviderName == UserPermissionValueProvider.ProviderName && Guid.TryParse(ProviderKey, out var parsedUserId))
); {
userId = parsedUserId;
}
await LocalEventBus.PublishAsync(new CurrentApplicationConfigurationCacheResetEventData(userId));
return NoContent(); return NoContent();
} }
@ -130,7 +136,7 @@ public class PermissionManagementModal : AbpPageModel
public bool IsDisabled(string currentProviderName) public bool IsDisabled(string currentProviderName)
{ {
var grantedProviders = Permissions.SelectMany(x => x.GrantedProviders); var grantedProviders = Permissions.SelectMany(x => x.GrantedProviders);
return Permissions.All(x => x.IsGranted) && grantedProviders.All(p => p.ProviderName != currentProviderName); return Permissions.All(x => x.IsGranted) && grantedProviders.All(p => p.ProviderName != currentProviderName);
} }
} }

2
modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/ResourcePermissionManagementModal.cshtml

@ -34,7 +34,7 @@
else else
{ {
<abp-modal size="Large"> <abp-modal size="Large">
<abp-modal-header title="@(L["ResourcePermissions"].Value) - Model.ResourceDisplayName"></abp-modal-header> <abp-modal-header title="@(L["ResourcePermissions"].Value) - @Model.ResourceDisplayName"></abp-modal-header>
<abp-modal-body> <abp-modal-body>
<div class="alert alert-warning" role="alert"> <div class="alert alert-warning" role="alert">
@if (!Model.HasAnyResourcePermission) @if (!Model.HasAnyResourcePermission)

15
modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementEntityFrameworkCoreTestModule.cs

@ -9,6 +9,7 @@ using Volo.Abp.Modularity;
using Volo.Abp.Threading; using Volo.Abp.Threading;
using Volo.Abp.Uow; using Volo.Abp.Uow;
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.PermissionManagement.EntityFrameworkCore; namespace Volo.Abp.PermissionManagement.EntityFrameworkCore;
@ -56,18 +57,10 @@ public class AbpPermissionManagementEntityFrameworkCoreTestModule : AbpModule
return connection; return connection;
} }
public override void OnApplicationInitialization(ApplicationInitializationContext context) public override void OnApplicationInitialization(ApplicationInitializationContext context)
{ {
var task = context.ServiceProvider.GetRequiredService<AbpPermissionManagementDomainModule>().GetInitializeDynamicPermissionsTask(); var rootServiceProvider = context.ServiceProvider.GetRequiredService<IRootServiceProvider>();
if (!task.IsCompleted) var initializer = rootServiceProvider.GetRequiredService<PermissionDynamicInitializer>();
{ AsyncHelper.RunSync(() => initializer.InitializeAsync(false));
AsyncHelper.RunSync(() => Awaited(task));
}
}
private async static Task Awaited(Task task)
{
await task;
} }
} }

7
modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/AbpSettingManagementDomainModule.cs

@ -25,7 +25,6 @@ namespace Volo.Abp.SettingManagement;
public class AbpSettingManagementDomainModule : AbpModule public class AbpSettingManagementDomainModule : AbpModule
{ {
private readonly CancellationTokenSource _cancellationTokenSource = new(); private readonly CancellationTokenSource _cancellationTokenSource = new();
private Task _initializeDynamicSettingsTask;
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
@ -58,7 +57,6 @@ public class AbpSettingManagementDomainModule : AbpModule
var rootServiceProvider = context.ServiceProvider.GetRequiredService<IRootServiceProvider>(); var rootServiceProvider = context.ServiceProvider.GetRequiredService<IRootServiceProvider>();
var initializer = rootServiceProvider.GetRequiredService<SettingDynamicInitializer>(); var initializer = rootServiceProvider.GetRequiredService<SettingDynamicInitializer>();
await initializer.InitializeAsync(true, _cancellationTokenSource.Token); await initializer.InitializeAsync(true, _cancellationTokenSource.Token);
_initializeDynamicSettingsTask = initializer.GetInitializationTask();
} }
public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context) public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context)
@ -66,9 +64,4 @@ public class AbpSettingManagementDomainModule : AbpModule
_cancellationTokenSource.Cancel(); _cancellationTokenSource.Cancel();
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task GetInitializeDynamicSettingsTask()
{
return _initializeDynamicSettingsTask ?? Task.CompletedTask;
}
} }

12
modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingDynamicInitializer.cs

@ -16,8 +16,6 @@ namespace Volo.Abp.SettingManagement;
public class SettingDynamicInitializer : ITransientDependency public class SettingDynamicInitializer : ITransientDependency
{ {
private Task _initializeDynamicSettingsTask;
public ILogger<SettingDynamicInitializer> Logger { get; set; } public ILogger<SettingDynamicInitializer> Logger { get; set; }
protected IServiceProvider ServiceProvider { get; } protected IServiceProvider ServiceProvider { get; }
@ -56,7 +54,7 @@ public class SettingDynamicInitializer : ITransientDependency
if (runInBackground) if (runInBackground)
{ {
_initializeDynamicSettingsTask = Task.Run(async () => Task.Run(async () =>
{ {
if (cancellationToken == default && ApplicationLifetime?.ApplicationStopping != null) if (cancellationToken == default && ApplicationLifetime?.ApplicationStopping != null)
{ {
@ -68,13 +66,7 @@ public class SettingDynamicInitializer : ITransientDependency
return Task.CompletedTask; return Task.CompletedTask;
} }
_initializeDynamicSettingsTask = ExecuteInitializationAsync(options, cancellationToken); return ExecuteInitializationAsync(options, cancellationToken);
return _initializeDynamicSettingsTask;
}
public virtual Task GetInitializationTask()
{
return _initializeDynamicSettingsTask ?? Task.CompletedTask;
} }
protected virtual async Task ExecuteInitializationAsync(SettingManagementOptions options, CancellationToken cancellationToken) protected virtual async Task ExecuteInitializationAsync(SettingManagementOptions options, CancellationToken cancellationToken)

13
modules/setting-management/test/Volo.Abp.SettingManagement.Tests/Volo/Abp/SettingManagement/SettingManager_Basic_Tests.cs

@ -18,11 +18,10 @@ public class SettingManager_Basic_Tests : SettingsTestBase
} }
[Fact] [Fact]
public async Task Should_Throw_Exception_When_Try_To_Get_An_Undefined_Setting() public async Task Should_Return_Null_When_Try_To_Get_An_Undefined_Setting()
{ {
await Assert.ThrowsAsync<AbpException>( var value = await _settingProvider.GetOrNullAsync("UndefinedSetting");
async () => await _settingProvider.GetOrNullAsync("UndefinedSetting") value.ShouldBeNull();
);
} }
[Fact] [Fact]
@ -64,7 +63,7 @@ public class SettingManager_Basic_Tests : SettingsTestBase
(await _settingManager.GetOrNullGlobalAsync("MySetting1")).ShouldBe("43"); (await _settingManager.GetOrNullGlobalAsync("MySetting1")).ShouldBe("43");
(await _settingProvider.GetOrNullAsync("MySetting1")).ShouldBe("43"); (await _settingProvider.GetOrNullAsync("MySetting1")).ShouldBe("43");
} }
[Fact] [Fact]
public async Task Set_Should_Throw_Exception_If_Provider_Not_Found() public async Task Set_Should_Throw_Exception_If_Provider_Not_Found()
{ {
@ -72,7 +71,7 @@ public class SettingManager_Basic_Tests : SettingsTestBase
{ {
await _settingManager.SetAsync("MySetting1", "43", "UndefinedProvider", "Test"); await _settingManager.SetAsync("MySetting1", "43", "UndefinedProvider", "Test");
}); });
exception.Message.ShouldBe("Unknown setting value provider: UndefinedProvider"); exception.Message.ShouldBe("Unknown setting value provider: UndefinedProvider");
} }
} }

32
text-template-management/src/Volo.Abp.TextTemplateManagement.Domain/Volo/Abp/TextTemplateManagement/StaticTemplateDefinitionChangedEventHandler.cs

@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus;
using Volo.Abp.StaticDefinitions;
using Volo.Abp.TextTemplating;
using Volo.Abp.Threading;
namespace Volo.Abp.TextTemplateManagement;
public class StaticTemplateDefinitionChangedEventHandler : ILocalEventHandler<StaticTemplateDefinitionChangedEvent>, ITransientDependency
{
protected IStaticDefinitionCache<TemplateDefinition, Dictionary<string, TemplateDefinition>> DefinitionCache { get; }
protected TextTemplateDynamicInitializer TextTemplateDynamicInitializer { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
public StaticTemplateDefinitionChangedEventHandler(
IStaticDefinitionCache<TemplateDefinition, Dictionary<string, TemplateDefinition>> definitionCache,
TextTemplateDynamicInitializer textTemplateDynamicInitializer,
ICancellationTokenProvider cancellationTokenProvider)
{
DefinitionCache = definitionCache;
TextTemplateDynamicInitializer = textTemplateDynamicInitializer;
CancellationTokenProvider = cancellationTokenProvider;
}
public virtual async Task HandleEventAsync(StaticTemplateDefinitionChangedEvent eventData)
{
await DefinitionCache.ClearAsync();
await TextTemplateDynamicInitializer.InitializeAsync(false, CancellationTokenProvider.Token);
}
}
Loading…
Cancel
Save