From 6895d24d5a23c4aa86326742ac6bb8c30b548d4d Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 1 Apr 2026 18:10:11 +0800 Subject: [PATCH] feat: Enhance URL-based culture handling with new interfaces and async methods --- .../AbpWasmCultureMenuItemUrlProvider.cs | 8 +++-- .../IRouteBasedCultureUrlHelper.cs | 14 ++++++++ .../WebAssembly/RouteBasedCultureUrlHelper.cs | 34 ++++++++++++++++++ .../AbpCultureMenuItemUrlProvider.cs | 15 ++++---- .../AbpRequestLocalizationMiddleware.cs | 5 ++- .../IMenuItemCulturePrefixHelper.cs | 8 +++++ .../Navigation/MenuItemCulturePrefixHelper.cs | 13 +++++-- .../Themes/Basic/LoginDisplay.razor.cs | 36 ++++++------------- 8 files changed, 94 insertions(+), 39 deletions(-) create mode 100644 framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IRouteBasedCultureUrlHelper.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/RouteBasedCultureUrlHelper.cs create mode 100644 framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/IMenuItemCulturePrefixHelper.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/AbpWasmCultureMenuItemUrlProvider.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/AbpWasmCultureMenuItemUrlProvider.cs index 0c7b9f4f09..9a1077e1b0 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/AbpWasmCultureMenuItemUrlProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/AbpWasmCultureMenuItemUrlProvider.cs @@ -14,11 +14,14 @@ namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming; public class AbpWasmCultureMenuItemUrlProvider : IMenuItemUrlProvider, ITransientDependency { protected ICachedApplicationConfigurationClient ConfigurationClient { get; } + protected IMenuItemCulturePrefixHelper MenuItemCulturePrefixHelper { get; } public AbpWasmCultureMenuItemUrlProvider( - ICachedApplicationConfigurationClient configurationClient) + ICachedApplicationConfigurationClient configurationClient, + IMenuItemCulturePrefixHelper menuItemCulturePrefixHelper) { ConfigurationClient = configurationClient; + MenuItemCulturePrefixHelper = menuItemCulturePrefixHelper; } public virtual async Task HandleAsync(MenuItemUrlProviderContext context) @@ -40,7 +43,7 @@ public class AbpWasmCultureMenuItemUrlProvider : IMenuItemUrlProvider, ITransien return; } - MenuItemCulturePrefixHelper.PrependCulturePrefix(context.Menu, "/" + culture); + await MenuItemCulturePrefixHelper.PrependCulturePrefixAsync(context.Menu, "/" + culture); } protected virtual string? GetCulture(Mvc.ApplicationConfigurations.ApplicationConfigurationDto config) @@ -57,5 +60,4 @@ public class AbpWasmCultureMenuItemUrlProvider : IMenuItemUrlProvider, ITransien return isKnownCulture ? currentCulture : null; } - } diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IRouteBasedCultureUrlHelper.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IRouteBasedCultureUrlHelper.cs new file mode 100644 index 0000000000..f56aeaf693 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/IRouteBasedCultureUrlHelper.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly; + +public interface IRouteBasedCultureUrlHelper +{ + /// + /// Prepends the current culture to when route-based culture is enabled + /// and the current culture is a known application language. + /// Returns the original unchanged when the feature is disabled or the + /// culture is not recognised. + /// + Task PrependCulturePrefixAsync(string url); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/RouteBasedCultureUrlHelper.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/RouteBasedCultureUrlHelper.cs new file mode 100644 index 0000000000..b9f6a07b64 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/RouteBasedCultureUrlHelper.cs @@ -0,0 +1,34 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Mvc.Client; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly; + +public class RouteBasedCultureUrlHelper : IRouteBasedCultureUrlHelper, ITransientDependency +{ + private readonly ICachedApplicationConfigurationClient _configurationClient; + + public RouteBasedCultureUrlHelper(ICachedApplicationConfigurationClient configurationClient) + { + _configurationClient = configurationClient; + } + + public virtual async Task PrependCulturePrefixAsync(string url) + { + var config = await _configurationClient.GetAsync(); + + if (config?.Localization.UseRouteBasedCulture != true) + { + return url; + } + + var currentCulture = CultureInfo.CurrentCulture.Name; + var isKnownCulture = config.Localization.Languages + .Any(l => string.Equals(l.CultureName, currentCulture, StringComparison.OrdinalIgnoreCase)); + + return isKnownCulture ? $"{currentCulture}/{url}" : url; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpCultureMenuItemUrlProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpCultureMenuItemUrlProvider.cs index d475b332b6..1b91ce72e3 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpCultureMenuItemUrlProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Localization/AbpCultureMenuItemUrlProvider.cs @@ -20,33 +20,34 @@ public class AbpCultureMenuItemUrlProvider : IMenuItemUrlProvider, ITransientDep protected IHttpContextAccessor HttpContextAccessor { get; } protected IOptions LocalizationOptions { get; } protected IOptions AbpLocalizationOptions { get; } + protected IMenuItemCulturePrefixHelper MenuItemCulturePrefixHelper { get; } public AbpCultureMenuItemUrlProvider( IHttpContextAccessor httpContextAccessor, IOptions localizationOptions, - IOptions abpLocalizationOptions) + IOptions abpLocalizationOptions, + IMenuItemCulturePrefixHelper menuItemCulturePrefixHelper) { HttpContextAccessor = httpContextAccessor; LocalizationOptions = localizationOptions; AbpLocalizationOptions = abpLocalizationOptions; + MenuItemCulturePrefixHelper = menuItemCulturePrefixHelper; } - public virtual Task HandleAsync(MenuItemUrlProviderContext context) + public virtual async Task HandleAsync(MenuItemUrlProviderContext context) { if (!LocalizationOptions.Value.UseRouteBasedCulture) { - return Task.CompletedTask; + return; } var culture = GetCulture(); if (string.IsNullOrEmpty(culture)) { - return Task.CompletedTask; + return; } - MenuItemCulturePrefixHelper.PrependCulturePrefix(context.Menu, "/" + culture); - - return Task.CompletedTask; + await MenuItemCulturePrefixHelper.PrependCulturePrefixAsync(context.Menu, "/" + culture); } protected virtual string? GetCulture() diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs index e71a5e6b7b..4683c3151d 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/AbpRequestLocalizationMiddleware.cs @@ -54,8 +54,11 @@ public class AbpRequestLocalizationMiddleware : AbpMiddlewareBase, ITransientDep // This cookie is used by AbpCultureMenuItemUrlProvider to determine if the // initial SSR page had a culture prefix, since the Blazor interactive circuit // (/_blazor) does not carry the original route values. + // Note: ComponentTypeMetadata is an internal ASP.NET Core type + // (Microsoft.AspNetCore.Components.Endpoints.ComponentTypeMetadata). + // We match by full type name to avoid false positives from other assemblies. var endpoint = context.GetEndpoint(); - if (endpoint?.Metadata.Any(m => m.GetType().Name == "ComponentTypeMetadata") == true) + if (endpoint?.Metadata.Any(m => m.GetType().FullName == "Microsoft.AspNetCore.Components.Endpoints.ComponentTypeMetadata") == true) { AbpRequestCultureCookieHelper.SetHasRouteCultureCookie( context, requestCultureFeature?.Provider is RouteDataRequestCultureProvider); diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/IMenuItemCulturePrefixHelper.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/IMenuItemCulturePrefixHelper.cs new file mode 100644 index 0000000000..93d9992b3f --- /dev/null +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/IMenuItemCulturePrefixHelper.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.UI.Navigation; + +public interface IMenuItemCulturePrefixHelper +{ + Task PrependCulturePrefixAsync(IHasMenuItems menuWithItems, string prefix); +} diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuItemCulturePrefixHelper.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuItemCulturePrefixHelper.cs index 9bdcfe4c3b..242cf9f679 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuItemCulturePrefixHelper.cs +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuItemCulturePrefixHelper.cs @@ -1,8 +1,17 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + namespace Volo.Abp.UI.Navigation; -public static class MenuItemCulturePrefixHelper +public class MenuItemCulturePrefixHelper : IMenuItemCulturePrefixHelper, ITransientDependency { - public static void PrependCulturePrefix(IHasMenuItems menuWithItems, string prefix) + public virtual Task PrependCulturePrefixAsync(IHasMenuItems menuWithItems, string prefix) + { + PrependCulturePrefix(menuWithItems, prefix); + return Task.CompletedTask; + } + + protected virtual void PrependCulturePrefix(IHasMenuItems menuWithItems, string prefix) { foreach (var item in menuWithItems.Items) { diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor.cs index f8be2fab25..7fe86a6ecc 100644 --- a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor.cs +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme/Themes/Basic/LoginDisplay.razor.cs @@ -1,14 +1,11 @@ -using System; -using System.Globalization; -using System.Linq; +using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Routing; using Microsoft.AspNetCore.Components.WebAssembly.Authentication; using Microsoft.JSInterop; using Volo.Abp.AspNetCore.Components.Web.Security; -using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; -using Volo.Abp.AspNetCore.Mvc.Client; +using Volo.Abp.AspNetCore.Components.WebAssembly; using Volo.Abp.UI.Navigation; namespace Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Themes.Basic; @@ -22,33 +19,18 @@ public partial class LoginDisplay : IDisposable protected ApplicationConfigurationChangedService ApplicationConfigurationChangedService { get; set; } [Inject] - protected ICachedApplicationConfigurationClient ConfigurationClient { get; set; } + protected IRouteBasedCultureUrlHelper CultureUrlHelper { get; set; } protected ApplicationMenu Menu { get; set; } - private ApplicationConfigurationDto _config; + protected string LoginUrl { get; set; } = string.Empty; - protected string LoginUrl => PrependCulturePrefix(AuthenticationOptions.Value.LoginUrl); - - protected string LogoutUrl => PrependCulturePrefix(AuthenticationOptions.Value.LogoutUrl); - - protected string PrependCulturePrefix(string url) - { - if (_config?.Localization.UseRouteBasedCulture != true) - { - return url; - } - - var currentCulture = CultureInfo.CurrentCulture.Name; - var isKnownCulture = _config.Localization.Languages - .Any(l => string.Equals(l.CultureName, currentCulture, StringComparison.OrdinalIgnoreCase)); - - return isKnownCulture ? $"{currentCulture}/{url}" : url; - } + protected string LogoutUrl { get; set; } = string.Empty; protected async override Task OnInitializedAsync() { - _config = await ConfigurationClient.GetAsync(); + LoginUrl = await CultureUrlHelper.PrependCulturePrefixAsync(AuthenticationOptions.Value.LoginUrl); + LogoutUrl = await CultureUrlHelper.PrependCulturePrefixAsync(AuthenticationOptions.Value.LogoutUrl); Menu = await MenuManager.GetAsync(StandardMenus.User); @@ -64,6 +46,8 @@ public partial class LoginDisplay : IDisposable private async void ApplicationConfigurationChanged() { + LoginUrl = await CultureUrlHelper.PrependCulturePrefixAsync(AuthenticationOptions.Value.LoginUrl); + LogoutUrl = await CultureUrlHelper.PrependCulturePrefixAsync(AuthenticationOptions.Value.LogoutUrl); Menu = await MenuManager.GetAsync(StandardMenus.User); await InvokeAsync(StateHasChanged); } @@ -88,7 +72,7 @@ public partial class LoginDisplay : IDisposable } } - private void BeginSignOut() + private async Task BeginSignOut() { if (AbpAspNetCoreComponentsWebOptions.Value.IsBlazorWebApp) {