diff --git a/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj b/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj index 64352257f3..2aefa76caa 100644 --- a/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj +++ b/framework/src/Volo.Abp.Localization/Volo.Abp.Localization.csproj @@ -22,6 +22,11 @@ + + + + + diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationModule.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationModule.cs index 4ce8c32f88..6508976d85 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationModule.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpLocalizationModule.cs @@ -1,6 +1,17 @@ -using Volo.Abp.Localization.Resources.AbpLocalization; +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.DependencyInjection; +using Volo.Abp.Localization.Distributed; +using Volo.Abp.Localization.Resources.AbpLocalization; using Volo.Abp.Modularity; using Volo.Abp.Settings; +using Volo.Abp.Threading; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.Localization; @@ -8,10 +19,13 @@ namespace Volo.Abp.Localization; [DependsOn( typeof(AbpVirtualFileSystemModule), typeof(AbpSettingsModule), - typeof(AbpLocalizationAbstractionsModule) + typeof(AbpLocalizationAbstractionsModule), + typeof(AbpThreadingModule) )] public class AbpLocalizationModule : AbpModule { + private readonly CancellationTokenSource _cancellationTokenSource = new(); + public override void ConfigureServices(ServiceConfigurationContext context) { AbpStringLocalizerFactory.Replace(context.Services); @@ -33,4 +47,75 @@ public class AbpLocalizationModule : AbpModule .AddVirtualJson("/Localization/Resources/AbpLocalization"); }); } + + public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context) + { + await SaveLocalizationsAsync(context); + } + + public override Task OnApplicationShutdownAsync(ApplicationShutdownContext context) + { + _cancellationTokenSource.Cancel(); + return Task.CompletedTask; + } + + private async Task SaveLocalizationsAsync(ApplicationInitializationContext context) + { + var options = context + .ServiceProvider + .GetRequiredService>() + .Value; + + if (!options.SaveToDistributedStore) + { + return; + } + + var rootServiceProvider = context.ServiceProvider.GetRequiredService(); + + 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 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); + } + } + // ReSharper disable once EmptyGeneralCatchClause (No need to log since it is logged above) + catch { } + }); + } }