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)
{