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..27856fe7e4 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;
@@ -84,20 +85,43 @@ public class MvcCachedApplicationConfigurationClient : ICachedApplicationConfigu
private 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
}
);
+ var config = await configTask;
+
+ ApplicationLocalizationDto localizationDto;
+ // In most cases, the culture matches and we can reuse the concurrent request.
+ // If not, we discard it and make a new request with the correct culture.
+ if (string.Equals(config.Localization.CurrentCulture.Name, cultureName, StringComparison.OrdinalIgnoreCase))
+ {
+ localizationDto = await localizationTask;
+ }
+ else
+ {
+ localizationDto = await ApplicationLocalizationClientProxy.GetAsync(
+ new ApplicationLocalizationRequestDto
+ {
+ CultureName = config.Localization.CurrentCulture.Name,
+ OnlyDynamics = true
+ }
+ );
+ }
+
config.Localization.Resources = localizationDto.Resources;
return config;
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..ed20593873
--- /dev/null
+++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Client.Tests/Volo/Abp/AspNetCore/Mvc/Client/MvcCachedApplicationConfigurationClient_Tests.cs
@@ -0,0 +1,90 @@
+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 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_Concurrent_Requests_When_Culture_Matches()
+ {
+ var cultureName = "en";
+
+ _configProxy.GetAsync(Arg.Any()).Returns(CreateConfigDto(cultureName));
+
+ var expectedResources = new Dictionary
+ {
+ ["TestResource"] = new()
+ };
+
+ _localizationProxy.GetAsync(Arg.Any()).Returns(new ApplicationLocalizationDto { Resources = expectedResources });
+
+ var result = await _applicationConfigurationClient.GetAsync();
+
+ result.Localization.Resources.ShouldBe(expectedResources);
+
+ await _configProxy.Received(1).GetAsync(Arg.Is(x => x.IncludeLocalizationResources == false));
+ await _localizationProxy.Received(1).GetAsync(Arg.Is(x => x.CultureName == cultureName && x.OnlyDynamics == true));
+ }
+
+ [Fact]
+ public async Task Should_Refetch_Localization_When_Culture_Differs()
+ {
+ var currentCulture = "en";
+ var serverCulture = "tr";
+
+ _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 == serverCulture));
+ }
+
+ private static ApplicationConfigurationDto CreateConfigDto(string cultureName)
+ {
+ return new ApplicationConfigurationDto
+ {
+ Localization =
+ {
+ CurrentCulture = new CurrentCultureDto { Name = cultureName }
+ }
+ };
+ }
+}