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..4f6a9bebe4 --- /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() + { + Interlocked.Exchange(ref _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/framework/test/Volo.Abp.Core.Tests/Volo/Abp/StaticDefinitions/StaticDefinitionCache_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/StaticDefinitions/StaticDefinitionCache_Tests.cs new file mode 100644 index 0000000000..21ab9b8c59 --- /dev/null +++ b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/StaticDefinitions/StaticDefinitionCache_Tests.cs @@ -0,0 +1,122 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.StaticDefinitions; + +public class StaticDefinitionCache_Tests : AbpIntegratedTest +{ + 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; } + } +} 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..9f6820274c --- /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 : ITransientDependency +{ + 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..e78885c07f --- /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 : ITransientDependency +{ + 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..6f388c8de8 --- /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 : ITransientDependency +{ + 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); + } +}