mirror of https://github.com/abpframework/abp.git
committed by
GitHub
23 changed files with 950 additions and 477 deletions
@ -0,0 +1,9 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.Authorization.Permissions; |
||||
|
|
||||
|
[Serializable] |
||||
|
public class StaticPermissionDefinitionChangedEvent |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.StaticDefinitions; |
||||
|
|
||||
|
public interface IStaticDefinitionCache<TKey, TValue> |
||||
|
{ |
||||
|
Task<TValue> GetOrCreateAsync(Func<Task<TValue>> factory); |
||||
|
|
||||
|
Task ClearAsync(); |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.StaticDefinitions; |
||||
|
|
||||
|
public class StaticDefinitionCache<TKey, TValue> : IStaticDefinitionCache<TKey, TValue> |
||||
|
{ |
||||
|
private Lazy<Task<TValue>>? _lazy; |
||||
|
|
||||
|
public virtual async Task<TValue> GetOrCreateAsync(Func<Task<TValue>> factory) |
||||
|
{ |
||||
|
var lazy = _lazy; |
||||
|
if (lazy != null) |
||||
|
{ |
||||
|
return await lazy.Value; |
||||
|
} |
||||
|
|
||||
|
var newLazy = new Lazy<Task<TValue>>(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; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.Features; |
||||
|
|
||||
|
[Serializable] |
||||
|
public class StaticFeatureDefinitionChangedEvent |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.Settings; |
||||
|
|
||||
|
[Serializable] |
||||
|
public class StaticSettingDefinitionChangedEvent |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.TextTemplating; |
||||
|
|
||||
|
[Serializable] |
||||
|
public class StaticTemplateDefinitionChangedEvent |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -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<AbpTestModule> |
||||
|
{ |
||||
|
protected readonly IStaticDefinitionCache<StaticDefinition1, List<StaticDefinition1>> _staticDefinitionCache1; |
||||
|
protected readonly IStaticDefinitionCache<StaticDefinition2, List<StaticDefinition2>> _staticDefinitionCache2; |
||||
|
|
||||
|
public StaticDefinitionCache_Tests() |
||||
|
{ |
||||
|
_staticDefinitionCache1 = GetRequiredService<IStaticDefinitionCache<StaticDefinition1, List<StaticDefinition1>>>(); |
||||
|
_staticDefinitionCache2 = GetRequiredService<IStaticDefinitionCache<StaticDefinition2, List<StaticDefinition2>>>(); |
||||
|
} |
||||
|
|
||||
|
[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<StaticDefinition1> { 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<StaticDefinition1> { new StaticDefinition1 {Name = "Definition1", Value = 1} }); |
||||
|
}); |
||||
|
var definitions2 = await _staticDefinitionCache2.GetOrCreateAsync(() => |
||||
|
{ |
||||
|
return Task.FromResult(new List<StaticDefinition2> { 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<StaticDefinition1> { new StaticDefinition1 {Name = "Definition1", Value = 1} }); |
||||
|
}); |
||||
|
var definitions2 = await _staticDefinitionCache2.GetOrCreateAsync(() => |
||||
|
{ |
||||
|
return Task.FromResult(new List<StaticDefinition2> { 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<StaticDefinition1> { new StaticDefinition1 {Name = "DefinitionNew", Value = 10} }); |
||||
|
}); |
||||
|
var definitions2AfterClear = await _staticDefinitionCache2.GetOrCreateAsync(() => |
||||
|
{ |
||||
|
return Task.FromResult(new List<StaticDefinition2> {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; } |
||||
|
} |
||||
|
} |
||||
@ -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<FeatureDynamicInitializer> Logger { get; set; } |
||||
|
|
||||
|
protected IServiceProvider ServiceProvider { get; } |
||||
|
protected IOptions<FeatureManagementOptions> Options { get; } |
||||
|
[CanBeNull] |
||||
|
protected IHostApplicationLifetime ApplicationLifetime { get; } |
||||
|
protected ICancellationTokenProvider CancellationTokenProvider { get; } |
||||
|
protected IDynamicFeatureDefinitionStore DynamicFeatureDefinitionStore { get; } |
||||
|
protected IStaticFeatureSaver StaticFeatureSaver { get; } |
||||
|
|
||||
|
public FeatureDynamicInitializer( |
||||
|
IServiceProvider serviceProvider, |
||||
|
IOptions<FeatureManagementOptions> options, |
||||
|
ICancellationTokenProvider cancellationTokenProvider, |
||||
|
IDynamicFeatureDefinitionStore dynamicFeatureDefinitionStore, |
||||
|
IStaticFeatureSaver staticFeatureSaver) |
||||
|
{ |
||||
|
Logger = NullLogger<FeatureDynamicInitializer>.Instance; |
||||
|
|
||||
|
ServiceProvider = serviceProvider; |
||||
|
Options = options; |
||||
|
ApplicationLifetime = ServiceProvider.GetService<IHostApplicationLifetime>(); |
||||
|
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<Exception>() |
||||
|
.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()
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<StaticFeatureDefinitionChangedEvent>, ITransientDependency |
||||
|
{ |
||||
|
protected IStaticDefinitionCache<FeatureGroupDefinition, Dictionary<string, FeatureGroupDefinition>> GroupCache { get; } |
||||
|
protected IStaticDefinitionCache<FeatureDefinition, Dictionary<string, FeatureDefinition>> DefinitionCache { get; } |
||||
|
protected FeatureDynamicInitializer FeatureDynamicInitializer { get; } |
||||
|
protected ICancellationTokenProvider CancellationTokenProvider { get; } |
||||
|
|
||||
|
public StaticFeatureDefinitionChangedEventHandler( |
||||
|
IStaticDefinitionCache<FeatureGroupDefinition, Dictionary<string, FeatureGroupDefinition>> groupCache, |
||||
|
IStaticDefinitionCache<FeatureDefinition, Dictionary<string, FeatureDefinition>> 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); |
||||
|
} |
||||
|
} |
||||
@ -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<PermissionDynamicInitializer> Logger { get; set; } |
||||
|
|
||||
|
protected IServiceProvider ServiceProvider { get; } |
||||
|
protected IOptions<PermissionManagementOptions> Options { get; } |
||||
|
[CanBeNull] |
||||
|
protected IHostApplicationLifetime ApplicationLifetime { get; } |
||||
|
protected ICancellationTokenProvider CancellationTokenProvider { get; } |
||||
|
protected IDynamicPermissionDefinitionStore DynamicPermissionDefinitionStore { get; } |
||||
|
protected IStaticPermissionSaver StaticPermissionSaver { get; } |
||||
|
|
||||
|
public PermissionDynamicInitializer( |
||||
|
IServiceProvider serviceProvider, |
||||
|
IOptions<PermissionManagementOptions> options, |
||||
|
ICancellationTokenProvider cancellationTokenProvider, |
||||
|
IDynamicPermissionDefinitionStore dynamicPermissionDefinitionStore, |
||||
|
IStaticPermissionSaver staticPermissionSaver) |
||||
|
{ |
||||
|
Logger = NullLogger<PermissionDynamicInitializer>.Instance; |
||||
|
|
||||
|
ServiceProvider = serviceProvider; |
||||
|
Options = options; |
||||
|
ApplicationLifetime = ServiceProvider.GetService<IHostApplicationLifetime>(); |
||||
|
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<Exception>() |
||||
|
.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()
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<StaticPermissionDefinitionChangedEvent>, ITransientDependency |
||||
|
{ |
||||
|
protected IStaticDefinitionCache<PermissionGroupDefinition, Dictionary<string, PermissionGroupDefinition>> GroupCache { get; } |
||||
|
protected IStaticDefinitionCache<PermissionDefinition, Dictionary<string, PermissionDefinition>> DefinitionCache { get; } |
||||
|
protected PermissionDynamicInitializer PermissionDynamicInitializer { get; } |
||||
|
protected ICancellationTokenProvider CancellationTokenProvider { get; } |
||||
|
|
||||
|
public StaticPermissionDefinitionChangedEventHandler( |
||||
|
IStaticDefinitionCache<PermissionGroupDefinition, Dictionary<string, PermissionGroupDefinition>> groupCache, |
||||
|
IStaticDefinitionCache<PermissionDefinition, Dictionary<string, PermissionDefinition>> 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); |
||||
|
} |
||||
|
} |
||||
@ -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<SettingDynamicInitializer> Logger { get; set; } |
||||
|
|
||||
|
protected IServiceProvider ServiceProvider { get; } |
||||
|
protected IOptions<SettingManagementOptions> Options { get; } |
||||
|
[CanBeNull] |
||||
|
protected IHostApplicationLifetime ApplicationLifetime { get; } |
||||
|
protected ICancellationTokenProvider CancellationTokenProvider { get; } |
||||
|
protected IDynamicSettingDefinitionStore DynamicSettingDefinitionStore { get; } |
||||
|
protected IStaticSettingSaver StaticSettingSaver { get; } |
||||
|
|
||||
|
public SettingDynamicInitializer( |
||||
|
IServiceProvider serviceProvider, |
||||
|
IOptions<SettingManagementOptions> options, |
||||
|
ICancellationTokenProvider cancellationTokenProvider, |
||||
|
IDynamicSettingDefinitionStore dynamicSettingDefinitionStore, |
||||
|
IStaticSettingSaver staticSettingSaver) |
||||
|
{ |
||||
|
Logger = NullLogger<SettingDynamicInitializer>.Instance; |
||||
|
|
||||
|
ServiceProvider = serviceProvider; |
||||
|
Options = options; |
||||
|
ApplicationLifetime = ServiceProvider.GetService<IHostApplicationLifetime>(); |
||||
|
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<Exception>() |
||||
|
.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()
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<StaticSettingDefinitionChangedEvent>, ITransientDependency |
||||
|
{ |
||||
|
protected IStaticDefinitionCache<SettingDefinition, Dictionary<string, SettingDefinition>> DefinitionCache { get; } |
||||
|
protected SettingDynamicInitializer SettingDynamicInitializer { get; } |
||||
|
protected ICancellationTokenProvider CancellationTokenProvider { get; } |
||||
|
|
||||
|
public StaticSettingDefinitionChangedEventHandler( |
||||
|
IStaticDefinitionCache<SettingDefinition, Dictionary<string, SettingDefinition>> 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); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue