From db98067117eb992c97a46cd2a8d6193b4a6d3433 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 28 Dec 2025 13:44:22 +0800 Subject: [PATCH 1/5] Refactor static definition stores and add dynamic initializers --- .../StaticPermissionDefinitionChangedEvent.cs | 9 + .../StaticPermissionDefinitionStore.cs | 90 +++++----- .../InternalServiceCollectionExtensions.cs | 3 +- .../IStaticDefinitionCache.cs | 11 ++ .../StaticDefinitionCache.cs | 30 ++++ .../StaticFeatureDefinitionChangedEvent.cs | 9 + .../Features/StaticFeatureDefinitionStore.cs | 112 ++++++------ .../StaticSettingDefinitionChangedEvent.cs | 9 + .../Settings/StaticSettingDefinitionStore.cs | 34 ++-- .../StaticTemplateDefinitionChangedEvent.cs | 9 + .../StaticTemplateDefinitionStore.cs | 40 +++-- .../MongoDB/CmsKitMongoDbTestModule.cs | 4 +- .../Volo.CmsKit.MongoDB.Tests.csproj | 1 + .../AbpFeatureManagementDomainModule.cs | 128 +------------- .../FeatureDynamicInitializer.cs | 157 +++++++++++++++++ ...ticFeatureDefinitionChangedEventHandler.cs | 36 ++++ .../AbpPermissionManagementDomainModule.cs | 123 +------------- .../PermissionDynamicInitializer.cs | 159 ++++++++++++++++++ ...PermissionDefinitionChangedEventHandler.cs | 36 ++++ .../AbpSettingManagementDomainModule.cs | 115 +------------ .../SettingDynamicInitializer.cs | 158 +++++++++++++++++ ...ticSettingDefinitionChangedEventHandler.cs | 32 ++++ 22 files changed, 828 insertions(+), 477 deletions(-) create mode 100644 framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionChangedEvent.cs create mode 100644 framework/src/Volo.Abp.Core/Volo/Abp/StaticDefinitions/IStaticDefinitionCache.cs create mode 100644 framework/src/Volo.Abp.Core/Volo/Abp/StaticDefinitions/StaticDefinitionCache.cs create mode 100644 framework/src/Volo.Abp.Features/Volo/Abp/Features/StaticFeatureDefinitionChangedEvent.cs create mode 100644 framework/src/Volo.Abp.Settings/Volo/Abp/Settings/StaticSettingDefinitionChangedEvent.cs create mode 100644 framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/StaticTemplateDefinitionChangedEvent.cs create mode 100644 modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDynamicInitializer.cs create mode 100644 modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/StaticFeatureDefinitionChangedEventHandler.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDynamicInitializer.cs create mode 100644 modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionDefinitionChangedEventHandler.cs create mode 100644 modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingDynamicInitializer.cs create mode 100644 modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/StaticSettingDefinitionChangedEventHandler.cs diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionChangedEvent.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionChangedEvent.cs new file mode 100644 index 0000000000..6bc7b5f165 --- /dev/null +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionChangedEvent.cs @@ -0,0 +1,9 @@ +using System; + +namespace Volo.Abp.Authorization.Permissions; + +[Serializable] +public class StaticPermissionDefinitionChangedEvent +{ + +} diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs index 4e6ff0d11c..8329fde12e 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs @@ -6,44 +6,58 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; +using Volo.Abp.StaticDefinitions; namespace Volo.Abp.Authorization.Permissions; public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore, ISingletonDependency { - protected IDictionary PermissionGroupDefinitions => _lazyPermissionGroupDefinitions.Value; - private readonly Lazy> _lazyPermissionGroupDefinitions; - - protected IDictionary PermissionDefinitions => _lazyPermissionDefinitions.Value; - private readonly Lazy> _lazyPermissionDefinitions; - + protected IServiceProvider ServiceProvider { get; } protected AbpPermissionOptions Options { get; } - - private readonly IServiceProvider _serviceProvider; + protected IStaticDefinitionCache> GroupCache { get; } + protected IStaticDefinitionCache> DefinitionCache { get; } public StaticPermissionDefinitionStore( IServiceProvider serviceProvider, - IOptions options) + IOptions options, + IStaticDefinitionCache> groupCache, + IStaticDefinitionCache> definitionCache) { - _serviceProvider = serviceProvider; + ServiceProvider = serviceProvider; Options = options.Value; + GroupCache = groupCache; + DefinitionCache = definitionCache; + } - _lazyPermissionDefinitions = new Lazy>( - CreatePermissionDefinitions, - isThreadSafe: true - ); + public async Task GetOrNullAsync(string name) + { + var defs = await GetPermissionDefinitionsAsync(); + return defs.GetOrDefault(name); + } - _lazyPermissionGroupDefinitions = new Lazy>( - CreatePermissionGroupDefinitions, - isThreadSafe: true - ); + public virtual async Task> GetPermissionsAsync() + { + var defs = await GetPermissionDefinitionsAsync(); + return defs.Values.ToImmutableList(); + } + + public async Task> GetGroupsAsync() + { + var groups = await GetPermissionGroupDefinitionsAsync(); + return groups.Values.ToImmutableList(); + } + + protected virtual async Task> GetPermissionDefinitionsAsync() + { + return await DefinitionCache.GetOrCreateAsync(CreatePermissionDefinitionsAsync); } - - protected virtual Dictionary CreatePermissionDefinitions() + + protected virtual async Task> CreatePermissionDefinitionsAsync() { var permissions = new Dictionary(); - foreach (var groupDefinition in PermissionGroupDefinitions.Values) + var groups = await GetPermissionGroupDefinitionsAsync(); + foreach (var groupDefinition in groups.Values) { foreach (var permission in groupDefinition.Permissions) { @@ -71,9 +85,14 @@ public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore, } } - protected virtual Dictionary CreatePermissionGroupDefinitions() + protected virtual async Task> GetPermissionGroupDefinitionsAsync() + { + return await GroupCache.GetOrCreateAsync(CreatePermissionGroupDefinitionsAsync); + } + + protected virtual Task> CreatePermissionGroupDefinitionsAsync() { - using (var scope = _serviceProvider.CreateScope()) + using (var scope = ServiceProvider.CreateScope()) { var context = new PermissionDefinitionContext(scope.ServiceProvider); @@ -99,29 +118,10 @@ public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore, context.CurrentProvider = provider; provider.PostDefine(context); } - + context.CurrentProvider = null; - return context.Groups; + return Task.FromResult(context.Groups); } } - - public Task GetOrNullAsync(string name) - { - return Task.FromResult(PermissionDefinitions.GetOrDefault(name)); - } - - public virtual Task> GetPermissionsAsync() - { - return Task.FromResult>( - PermissionDefinitions.Values.ToImmutableList() - ); - } - - public Task> GetGroupsAsync() - { - return Task.FromResult>( - PermissionGroupDefinitions.Values.ToImmutableList() - ); - } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/InternalServiceCollectionExtensions.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/InternalServiceCollectionExtensions.cs index be28f569ea..5ed5561e0e 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/InternalServiceCollectionExtensions.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/InternalServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using Volo.Abp.Logging; using Volo.Abp.Modularity; using Volo.Abp.Reflection; using Volo.Abp.SimpleStateChecking; +using Volo.Abp.StaticDefinitions; namespace Volo.Abp.Internal; @@ -42,7 +43,7 @@ internal static class InternalServiceCollectionExtensions services.AddAssemblyOf(); services.AddTransient(typeof(ISimpleStateCheckerManager<>), typeof(SimpleStateCheckerManager<>)); - + services.AddSingleton(typeof(IStaticDefinitionCache<,>), typeof(StaticDefinitionCache<,>)); services.Configure(options => { options.Contributors.Add(); diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/StaticDefinitions/IStaticDefinitionCache.cs b/framework/src/Volo.Abp.Core/Volo/Abp/StaticDefinitions/IStaticDefinitionCache.cs new file mode 100644 index 0000000000..0f4bb5b5a8 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/StaticDefinitions/IStaticDefinitionCache.cs @@ -0,0 +1,11 @@ +using System; +using System.Threading.Tasks; + +namespace Volo.Abp.StaticDefinitions; + +public interface IStaticDefinitionCache +{ + Task GetOrCreateAsync(Func> factory); + + Task ClearAsync(); +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/StaticDefinitions/StaticDefinitionCache.cs b/framework/src/Volo.Abp.Core/Volo/Abp/StaticDefinitions/StaticDefinitionCache.cs new file mode 100644 index 0000000000..ce9ac7827d --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/StaticDefinitions/StaticDefinitionCache.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Volo.Abp.StaticDefinitions; + +public class StaticDefinitionCache : IStaticDefinitionCache +{ + private Lazy>? _lazy; + + public virtual async Task GetOrCreateAsync(Func> factory) + { + var lazy = _lazy; + if (lazy != null) + { + return await lazy.Value; + } + + var newLazy = new Lazy>(factory, LazyThreadSafetyMode.ExecutionAndPublication); + lazy = Interlocked.CompareExchange(ref _lazy, newLazy, null) ?? newLazy; + + return await lazy.Value; + } + + public virtual Task ClearAsync() + { + _lazy = null; + return Task.CompletedTask; + } +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/StaticFeatureDefinitionChangedEvent.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/StaticFeatureDefinitionChangedEvent.cs new file mode 100644 index 0000000000..e654516d98 --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/StaticFeatureDefinitionChangedEvent.cs @@ -0,0 +1,9 @@ +using System; + +namespace Volo.Abp.Features; + +[Serializable] +public class StaticFeatureDefinitionChangedEvent +{ + +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/StaticFeatureDefinitionStore.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/StaticFeatureDefinitionStore.cs index d0647168d3..12c21b3987 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/StaticFeatureDefinitionStore.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/StaticFeatureDefinitionStore.cs @@ -5,37 +5,27 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; +using Volo.Abp.StaticDefinitions; namespace Volo.Abp.Features; public class StaticFeatureDefinitionStore: IStaticFeatureDefinitionStore, ISingletonDependency { - protected IDictionary FeatureGroupDefinitions => _lazyFeatureGroupDefinitions.Value; - private readonly Lazy> _lazyFeatureGroupDefinitions; - - protected IDictionary FeatureDefinitions => _lazyFeatureDefinitions.Value; - private readonly Lazy> _lazyFeatureDefinitions; - + protected IServiceProvider ServiceProvider { get; } protected AbpFeatureOptions Options { get; } - - private readonly IServiceProvider _serviceProvider; + protected IStaticDefinitionCache> GroupCache { get; } + protected IStaticDefinitionCache> DefinitionCache { get; } public StaticFeatureDefinitionStore( + IServiceProvider serviceProvider, IOptions options, - IServiceProvider serviceProvider) + IStaticDefinitionCache> groupCache, + IStaticDefinitionCache> definitionCache) { - _serviceProvider = serviceProvider; + ServiceProvider = serviceProvider; Options = options.Value; - - _lazyFeatureDefinitions = new Lazy>( - CreateFeatureDefinitions, - isThreadSafe: true - ); - - _lazyFeatureGroupDefinitions = new Lazy>( - CreateFeatureGroupDefinitions, - isThreadSafe: true - ); + GroupCache = groupCache; + DefinitionCache = definitionCache; } public virtual async Task GetAsync(string name) @@ -52,43 +42,39 @@ public class StaticFeatureDefinitionStore: IStaticFeatureDefinitionStore, ISingl return feature; } - protected virtual Dictionary CreateFeatureDefinitions() + public virtual async Task GetOrNullAsync(string name) { - var features = new Dictionary(); - - foreach (var groupDefinition in FeatureGroupDefinitions.Values) - { - foreach (var feature in groupDefinition.Features) - { - AddFeatureToDictionaryRecursively(features, feature); - } - } + var defs = await GetFeatureDefinitionsAsync(); + return defs.GetOrDefault(name); + } - return features; + public virtual async Task> GetFeaturesAsync() + { + var defs = await GetFeatureDefinitionsAsync(); + return defs.Values.ToList(); } - protected virtual void AddFeatureToDictionaryRecursively( - Dictionary features, - FeatureDefinition feature) + public virtual async Task> GetGroupsAsync() { - if (features.ContainsKey(feature.Name)) - { - throw new AbpException("Duplicate feature name: " + feature.Name); - } + var groups = await GetFeatureGroupDefinitionsAsync(); + return groups.Values.ToList(); + } - features[feature.Name] = feature; + protected virtual async Task> GetFeatureGroupDefinitionsAsync() + { + return await GroupCache.GetOrCreateAsync(CreateFeatureGroupDefinitionsAsync); + } - foreach (var child in feature.Children) - { - AddFeatureToDictionaryRecursively(features, child); - } + protected virtual async Task> GetFeatureDefinitionsAsync() + { + return await DefinitionCache.GetOrCreateAsync(CreateFeatureDefinitionsAsync); } - protected virtual Dictionary CreateFeatureGroupDefinitions() + protected virtual Task> CreateFeatureGroupDefinitionsAsync() { var context = new FeatureDefinitionContext(); - using (var scope = _serviceProvider.CreateScope()) + using (var scope = ServiceProvider.CreateScope()) { var providers = Options .DefinitionProviders @@ -101,21 +87,39 @@ public class StaticFeatureDefinitionStore: IStaticFeatureDefinitionStore, ISingl } } - return context.Groups; + return Task.FromResult(context.Groups); } - public virtual Task GetOrNullAsync(string name) + protected virtual async Task> CreateFeatureDefinitionsAsync() { - return Task.FromResult(FeatureDefinitions.GetOrDefault(name)); - } + var features = new Dictionary(); - public virtual Task> GetFeaturesAsync() - { - return Task.FromResult>(FeatureDefinitions.Values.ToList()); + var groups = await GetFeatureGroupDefinitionsAsync(); + foreach (var groupDefinition in groups.Values) + { + foreach (var feature in groupDefinition.Features) + { + AddFeatureToDictionaryRecursively(features, feature); + } + } + + return features; } - public virtual Task> GetGroupsAsync() + protected virtual void AddFeatureToDictionaryRecursively( + Dictionary features, + FeatureDefinition feature) { - return Task.FromResult>(FeatureGroupDefinitions.Values.ToList()); + if (features.ContainsKey(feature.Name)) + { + throw new AbpException("Duplicate feature name: " + feature.Name); + } + + features[feature.Name] = feature; + + foreach (var child in feature.Children) + { + AddFeatureToDictionaryRecursively(features, child); + } } } diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/StaticSettingDefinitionChangedEvent.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/StaticSettingDefinitionChangedEvent.cs new file mode 100644 index 0000000000..591ff88890 --- /dev/null +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/StaticSettingDefinitionChangedEvent.cs @@ -0,0 +1,9 @@ +using System; + +namespace Volo.Abp.Settings; + +[Serializable] +public class StaticSettingDefinitionChangedEvent +{ + +} diff --git a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/StaticSettingDefinitionStore.cs b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/StaticSettingDefinitionStore.cs index 653ee935f7..a35ed31b75 100644 --- a/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/StaticSettingDefinitionStore.cs +++ b/framework/src/Volo.Abp.Settings/Volo/Abp/Settings/StaticSettingDefinitionStore.cs @@ -6,23 +6,24 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; +using Volo.Abp.StaticDefinitions; namespace Volo.Abp.Settings; public class StaticSettingDefinitionStore : IStaticSettingDefinitionStore, ISingletonDependency { - protected Lazy> SettingDefinitions { get; } - - protected AbpSettingOptions Options { get; } - protected IServiceProvider ServiceProvider { get; } + protected AbpSettingOptions Options { get; } + protected IStaticDefinitionCache> DefinitionCache { get; } - public StaticSettingDefinitionStore(IOptions options, IServiceProvider serviceProvider) + public StaticSettingDefinitionStore( + IServiceProvider serviceProvider, + IOptions options, + IStaticDefinitionCache> definitionCache) { ServiceProvider = serviceProvider; Options = options.Value; - - SettingDefinitions = new Lazy>(CreateSettingDefinitions, true); + DefinitionCache = definitionCache; } public virtual async Task GetAsync(string name) @@ -39,17 +40,24 @@ public class StaticSettingDefinitionStore : IStaticSettingDefinitionStore, ISing return setting; } - public virtual Task> GetAllAsync() + public virtual async Task> GetAllAsync() + { + var defs = await GetSettingDefinitionsAsync(); + return defs.Values.ToImmutableList(); + } + + public virtual async Task GetOrNullAsync(string name) { - return Task.FromResult>(SettingDefinitions.Value.Values.ToImmutableList()); + var defs = await GetSettingDefinitionsAsync(); + return defs.GetOrDefault(name); } - public virtual Task GetOrNullAsync(string name) + protected virtual async Task> GetSettingDefinitionsAsync() { - return Task.FromResult(SettingDefinitions.Value.GetOrDefault(name)); + return await DefinitionCache.GetOrCreateAsync(CreateSettingDefinitionsAsync); } - protected virtual IDictionary CreateSettingDefinitions() + protected virtual Task> CreateSettingDefinitionsAsync() { var settings = new Dictionary(); @@ -66,6 +74,6 @@ public class StaticSettingDefinitionStore : IStaticSettingDefinitionStore, ISing } } - return settings; + return Task.FromResult(settings); } } diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/StaticTemplateDefinitionChangedEvent.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/StaticTemplateDefinitionChangedEvent.cs new file mode 100644 index 0000000000..dd862b9c69 --- /dev/null +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/StaticTemplateDefinitionChangedEvent.cs @@ -0,0 +1,9 @@ +using System; + +namespace Volo.Abp.TextTemplating; + +[Serializable] +public class StaticTemplateDefinitionChangedEvent +{ + +} diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/StaticTemplateDefinitionStore.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/StaticTemplateDefinitionStore.cs index 4231f39b4d..083a111f87 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/StaticTemplateDefinitionStore.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/StaticTemplateDefinitionStore.cs @@ -6,50 +6,58 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; +using Volo.Abp.StaticDefinitions; namespace Volo.Abp.TextTemplating; public class StaticTemplateDefinitionStore : IStaticTemplateDefinitionStore, ISingletonDependency { - protected Lazy> TemplateDefinitions { get; } - - protected AbpTextTemplatingOptions Options { get; } - protected IServiceProvider ServiceProvider { get; } + protected AbpTextTemplatingOptions Options { get; } + protected IStaticDefinitionCache> DefinitionCache { get; } - public StaticTemplateDefinitionStore(IOptions options, IServiceProvider serviceProvider) + public StaticTemplateDefinitionStore( + IServiceProvider serviceProvider, + IOptions options, + IStaticDefinitionCache> definitionCache) { ServiceProvider = serviceProvider; Options = options.Value; - - TemplateDefinitions = new Lazy>(CreateTextTemplateDefinitions, true); + DefinitionCache = definitionCache; } - public virtual Task GetAsync(string name) + public virtual async Task GetAsync(string name) { Check.NotNull(name, nameof(name)); - var template = GetOrNullAsync(name); + var template = await GetOrNullAsync(name); if (template == null) { throw new AbpException("Undefined template: " + name); } - return template!; + return template; + } + + public virtual async Task> GetAllAsync() + { + var defs = await GetTemplateDefinitionsAsync(); + return defs.Values.ToImmutableList(); } - public virtual Task> GetAllAsync() + public virtual async Task GetOrNullAsync(string name) { - return Task.FromResult>(TemplateDefinitions.Value.Values.ToImmutableList()); + var defs = await GetTemplateDefinitionsAsync(); + return defs.GetOrDefault(name); } - public virtual Task GetOrNullAsync(string name) + protected virtual async Task> GetTemplateDefinitionsAsync() { - return Task.FromResult(TemplateDefinitions.Value.GetOrDefault(name)); + return await DefinitionCache.GetOrCreateAsync(CreateTextTemplateDefinitionsAsync); } - protected virtual IDictionary CreateTextTemplateDefinitions() + protected virtual Task> CreateTextTemplateDefinitionsAsync() { var templates = new Dictionary(); @@ -78,6 +86,6 @@ public class StaticTemplateDefinitionStore : IStaticTemplateDefinitionStore, ISi } } - return templates; + return Task.FromResult(templates); } } diff --git a/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/CmsKitMongoDbTestModule.cs b/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/CmsKitMongoDbTestModule.cs index 80ae993732..480d055cec 100644 --- a/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/CmsKitMongoDbTestModule.cs +++ b/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/MongoDB/CmsKitMongoDbTestModule.cs @@ -1,13 +1,15 @@ using System; using Volo.Abp.Data; using Volo.Abp.Modularity; +using Volo.Abp.SettingManagement.MongoDB; using Volo.Abp.Uow; namespace Volo.CmsKit.MongoDB; [DependsOn( typeof(CmsKitTestBaseModule), - typeof(CmsKitMongoDbModule) + typeof(CmsKitMongoDbModule), + typeof(AbpSettingManagementMongoDbModule) )] public class CmsKitMongoDbTestModule : AbpModule { diff --git a/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/Volo.CmsKit.MongoDB.Tests.csproj b/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/Volo.CmsKit.MongoDB.Tests.csproj index dfa0e199b5..15222835c2 100644 --- a/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/Volo.CmsKit.MongoDB.Tests.csproj +++ b/modules/cms-kit/test/Volo.CmsKit.MongoDB.Tests/Volo.CmsKit.MongoDB.Tests.csproj @@ -11,6 +11,7 @@ + diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs index 42ad413f3f..001bbfe0d8 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs @@ -24,6 +24,9 @@ namespace Volo.Abp.FeatureManagement; )] public class AbpFeatureManagementDomainModule : AbpModule { + private readonly CancellationTokenSource _cancellationTokenSource = new(); + private Task _initializeDynamicFeaturesTask; + public override void ConfigureServices(ServiceConfigurationContext context) { Configure(options => @@ -51,18 +54,17 @@ public class AbpFeatureManagementDomainModule : AbpModule } } - private readonly CancellationTokenSource _cancellationTokenSource = new(); - private Task _initializeDynamicFeaturesTask; - public override void OnApplicationInitialization(ApplicationInitializationContext context) { AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context)); } - public override Task OnApplicationInitializationAsync(ApplicationInitializationContext context) + public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) { - InitializeDynamicFeatures(context); - return Task.CompletedTask; + var rootServiceProvider = context.ServiceProvider.GetRequiredService(); + var initializer = rootServiceProvider.GetRequiredService(); + await initializer.InitializeAsync(true, _cancellationTokenSource.Token); + _initializeDynamicFeaturesTask = initializer.GetInitializationTask(); } public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context) @@ -75,118 +77,4 @@ public class AbpFeatureManagementDomainModule : AbpModule { return _initializeDynamicFeaturesTask ?? Task.CompletedTask; } - - private void InitializeDynamicFeatures(ApplicationInitializationContext context) - { - var options = context - .ServiceProvider - .GetRequiredService>() - .Value; - - if (!options.SaveStaticFeaturesToDatabase && !options.IsDynamicFeatureStoreEnabled) - { - return; - } - - var rootServiceProvider = context.ServiceProvider.GetRequiredService(); - - _initializeDynamicFeaturesTask = Task.Run(async () => - { - using var scope = rootServiceProvider.CreateScope(); - var applicationLifetime = scope.ServiceProvider.GetService(); - var cancellationTokenProvider = scope.ServiceProvider.GetRequiredService(); - var cancellationToken = applicationLifetime?.ApplicationStopping ?? _cancellationTokenSource.Token; - - try - { - using (cancellationTokenProvider.Use(cancellationToken)) - { - if (cancellationTokenProvider.Token.IsCancellationRequested) - { - return; - } - - await SaveStaticFeaturesToDatabaseAsync(options, scope, cancellationTokenProvider); - - if (cancellationTokenProvider.Token.IsCancellationRequested) - { - return; - } - - await PreCacheDynamicFeaturesAsync(options, scope); - } - } - // ReSharper disable once EmptyGeneralCatchClause (No need to log since it is logged above) - catch { } - }); - } - - private static async Task SaveStaticFeaturesToDatabaseAsync( - FeatureManagementOptions options, - IServiceScope scope, - ICancellationTokenProvider cancellationTokenProvider) - { - if (!options.SaveStaticFeaturesToDatabase) - { - return; - } - - await Policy - .Handle() - .WaitAndRetryAsync( - 8, - retryAttempt => TimeSpan.FromSeconds( - RandomHelper.GetRandom( - (int)Math.Pow(2, retryAttempt) * 8, - (int)Math.Pow(2, retryAttempt) * 12) - ) - ) - .ExecuteAsync(async _ => - { - try - { - // ReSharper disable once AccessToDisposedClosure - await scope - .ServiceProvider - .GetRequiredService() - .SaveAsync(); - } - catch (Exception ex) - { - // ReSharper disable once AccessToDisposedClosure - scope.ServiceProvider - .GetService>()? - .LogException(ex); - - throw; // Polly will catch it - } - }, cancellationTokenProvider.Token); - } - - private static async Task PreCacheDynamicFeaturesAsync(FeatureManagementOptions options, IServiceScope scope) - { - if (!options.IsDynamicFeatureStoreEnabled) - { - return; - } - - try - { - // Pre-cache features, so first request doesn't wait - await scope - .ServiceProvider - .GetRequiredService() - .GetGroupsAsync(); - } - catch (Exception ex) - { - // ReSharper disable once AccessToDisposedClosure - scope - .ServiceProvider - .GetService>()? - .LogException(ex); - - throw; // It will be cached in InitializeDynamicFeatures - } - } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDynamicInitializer.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDynamicInitializer.cs new file mode 100644 index 0000000000..8dcdda388e --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDynamicInitializer.cs @@ -0,0 +1,157 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Polly; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; +using Volo.Abp.Threading; + +namespace Volo.Abp.FeatureManagement; + +public class FeatureDynamicInitializer : ISingletonDependency +{ + private Task _initializeDynamicFeaturesTask; + + public ILogger Logger { get; set; } + + protected IServiceProvider ServiceProvider { get; } + protected IOptions Options { get; } + [CanBeNull] + protected IHostApplicationLifetime ApplicationLifetime { get; } + protected ICancellationTokenProvider CancellationTokenProvider { get; } + protected IDynamicFeatureDefinitionStore DynamicFeatureDefinitionStore { get; } + protected IStaticFeatureSaver StaticFeatureSaver { get; } + + public FeatureDynamicInitializer( + IServiceProvider serviceProvider, + IOptions options, + ICancellationTokenProvider cancellationTokenProvider, + IDynamicFeatureDefinitionStore dynamicFeatureDefinitionStore, + IStaticFeatureSaver staticFeatureSaver) + { + Logger = NullLogger.Instance; + + ServiceProvider = serviceProvider; + Options = options; + ApplicationLifetime = ServiceProvider.GetService(); + CancellationTokenProvider = cancellationTokenProvider; + DynamicFeatureDefinitionStore = dynamicFeatureDefinitionStore; + StaticFeatureSaver = staticFeatureSaver; + } + + public virtual Task InitializeAsync(bool runInBackground, CancellationToken cancellationToken = default) + { + var options = Options.Value; + + if (!options.SaveStaticFeaturesToDatabase && !options.IsDynamicFeatureStoreEnabled) + { + return Task.CompletedTask; + } + + if (runInBackground) + { + _initializeDynamicFeaturesTask = Task.Run(async () => + { + if (cancellationToken == default && ApplicationLifetime?.ApplicationStopping != null) + { + cancellationToken = ApplicationLifetime.ApplicationStopping; + } + await ExecuteInitializationAsync(options, cancellationToken); + }, cancellationToken); + return Task.CompletedTask; + } + + _initializeDynamicFeaturesTask = ExecuteInitializationAsync(options, cancellationToken); + return _initializeDynamicFeaturesTask; + } + + public virtual Task GetInitializationTask() + { + return _initializeDynamicFeaturesTask ?? Task.CompletedTask; + } + + protected virtual async Task ExecuteInitializationAsync(FeatureManagementOptions options, CancellationToken cancellationToken) + { + try + { + using (CancellationTokenProvider.Use(cancellationToken)) + { + if (CancellationTokenProvider.Token.IsCancellationRequested) + { + return; + } + + await SaveStaticFeaturesToDatabaseAsync(options, cancellationToken); + + if (CancellationTokenProvider.Token.IsCancellationRequested) + { + return; + } + + await PreCacheDynamicFeaturesAsync(options); + } + } + catch + { + // No need to log here since inner calls log + } + } + + protected virtual async Task SaveStaticFeaturesToDatabaseAsync( + FeatureManagementOptions options, + CancellationToken cancellationToken) + { + if (!options.SaveStaticFeaturesToDatabase) + { + return; + } + + await Policy + .Handle() + .WaitAndRetryAsync( + 8, + retryAttempt => TimeSpan.FromSeconds( + Volo.Abp.RandomHelper.GetRandom( + (int)Math.Pow(2, retryAttempt) * 8, + (int)Math.Pow(2, retryAttempt) * 12) + ) + ) + .ExecuteAsync(async _ => + { + try + { + await StaticFeatureSaver.SaveAsync(); + } + catch (Exception ex) + { + Logger.LogException(ex); + throw; // Polly will catch it + } + }, cancellationToken); + } + + protected virtual async Task PreCacheDynamicFeaturesAsync(FeatureManagementOptions options) + { + if (!options.IsDynamicFeatureStoreEnabled) + { + return; + } + + try + { + // Pre-cache features, so first request doesn't wait + await DynamicFeatureDefinitionStore.GetGroupsAsync(); + } + catch (Exception ex) + { + Logger.LogException(ex); + throw; // It will be cached in Initialize() + } + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/StaticFeatureDefinitionChangedEventHandler.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/StaticFeatureDefinitionChangedEventHandler.cs new file mode 100644 index 0000000000..b26b50d9b5 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/StaticFeatureDefinitionChangedEventHandler.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus; +using Volo.Abp.Features; +using Volo.Abp.StaticDefinitions; +using Volo.Abp.Threading; + +namespace Volo.Abp.FeatureManagement; + +public class StaticFeatureDefinitionChangedEventHandler : ILocalEventHandler, ITransientDependency +{ + protected IStaticDefinitionCache> GroupCache { get; } + protected IStaticDefinitionCache> DefinitionCache { get; } + protected FeatureDynamicInitializer FeatureDynamicInitializer { get; } + protected ICancellationTokenProvider CancellationTokenProvider { get; } + + public StaticFeatureDefinitionChangedEventHandler( + IStaticDefinitionCache> groupCache, + IStaticDefinitionCache> definitionCache, + FeatureDynamicInitializer featureDynamicInitializer, + ICancellationTokenProvider cancellationTokenProvider) + { + GroupCache = groupCache; + DefinitionCache = definitionCache; + FeatureDynamicInitializer = featureDynamicInitializer; + CancellationTokenProvider = cancellationTokenProvider; + } + + public virtual async Task HandleEventAsync(StaticFeatureDefinitionChangedEvent eventData) + { + await GroupCache.ClearAsync(); + await DefinitionCache.ClearAsync(); + await FeatureDynamicInitializer.InitializeAsync(false, CancellationTokenProvider.Token); + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/AbpPermissionManagementDomainModule.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/AbpPermissionManagementDomainModule.cs index 0f40e3c039..e3825ff397 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/AbpPermissionManagementDomainModule.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/AbpPermissionManagementDomainModule.cs @@ -27,6 +27,7 @@ public class AbpPermissionManagementDomainModule : AbpModule { private readonly CancellationTokenSource _cancellationTokenSource = new(); private Task _initializeDynamicPermissionsTask; + public override void ConfigureServices(ServiceConfigurationContext context) { if (context.Services.IsDataMigrationEnvironment()) @@ -44,10 +45,12 @@ public class AbpPermissionManagementDomainModule : AbpModule AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context)); } - public override Task OnApplicationInitializationAsync(ApplicationInitializationContext context) + public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) { - InitializeDynamicPermissions(context); - return Task.CompletedTask; + var rootServiceProvider = context.ServiceProvider.GetRequiredService(); + var initializer = rootServiceProvider.GetRequiredService(); + await initializer.InitializeAsync(true, _cancellationTokenSource.Token); + _initializeDynamicPermissionsTask = initializer.GetInitializationTask(); } public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context) @@ -60,118 +63,4 @@ public class AbpPermissionManagementDomainModule : AbpModule { return _initializeDynamicPermissionsTask ?? Task.CompletedTask; } - - private void InitializeDynamicPermissions(ApplicationInitializationContext context) - { - var options = context - .ServiceProvider - .GetRequiredService>() - .Value; - - if (!options.SaveStaticPermissionsToDatabase && !options.IsDynamicPermissionStoreEnabled) - { - return; - } - - var rootServiceProvider = context.ServiceProvider.GetRequiredService(); - - _initializeDynamicPermissionsTask = Task.Run(async () => - { - using var scope = rootServiceProvider.CreateScope(); - var applicationLifetime = scope.ServiceProvider.GetService(); - var cancellationTokenProvider = scope.ServiceProvider.GetRequiredService(); - var cancellationToken = applicationLifetime?.ApplicationStopping ?? _cancellationTokenSource.Token; - - try - { - using (cancellationTokenProvider.Use(cancellationToken)) - { - if (cancellationTokenProvider.Token.IsCancellationRequested) - { - return; - } - - await SaveStaticPermissionsToDatabaseAsync(options, scope, cancellationTokenProvider); - - if (cancellationTokenProvider.Token.IsCancellationRequested) - { - return; - } - - await PreCacheDynamicPermissionsAsync(options, scope); - } - } - // ReSharper disable once EmptyGeneralCatchClause (No need to log since it is logged above) - catch { } - }); - } - - private async static Task SaveStaticPermissionsToDatabaseAsync( - PermissionManagementOptions options, - IServiceScope scope, - ICancellationTokenProvider cancellationTokenProvider) - { - if (!options.SaveStaticPermissionsToDatabase) - { - return; - } - - await Policy - .Handle() - .WaitAndRetryAsync( - 8, - retryAttempt => TimeSpan.FromSeconds( - RandomHelper.GetRandom( - (int)Math.Pow(2, retryAttempt) * 8, - (int)Math.Pow(2, retryAttempt) * 12) - ) - ) - .ExecuteAsync(async _ => - { - try - { - // ReSharper disable once AccessToDisposedClosure - await scope - .ServiceProvider - .GetRequiredService() - .SaveAsync(); - } - catch (Exception ex) - { - // ReSharper disable once AccessToDisposedClosure - scope.ServiceProvider - .GetService>()? - .LogException(ex); - - throw; // Polly will catch it - } - }, cancellationTokenProvider.Token); - } - - private async static Task PreCacheDynamicPermissionsAsync(PermissionManagementOptions options, IServiceScope scope) - { - if (!options.IsDynamicPermissionStoreEnabled) - { - return; - } - - try - { - // Pre-cache permissions, so first request doesn't wait - await scope - .ServiceProvider - .GetRequiredService() - .GetGroupsAsync(); - } - catch (Exception ex) - { - // ReSharper disable once AccessToDisposedClosure - scope - .ServiceProvider - .GetService>()? - .LogException(ex); - - throw; // It will be cached in InitializeDynamicPermissions - } - } } diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDynamicInitializer.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDynamicInitializer.cs new file mode 100644 index 0000000000..172d878409 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDynamicInitializer.cs @@ -0,0 +1,159 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Polly; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Threading; + +namespace Volo.Abp.PermissionManagement; + +public class PermissionDynamicInitializer : ISingletonDependency +{ + private Task _initializeDynamicPermissionsTask; + + public ILogger Logger { get; set; } + + protected IServiceProvider ServiceProvider { get; } + protected IOptions Options { get; } + [CanBeNull] + protected IHostApplicationLifetime ApplicationLifetime { get; } + protected ICancellationTokenProvider CancellationTokenProvider { get; } + protected IDynamicPermissionDefinitionStore DynamicPermissionDefinitionStore { get; } + protected IStaticPermissionSaver StaticPermissionSaver { get; } + + public PermissionDynamicInitializer( + IServiceProvider serviceProvider, + IOptions options, + ICancellationTokenProvider cancellationTokenProvider, + IDynamicPermissionDefinitionStore dynamicPermissionDefinitionStore, + IStaticPermissionSaver staticPermissionSaver) + { + Logger = NullLogger.Instance; + + ServiceProvider = serviceProvider; + Options = options; + ApplicationLifetime = ServiceProvider.GetService(); + CancellationTokenProvider = cancellationTokenProvider; + DynamicPermissionDefinitionStore = dynamicPermissionDefinitionStore; + StaticPermissionSaver = staticPermissionSaver; + } + + public virtual Task InitializeAsync(bool runInBackground, CancellationToken cancellationToken = default) + { + var options = Options.Value; + + if (!options.SaveStaticPermissionsToDatabase && !options.IsDynamicPermissionStoreEnabled) + { + return Task.CompletedTask; + } + + if (runInBackground) + { + _initializeDynamicPermissionsTask = Task.Run(async () => + { + if (cancellationToken == default && ApplicationLifetime?.ApplicationStopping != null) + { + cancellationToken = ApplicationLifetime.ApplicationStopping; + } + await ExecuteInitializationAsync(options, cancellationToken); + }, cancellationToken); + return Task.CompletedTask; + } + + _initializeDynamicPermissionsTask = ExecuteInitializationAsync(options, cancellationToken); + return _initializeDynamicPermissionsTask; + } + + public virtual Task GetInitializationTask() + { + return _initializeDynamicPermissionsTask ?? Task.CompletedTask; + } + + protected virtual async Task ExecuteInitializationAsync(PermissionManagementOptions options, CancellationToken cancellationToken) + { + try + { + using (CancellationTokenProvider.Use(cancellationToken)) + { + if (CancellationTokenProvider.Token.IsCancellationRequested) + { + return; + } + + await SaveStaticPermissionsToDatabaseAsync(options, cancellationToken); + + if (CancellationTokenProvider.Token.IsCancellationRequested) + { + return; + } + + await PreCacheDynamicPermissionsAsync(options); + } + } + catch + { + // No need to log here since inner calls log + } + } + + protected virtual async Task SaveStaticPermissionsToDatabaseAsync( + PermissionManagementOptions options, + CancellationToken cancellationToken) + { + if (!options.SaveStaticPermissionsToDatabase) + { + return; + } + + await Policy + .Handle() + .WaitAndRetryAsync( + 8, + retryAttempt => TimeSpan.FromSeconds( + Volo.Abp.RandomHelper.GetRandom( + (int)Math.Pow(2, retryAttempt) * 8, + (int)Math.Pow(2, retryAttempt) * 12) + ) + ) + .ExecuteAsync(async _ => + { + try + { + await StaticPermissionSaver.SaveAsync(); + } + catch (Exception ex) + { + Logger.LogException(ex); + + throw; // Polly will catch it + } + }, cancellationToken); + } + + protected virtual async Task PreCacheDynamicPermissionsAsync(PermissionManagementOptions options) + { + if (!options.IsDynamicPermissionStoreEnabled) + { + return; + } + + try + { + // Pre-cache permissions, so first request doesn't wait + await DynamicPermissionDefinitionStore.GetGroupsAsync(); + } + catch (Exception ex) + { + Logger.LogException(ex); + + throw; // It will be cached in Initialize() + } + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionDefinitionChangedEventHandler.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionDefinitionChangedEventHandler.cs new file mode 100644 index 0000000000..ceff978a96 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionDefinitionChangedEventHandler.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.StaticDefinitions; +using Volo.Abp.Threading; + +namespace Volo.Abp.PermissionManagement; + +public class StaticPermissionDefinitionChangedEventHandler : ILocalEventHandler, ITransientDependency +{ + protected IStaticDefinitionCache> GroupCache { get; } + protected IStaticDefinitionCache> DefinitionCache { get; } + protected PermissionDynamicInitializer PermissionDynamicInitializer { get; } + protected ICancellationTokenProvider CancellationTokenProvider { get; } + + public StaticPermissionDefinitionChangedEventHandler( + IStaticDefinitionCache> groupCache, + IStaticDefinitionCache> definitionCache, + PermissionDynamicInitializer permissionDynamicInitializer, + ICancellationTokenProvider cancellationTokenProvider) + { + GroupCache = groupCache; + DefinitionCache = definitionCache; + PermissionDynamicInitializer = permissionDynamicInitializer; + CancellationTokenProvider = cancellationTokenProvider; + } + + public virtual async Task HandleEventAsync(StaticPermissionDefinitionChangedEvent eventData) + { + await GroupCache.ClearAsync(); + await DefinitionCache.ClearAsync(); + await PermissionDynamicInitializer.InitializeAsync(false, CancellationTokenProvider.Token); + } +} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/AbpSettingManagementDomainModule.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/AbpSettingManagementDomainModule.cs index 3f73342d0a..c3cd35daef 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/AbpSettingManagementDomainModule.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/AbpSettingManagementDomainModule.cs @@ -53,10 +53,12 @@ public class AbpSettingManagementDomainModule : AbpModule AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context)); } - public override Task OnApplicationInitializationAsync(ApplicationInitializationContext context) + public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) { - InitializeDynamicSettings(context); - return Task.CompletedTask; + var rootServiceProvider = context.ServiceProvider.GetRequiredService(); + var initializer = rootServiceProvider.GetRequiredService(); + await initializer.InitializeAsync(true, _cancellationTokenSource.Token); + _initializeDynamicSettingsTask = initializer.GetInitializationTask(); } public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context) @@ -69,111 +71,4 @@ public class AbpSettingManagementDomainModule : AbpModule { return _initializeDynamicSettingsTask ?? Task.CompletedTask; } - - private void InitializeDynamicSettings(ApplicationInitializationContext context) - { - var options = context - .ServiceProvider - .GetRequiredService>() - .Value; - - if (!options.SaveStaticSettingsToDatabase && !options.IsDynamicSettingStoreEnabled) - { - return; - } - - var rootServiceProvider = context.ServiceProvider.GetRequiredService(); - - _initializeDynamicSettingsTask = Task.Run(async () => - { - using var scope = rootServiceProvider.CreateScope(); - var applicationLifetime = scope.ServiceProvider.GetService(); - var cancellationTokenProvider = scope.ServiceProvider.GetRequiredService(); - var cancellationToken = applicationLifetime?.ApplicationStopping ?? _cancellationTokenSource.Token; - - try - { - using (cancellationTokenProvider.Use(cancellationToken)) - { - if (cancellationTokenProvider.Token.IsCancellationRequested) - { - return; - } - - await SaveStaticSettingsToDatabaseAsync(options, scope, cancellationTokenProvider); - - if (cancellationTokenProvider.Token.IsCancellationRequested) - { - return; - } - - await PreCacheDynamicSettingsAsync(options, scope); - } - } - // ReSharper disable once EmptyGeneralCatchClause (No need to log since it is logged above) - catch { } - }); - } - - private async static Task SaveStaticSettingsToDatabaseAsync( - SettingManagementOptions options, - IServiceScope scope, - ICancellationTokenProvider cancellationTokenProvider) - { - if (!options.SaveStaticSettingsToDatabase) - { - return; - } - - await Policy - .Handle() - .WaitAndRetryAsync(8, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt) * 10)) - .ExecuteAsync(async _ => - { - try - { - // ReSharper disable once AccessToDisposedClosure - await scope - .ServiceProvider - .GetRequiredService() - .SaveAsync(); - } - catch (Exception ex) - { - // ReSharper disable once AccessToDisposedClosure - scope.ServiceProvider - .GetService>()? - .LogException(ex); - - throw; // Polly will catch it - } - }, cancellationTokenProvider.Token); - } - - private async static Task PreCacheDynamicSettingsAsync(SettingManagementOptions options, IServiceScope scope) - { - if (!options.IsDynamicSettingStoreEnabled) - { - return; - } - - try - { - // Pre-cache settings, so first request doesn't wait - await scope - .ServiceProvider - .GetRequiredService() - .GetAllAsync(); - } - catch (Exception ex) - { - // ReSharper disable once AccessToDisposedClosure - scope - .ServiceProvider - .GetService>()? - .LogException(ex); - - throw; // It will be cached in InitializeDynamicSettings - } - } } diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingDynamicInitializer.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingDynamicInitializer.cs new file mode 100644 index 0000000000..65148f5a80 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingDynamicInitializer.cs @@ -0,0 +1,158 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Polly; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Settings; +using Volo.Abp.Threading; + +namespace Volo.Abp.SettingManagement; + +public class SettingDynamicInitializer : ISingletonDependency +{ + private Task _initializeDynamicSettingsTask; + + public ILogger Logger { get; set; } + + protected IServiceProvider ServiceProvider { get; } + protected IOptions Options { get; } + [CanBeNull] + protected IHostApplicationLifetime ApplicationLifetime { get; } + protected ICancellationTokenProvider CancellationTokenProvider { get; } + protected IDynamicSettingDefinitionStore DynamicSettingDefinitionStore { get; } + protected IStaticSettingSaver StaticSettingSaver { get; } + + public SettingDynamicInitializer( + IServiceProvider serviceProvider, + IOptions options, + ICancellationTokenProvider cancellationTokenProvider, + IDynamicSettingDefinitionStore dynamicSettingDefinitionStore, + IStaticSettingSaver staticSettingSaver) + { + Logger = NullLogger.Instance; + + ServiceProvider = serviceProvider; + Options = options; + ApplicationLifetime = ServiceProvider.GetService(); + CancellationTokenProvider = cancellationTokenProvider; + DynamicSettingDefinitionStore = dynamicSettingDefinitionStore; + StaticSettingSaver = staticSettingSaver; + } + + public virtual Task InitializeAsync(bool runInBackground, CancellationToken cancellationToken = default) + { + var options = Options.Value; + + if (!options.SaveStaticSettingsToDatabase && !options.IsDynamicSettingStoreEnabled) + { + return Task.CompletedTask; + } + + if (runInBackground) + { + _initializeDynamicSettingsTask = Task.Run(async () => + { + if (cancellationToken == default && ApplicationLifetime?.ApplicationStopping != null) + { + cancellationToken = ApplicationLifetime.ApplicationStopping; + } + await ExecuteInitializationAsync(options, cancellationToken); + }, cancellationToken); + + return Task.CompletedTask; + } + + _initializeDynamicSettingsTask = ExecuteInitializationAsync(options, cancellationToken); + return _initializeDynamicSettingsTask; + } + + public virtual Task GetInitializationTask() + { + return _initializeDynamicSettingsTask ?? Task.CompletedTask; + } + + protected virtual async Task ExecuteInitializationAsync(SettingManagementOptions options, CancellationToken cancellationToken) + { + try + { + using (CancellationTokenProvider.Use(cancellationToken)) + { + if (CancellationTokenProvider.Token.IsCancellationRequested) + { + return; + } + + await SaveStaticSettingsToDatabaseAsync(options, cancellationToken); + + if (CancellationTokenProvider.Token.IsCancellationRequested) + { + return; + } + + await PreCacheDynamicSettingsAsync(options); + } + } + catch + { + // No need to log here since inner calls log + } + } + + protected virtual async Task SaveStaticSettingsToDatabaseAsync( + SettingManagementOptions options, + CancellationToken cancellationToken) + { + if (!options.SaveStaticSettingsToDatabase) + { + return; + } + + await Policy + .Handle() + .WaitAndRetryAsync( + 8, + retryAttempt => TimeSpan.FromSeconds( + Volo.Abp.RandomHelper.GetRandom( + (int)Math.Pow(2, retryAttempt) * 8, + (int)Math.Pow(2, retryAttempt) * 12) + ) + ) + .ExecuteAsync(async _ => + { + try + { + await StaticSettingSaver.SaveAsync(); + } + catch (Exception ex) + { + Logger.LogException(ex); + throw; // Polly will catch it + } + }, cancellationToken); + } + + protected virtual async Task PreCacheDynamicSettingsAsync(SettingManagementOptions options) + { + if (!options.IsDynamicSettingStoreEnabled) + { + return; + } + + try + { + // Pre-cache settings, so first request doesn't wait + await DynamicSettingDefinitionStore.GetAllAsync(); + } + catch (Exception ex) + { + Logger.LogException(ex); + throw; // It will be cached in Initialize() + } + } +} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/StaticSettingDefinitionChangedEventHandler.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/StaticSettingDefinitionChangedEventHandler.cs new file mode 100644 index 0000000000..cfc1058bb1 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/StaticSettingDefinitionChangedEventHandler.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus; +using Volo.Abp.Settings; +using Volo.Abp.StaticDefinitions; +using Volo.Abp.Threading; + +namespace Volo.Abp.SettingManagement; + +public class StaticSettingDefinitionChangedEventHandler : ILocalEventHandler, ITransientDependency +{ + protected IStaticDefinitionCache> DefinitionCache { get; } + protected SettingDynamicInitializer SettingDynamicInitializer { get; } + protected ICancellationTokenProvider CancellationTokenProvider { get; } + + public StaticSettingDefinitionChangedEventHandler( + IStaticDefinitionCache> definitionCache, + SettingDynamicInitializer settingDynamicInitializer, + ICancellationTokenProvider cancellationTokenProvider) + { + DefinitionCache = definitionCache; + SettingDynamicInitializer = settingDynamicInitializer; + CancellationTokenProvider = cancellationTokenProvider; + } + + public virtual async Task HandleEventAsync(StaticSettingDefinitionChangedEvent eventData) + { + await DefinitionCache.ClearAsync(); + await SettingDynamicInitializer.InitializeAsync(false, CancellationTokenProvider.Token); + } +} From 72b5d04337be9761618b8cc69c1b437e3e6dc1d5 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 28 Dec 2025 14:10:55 +0800 Subject: [PATCH 2/5] Change initializers to transient dependencies --- .../Volo/Abp/FeatureManagement/FeatureDynamicInitializer.cs | 2 +- .../Abp/PermissionManagement/PermissionDynamicInitializer.cs | 2 +- .../Volo/Abp/SettingManagement/SettingDynamicInitializer.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDynamicInitializer.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDynamicInitializer.cs index 8dcdda388e..9f6820274c 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDynamicInitializer.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/FeatureDynamicInitializer.cs @@ -14,7 +14,7 @@ using Volo.Abp.Threading; namespace Volo.Abp.FeatureManagement; -public class FeatureDynamicInitializer : ISingletonDependency +public class FeatureDynamicInitializer : ITransientDependency { private Task _initializeDynamicFeaturesTask; diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDynamicInitializer.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDynamicInitializer.cs index 172d878409..e78885c07f 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDynamicInitializer.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDynamicInitializer.cs @@ -14,7 +14,7 @@ using Volo.Abp.Threading; namespace Volo.Abp.PermissionManagement; -public class PermissionDynamicInitializer : ISingletonDependency +public class PermissionDynamicInitializer : ITransientDependency { private Task _initializeDynamicPermissionsTask; diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingDynamicInitializer.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingDynamicInitializer.cs index 65148f5a80..6f388c8de8 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingDynamicInitializer.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Domain/Volo/Abp/SettingManagement/SettingDynamicInitializer.cs @@ -14,7 +14,7 @@ using Volo.Abp.Threading; namespace Volo.Abp.SettingManagement; -public class SettingDynamicInitializer : ISingletonDependency +public class SettingDynamicInitializer : ITransientDependency { private Task _initializeDynamicSettingsTask; From 576ca219de7c88bba932a6f8b4866e4b4fc88185 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 28 Dec 2025 15:01:20 +0800 Subject: [PATCH 3/5] Make StaticDefinitionCache thread-safe and add tests --- .../StaticDefinitionCache.cs | 2 +- .../StaticDefinitionCache_Tests.cs | 122 ++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 framework/test/Volo.Abp.Core.Tests/Volo/Abp/StaticDefinitions/StaticDefinitionCache_Tests.cs diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/StaticDefinitions/StaticDefinitionCache.cs b/framework/src/Volo.Abp.Core/Volo/Abp/StaticDefinitions/StaticDefinitionCache.cs index ce9ac7827d..4f6a9bebe4 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/StaticDefinitions/StaticDefinitionCache.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/StaticDefinitions/StaticDefinitionCache.cs @@ -24,7 +24,7 @@ public class StaticDefinitionCache : IStaticDefinitionCache +{ + protected readonly IStaticDefinitionCache> _staticDefinitionCache1; + protected readonly IStaticDefinitionCache> _staticDefinitionCache2; + + public StaticDefinitionCache_Tests() + { + _staticDefinitionCache1 = GetRequiredService>>(); + _staticDefinitionCache2 = GetRequiredService>>(); + } + + [Fact] + public async Task GetOrCreate_Test() + { + var definition1 = new StaticDefinition1 { Name = "Definition1", Value = 1 }; + var definition2 = new StaticDefinition1 { Name = "Definition2", Value = 2 }; + + var definitionsFirstRetrieval = await _staticDefinitionCache1.GetOrCreateAsync(() => + { + return Task.FromResult(new List { definition1, definition2 }); + }); + + var definitionsSecondRetrieval = await _staticDefinitionCache1.GetOrCreateAsync(() => + { + throw new AbpException("Factory should not be called on second retrieval"); + }); + + definitionsFirstRetrieval.ShouldBe(definitionsSecondRetrieval); + + definitionsSecondRetrieval.Count.ShouldBe(2); + + definitionsSecondRetrieval[0].Name.ShouldBe("Definition1"); + definitionsSecondRetrieval[0].Value.ShouldBe(1); + + definitionsSecondRetrieval[1].Name.ShouldBe("Definition2"); + definitionsSecondRetrieval[1].Value.ShouldBe(2); + } + + [Fact] + public async Task Separate_Caches_For_Different_Types_Test() + { + var definitions1 = await _staticDefinitionCache1.GetOrCreateAsync(() => + { + return Task.FromResult(new List { new StaticDefinition1 {Name = "Definition1", Value = 1} }); + }); + var definitions2 = await _staticDefinitionCache2.GetOrCreateAsync(() => + { + return Task.FromResult(new List { new StaticDefinition2 {Name = "DefinitionA", Value = 100} }); + }); + + definitions1.Count.ShouldBe(1); + definitions1[0].Name.ShouldBe("Definition1"); + definitions1[0].Value.ShouldBe(1); + + definitions2.Count.ShouldBe(1); + definitions2[0].Name.ShouldBe("DefinitionA"); + definitions2[0].Value.ShouldBe(100); + } + + [Fact] + public async Task Clear_Test() + { + var definitions1 = await _staticDefinitionCache1.GetOrCreateAsync(() => + { + return Task.FromResult(new List { new StaticDefinition1 {Name = "Definition1", Value = 1} }); + }); + var definitions2 = await _staticDefinitionCache2.GetOrCreateAsync(() => + { + return Task.FromResult(new List { new StaticDefinition2 {Name = "DefinitionA", Value = 100} }); + }); + + definitions1.Count.ShouldBe(1); + definitions1[0].Name.ShouldBe("Definition1"); + definitions1[0].Value.ShouldBe(1); + + definitions2.Count.ShouldBe(1); + definitions2[0].Name.ShouldBe("DefinitionA"); + definitions2[0].Value.ShouldBe(100); + + await _staticDefinitionCache1.ClearAsync(); + await _staticDefinitionCache2.ClearAsync(); + + var definitions1AfterClear = await _staticDefinitionCache1.GetOrCreateAsync(() => + { + return Task.FromResult(new List { new StaticDefinition1 {Name = "DefinitionNew", Value = 10} }); + }); + var definitions2AfterClear = await _staticDefinitionCache2.GetOrCreateAsync(() => + { + return Task.FromResult(new List {new StaticDefinition2 {Name = "DefinitionNewA", Value = 200}}); + }); + + definitions1AfterClear.Count.ShouldBe(1); + definitions1AfterClear[0].Name.ShouldBe("DefinitionNew"); + definitions1AfterClear[0].Value.ShouldBe(10); + + definitions2AfterClear.Count.ShouldBe(1); + definitions2AfterClear[0].Name.ShouldBe("DefinitionNewA"); + definitions2AfterClear[0].Value.ShouldBe(200); + } + + public class StaticDefinition1 + { + public string Name { get; set; } + + public int Value { get; set; } + } + + public class StaticDefinition2 + { + public string Name { get; set; } + + public int Value { get; set; } + } +} From cf9f31719ca141c7fa0e5444a3e081675e74d2b0 Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 29 Dec 2025 20:15:00 +0800 Subject: [PATCH 4/5] Refactor StaticPermissionDefinitionStore to use caches --- .../StaticPermissionDefinitionStore.cs | 134 +++++++++--------- 1 file changed, 66 insertions(+), 68 deletions(-) diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs index 5ac2257060..2f95f3fef6 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/StaticPermissionDefinitionStore.cs @@ -6,76 +6,72 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; +using Volo.Abp.StaticDefinitions; namespace Volo.Abp.Authorization.Permissions; public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore, ISingletonDependency { - protected IDictionary PermissionGroupDefinitions => _lazyPermissionGroupDefinitions.Value.Item1; - private readonly Lazy<(Dictionary, List)> _lazyPermissionGroupDefinitions; - - protected IDictionary PermissionDefinitions => _lazyPermissionDefinitions.Value; - private readonly Lazy> _lazyPermissionDefinitions; - - protected IList ResourcePermissionDefinitions => _lazyPermissionGroupDefinitions.Value.Item2; - + protected IServiceProvider ServiceProvider { get; } protected AbpPermissionOptions Options { get; } - - private readonly IServiceProvider _serviceProvider; + protected IStaticDefinitionCache, List)> GroupCache { get; } + protected IStaticDefinitionCache> DefinitionCache { get; } public StaticPermissionDefinitionStore( IServiceProvider serviceProvider, - IOptions options) + IOptions options, + IStaticDefinitionCache, List)> groupCache, + IStaticDefinitionCache> definitionCache) { - _serviceProvider = serviceProvider; + ServiceProvider = serviceProvider; Options = options.Value; + GroupCache = groupCache; + DefinitionCache = definitionCache; + } - _lazyPermissionDefinitions = new Lazy>( - CreatePermissionDefinitions, - isThreadSafe: true - ); - - _lazyPermissionGroupDefinitions = new Lazy<(Dictionary, List)>( - CreatePermissionGroupDefinitions, - isThreadSafe: true - ); + public async Task GetOrNullAsync(string name) + { + var defs = await GetPermissionDefinitionsAsync(); + return defs.GetOrDefault(name); } - protected virtual Dictionary CreatePermissionDefinitions() + public virtual async Task> GetPermissionsAsync() { - var permissions = new Dictionary(); + var defs = await GetPermissionDefinitionsAsync(); + return defs.Values.ToImmutableList(); + } - foreach (var groupDefinition in PermissionGroupDefinitions.Values) - { - foreach (var permission in groupDefinition.Permissions) - { - AddPermissionToDictionaryRecursively(permissions, permission); - } - } + public virtual async Task GetResourcePermissionOrNullAsync(string resourceName, string name) + { + var (_, resourcePermissions) = await GetPermissionGroupDefinitionsAsync(); + return resourcePermissions.FirstOrDefault(p => p.ResourceName == resourceName && p.Name == name); + } - return permissions; + public virtual async Task> GetResourcePermissionsAsync() + { + var (_, resourcePermissions) = await GetPermissionGroupDefinitionsAsync(); + return resourcePermissions.ToImmutableList(); } - protected virtual void AddPermissionToDictionaryRecursively( - Dictionary permissions, - PermissionDefinition permission) + public async Task> GetGroupsAsync() { - if (permissions.ContainsKey(permission.Name)) - { - throw new AbpException("Duplicate permission name: " + permission.Name); - } + var (groups, _) = await GetPermissionGroupDefinitionsAsync(); + return groups.Values.ToImmutableList(); + } - permissions[permission.Name] = permission; + protected virtual async Task<(Dictionary, List)> GetPermissionGroupDefinitionsAsync() + { + return await GroupCache.GetOrCreateAsync(CreatePermissionGroupDefinitionsAsync); + } - foreach (var child in permission.Children) - { - AddPermissionToDictionaryRecursively(permissions, child); - } + protected virtual async Task> GetPermissionDefinitionsAsync() + { + return await DefinitionCache.GetOrCreateAsync(CreatePermissionDefinitionsAsync); } - protected virtual (Dictionary, List) CreatePermissionGroupDefinitions() + protected virtual Task<(Dictionary, List)> CreatePermissionGroupDefinitionsAsync() { - using (var scope = _serviceProvider.CreateScope()) + using (var scope = ServiceProvider.CreateScope()) { var context = new PermissionDefinitionContext(scope.ServiceProvider); @@ -104,38 +100,40 @@ public class StaticPermissionDefinitionStore : IStaticPermissionDefinitionStore, context.CurrentProvider = null; - return (context.Groups, context.ResourcePermissions); + return Task.FromResult((context.Groups, context.ResourcePermissions)); } } - public Task GetOrNullAsync(string name) + protected virtual async Task> CreatePermissionDefinitionsAsync() { - return Task.FromResult(PermissionDefinitions.GetOrDefault(name)); - } + var permissions = new Dictionary(); - public virtual Task> GetPermissionsAsync() - { - return Task.FromResult>( - PermissionDefinitions.Values.ToImmutableList() - ); - } + var (groups, _) = await GetPermissionGroupDefinitionsAsync(); + foreach (var groupDefinition in groups.Values) + { + foreach (var permission in groupDefinition.Permissions) + { + AddPermissionToDictionaryRecursively(permissions, permission); + } + } - public virtual Task GetResourcePermissionOrNullAsync(string resourceName, string name) - { - return Task.FromResult(ResourcePermissionDefinitions.FirstOrDefault(p => p.ResourceName == resourceName && p.Name == name)); + return permissions; } - public virtual Task> GetResourcePermissionsAsync() + protected virtual void AddPermissionToDictionaryRecursively( + Dictionary permissions, + PermissionDefinition permission) { - return Task.FromResult>( - ResourcePermissionDefinitions.ToImmutableList() - ); - } + if (permissions.ContainsKey(permission.Name)) + { + throw new AbpException("Duplicate permission name: " + permission.Name); + } - public Task> GetGroupsAsync() - { - return Task.FromResult>( - PermissionGroupDefinitions.Values.ToImmutableList() - ); + permissions[permission.Name] = permission; + + foreach (var child in permission.Children) + { + AddPermissionToDictionaryRecursively(permissions, child); + } } } From 68cdc7449cd666bf71be7c1e536be586de01f175 Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 29 Dec 2025 20:21:36 +0800 Subject: [PATCH 5/5] Update GroupCache type in event handler --- .../StaticPermissionDefinitionChangedEventHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionDefinitionChangedEventHandler.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionDefinitionChangedEventHandler.cs index ceff978a96..5f6dd1e700 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionDefinitionChangedEventHandler.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/StaticPermissionDefinitionChangedEventHandler.cs @@ -10,13 +10,13 @@ namespace Volo.Abp.PermissionManagement; public class StaticPermissionDefinitionChangedEventHandler : ILocalEventHandler, ITransientDependency { - protected IStaticDefinitionCache> GroupCache { get; } + protected IStaticDefinitionCache, List)> GroupCache { get; } protected IStaticDefinitionCache> DefinitionCache { get; } protected PermissionDynamicInitializer PermissionDynamicInitializer { get; } protected ICancellationTokenProvider CancellationTokenProvider { get; } public StaticPermissionDefinitionChangedEventHandler( - IStaticDefinitionCache> groupCache, + IStaticDefinitionCache, List)> groupCache, IStaticDefinitionCache> definitionCache, PermissionDynamicInitializer permissionDynamicInitializer, ICancellationTokenProvider cancellationTokenProvider)