|
|
|
@ -1,8 +1,18 @@ |
|
|
|
using Volo.Abp.Caching; |
|
|
|
using System; |
|
|
|
using System.Threading; |
|
|
|
using System.Threading.Tasks; |
|
|
|
using Microsoft.Extensions.DependencyInjection; |
|
|
|
using Microsoft.Extensions.Hosting; |
|
|
|
using Microsoft.Extensions.Logging; |
|
|
|
using Microsoft.Extensions.Options; |
|
|
|
using Polly; |
|
|
|
using Volo.Abp.Caching; |
|
|
|
using Volo.Abp.DependencyInjection; |
|
|
|
using Volo.Abp.FeatureManagement.Localization; |
|
|
|
using Volo.Abp.Features; |
|
|
|
using Volo.Abp.Localization.ExceptionHandling; |
|
|
|
using Volo.Abp.Modularity; |
|
|
|
using Volo.Abp.Threading; |
|
|
|
|
|
|
|
namespace Volo.Abp.FeatureManagement; |
|
|
|
|
|
|
|
@ -30,4 +40,125 @@ public class AbpFeatureManagementDomainModule : AbpModule |
|
|
|
options.MapCodeNamespace("AbpFeatureManagement", typeof(AbpFeatureManagementResource)); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
private readonly CancellationTokenSource _cancellationTokenSource = new(); |
|
|
|
|
|
|
|
public override Task OnApplicationInitializationAsync(ApplicationInitializationContext context) |
|
|
|
{ |
|
|
|
InitializeDynamicFeatures(context); |
|
|
|
return Task.CompletedTask; |
|
|
|
} |
|
|
|
|
|
|
|
public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context) |
|
|
|
{ |
|
|
|
_cancellationTokenSource.Cancel(); |
|
|
|
return Task.CompletedTask; |
|
|
|
} |
|
|
|
|
|
|
|
private void InitializeDynamicFeatures(ApplicationInitializationContext context) |
|
|
|
{ |
|
|
|
var options = context |
|
|
|
.ServiceProvider |
|
|
|
.GetRequiredService<IOptions<FeatureManagementOptions>>() |
|
|
|
.Value; |
|
|
|
|
|
|
|
if (!options.SaveStaticFeaturesToDatabase && !options.IsDynamicFeatureStoreEnabled) |
|
|
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
var rootServiceProvider = context.ServiceProvider.GetRequiredService<IRootServiceProvider>(); |
|
|
|
|
|
|
|
Task.Run(async () => |
|
|
|
{ |
|
|
|
using var scope = rootServiceProvider.CreateScope(); |
|
|
|
var applicationLifetime = scope.ServiceProvider.GetService<IHostApplicationLifetime>(); |
|
|
|
var cancellationTokenProvider = scope.ServiceProvider.GetRequiredService<ICancellationTokenProvider>(); |
|
|
|
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<Exception>() |
|
|
|
.WaitAndRetryAsync(8, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt) * 10)) |
|
|
|
.ExecuteAsync(async _ => |
|
|
|
{ |
|
|
|
try |
|
|
|
{ |
|
|
|
// ReSharper disable once AccessToDisposedClosure
|
|
|
|
await scope |
|
|
|
.ServiceProvider |
|
|
|
.GetRequiredService<IStaticFeatureSaver>() |
|
|
|
.SaveAsync(); |
|
|
|
} |
|
|
|
catch (Exception ex) |
|
|
|
{ |
|
|
|
// ReSharper disable once AccessToDisposedClosure
|
|
|
|
scope.ServiceProvider |
|
|
|
.GetService<ILogger<AbpFeatureManagementDomainModule>>()? |
|
|
|
.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<IDynamicFeatureDefinitionStore>() |
|
|
|
.GetGroupsAsync(); |
|
|
|
} |
|
|
|
catch (Exception ex) |
|
|
|
{ |
|
|
|
// ReSharper disable once AccessToDisposedClosure
|
|
|
|
scope |
|
|
|
.ServiceProvider |
|
|
|
.GetService<ILogger<AbpFeatureManagementDomainModule>>()? |
|
|
|
.LogException(ex); |
|
|
|
|
|
|
|
throw; // It will be cached in InitializeDynamicFeatures
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|