diff --git a/framework/Volo.Abp.slnx b/framework/Volo.Abp.slnx index 3d74af9f95..1302600c09 100644 --- a/framework/Volo.Abp.slnx +++ b/framework/Volo.Abp.slnx @@ -181,6 +181,7 @@ + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs index e3d3c12370..5d87371ae8 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Client/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using Microsoft.AspNetCore.Http; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; @@ -7,6 +8,7 @@ using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies; using Volo.Abp.Caching; using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; using Volo.Abp.Threading; using Volo.Abp.Users; @@ -82,28 +84,47 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu return configuration; } - private async Task GetRemoteConfigurationAsync() + protected virtual async Task GetRemoteConfigurationAsync() { - var config = await ApplicationConfigurationAppService.GetAsync( + var cultureName = CultureInfo.CurrentUICulture.Name; + + var configTask = ApplicationConfigurationAppService.GetAsync( new ApplicationConfigurationRequestOptions { IncludeLocalizationResources = false } ); - var localizationDto = await ApplicationLocalizationClientProxy.GetAsync( - new ApplicationLocalizationRequestDto { - CultureName = config.Localization.CurrentCulture.Name, + var localizationTask = ApplicationLocalizationClientProxy.GetAsync( + new ApplicationLocalizationRequestDto + { + CultureName = cultureName, OnlyDynamics = true } ); + await Task.WhenAll(configTask, localizationTask); + + var config = configTask.Result; + var localizationDto = localizationTask.Result; + + if (!CultureHelper.IsCompatibleCulture(config.Localization.CurrentCulture.Name, cultureName)) + { + localizationDto = await ApplicationLocalizationClientProxy.GetAsync( + new ApplicationLocalizationRequestDto + { + CultureName = config.Localization.CurrentCulture.Name, + OnlyDynamics = true + } + ); + } + config.Localization.Resources = localizationDto.Resources; return config; } - public ApplicationConfigurationDto Get() + public virtual ApplicationConfigurationDto Get() { string? cacheKey = null; var httpContext = HttpContextAccessor?.HttpContext; diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Client.Tests/Volo.Abp.AspNetCore.Mvc.Client.Tests.csproj b/framework/test/Volo.Abp.AspNetCore.Mvc.Client.Tests/Volo.Abp.AspNetCore.Mvc.Client.Tests.csproj new file mode 100644 index 0000000000..d1190652a3 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Client.Tests/Volo.Abp.AspNetCore.Mvc.Client.Tests.csproj @@ -0,0 +1,17 @@ + + + + + + net10.0 + + + + + + + + + + + diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Client.Tests/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientTestBase.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Client.Tests/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientTestBase.cs new file mode 100644 index 0000000000..96e82fe105 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Client.Tests/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientTestBase.cs @@ -0,0 +1,11 @@ +using Volo.Abp.Testing; + +namespace Volo.Abp.AspNetCore.Mvc.Client; + +public abstract class AbpAspNetCoreMvcClientTestBase : AbpIntegratedTest +{ + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Client.Tests/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientTestModule.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Client.Tests/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientTestModule.cs new file mode 100644 index 0000000000..04e9f856a2 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Client.Tests/Volo/Abp/AspNetCore/Mvc/Client/AbpAspNetCoreMvcClientTestModule.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Mvc.Client; + +[DependsOn( + typeof(AbpAspNetCoreMvcClientModule), + typeof(AbpAutofacModule) +)] +public class AbpAspNetCoreMvcClientTestModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddHttpContextAccessor(); + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Client.Tests/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Client.Tests/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient_Tests.cs new file mode 100644 index 0000000000..6784c69954 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Client.Tests/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient_Tests.cs @@ -0,0 +1,105 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using NSubstitute; +using Shouldly; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ClientProxies; +using Volo.Abp.Localization; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Client; + +public class MvcCachedApplicationConfigurationClient_Tests : AbpAspNetCoreMvcClientTestBase +{ + private AbpApplicationConfigurationClientProxy _configProxy; + private AbpApplicationLocalizationClientProxy _localizationProxy; + + private readonly ICachedApplicationConfigurationClient _applicationConfigurationClient; + + public MvcCachedApplicationConfigurationClient_Tests() + { + _applicationConfigurationClient = GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + _configProxy = Substitute.For(); + _localizationProxy = Substitute.For(); + + services.Replace(ServiceDescriptor.Transient(_ => _configProxy)); + services.Replace(ServiceDescriptor.Transient(_ => _localizationProxy)); + } + + [Fact] + public async Task Should_Use_CurrentUICulture_For_Localization_Request() + { + var cultureName = "en"; + + using (CultureHelper.Use(cultureName)) + { + var configTcs = new TaskCompletionSource(); + _configProxy.GetAsync(Arg.Any()).Returns(configTcs.Task); + + var expectedResources = new Dictionary + { + ["TestResource"] = new() + }; + + _localizationProxy.GetAsync(Arg.Any()).Returns(new ApplicationLocalizationDto { Resources = expectedResources }); + + var resultTask = _applicationConfigurationClient.GetAsync(); + + // Localization request should be fired before config completes (concurrent). + await _localizationProxy.Received(1).GetAsync(Arg.Is(x => x.CultureName == cultureName && x.OnlyDynamics == true)); + + // Now let config complete. + configTcs.SetResult(CreateConfigDto(cultureName)); + var result = await resultTask; + + result.Localization.Resources.ShouldBe(expectedResources); + + await _configProxy.Received(1).GetAsync(Arg.Is(x => x.IncludeLocalizationResources == false)); + } + } + + [Fact] + public async Task Should_Refetch_Localization_When_Culture_Differs() + { + var currentCulture = "en"; + var serverCulture = "tr"; + + using (CultureHelper.Use(currentCulture)) + { + _configProxy.GetAsync(Arg.Any()).Returns(CreateConfigDto(serverCulture)); + + var wrongResources = new Dictionary(); + var correctResources = new Dictionary + { + ["TestResource"] = new() + }; + + _localizationProxy.GetAsync(Arg.Is(x => x.CultureName == currentCulture)).Returns(new ApplicationLocalizationDto { Resources = wrongResources }); + _localizationProxy.GetAsync(Arg.Is(x => x.CultureName == serverCulture)).Returns(new ApplicationLocalizationDto { Resources = correctResources }); + + var result = await _applicationConfigurationClient.GetAsync(); + + result.Localization.Resources.ShouldBe(correctResources); + + await _localizationProxy.Received(1).GetAsync(Arg.Is(x => x.CultureName == currentCulture)); + await _localizationProxy.Received(1).GetAsync(Arg.Is(x => x.CultureName == serverCulture)); + } + } + + private static ApplicationConfigurationDto CreateConfigDto(string cultureName) + { + return new ApplicationConfigurationDto + { + Localization = + { + CurrentCulture = new CurrentCultureDto { Name = cultureName } + } + }; + } +}