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 }
+ }
+ };
+ }
+}