From eb2f2d9856c6febf41d361a08c2ede792e00d972 Mon Sep 17 00:00:00 2001 From: Yaser Moradi Date: Tue, 15 Oct 2024 20:00:57 +0200 Subject: [PATCH] fix(templates): resolve issues of universal link in MAUI client of Boilerplate #8903 (#8904) --- .../Components/AppInitializer.cs | 6 +- .../Components/Layout/RootLayout.razor.cs | 2 +- .../Extensions/NavigationManagerExtensions.cs | 68 ++----------------- .../Boilerplate.Client.Core/Routes.razor.cs | 8 +++ .../Services/CultureService.cs | 2 +- .../Client/Boilerplate.Client.Web/Program.cs | 2 +- .../src/Shared/Extensions/UriExtensions.cs | 68 +++++++++++++++++++ 7 files changed, 87 insertions(+), 69 deletions(-) create mode 100644 src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Extensions/UriExtensions.cs diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppInitializer.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppInitializer.cs index 03bfd50edb..ccd19bfc3e 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppInitializer.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/AppInitializer.cs @@ -22,6 +22,7 @@ public partial class AppInitializer : AppComponentBase [AutoInject] private IStorageService storageService = default!; [AutoInject] private CultureInfoManager cultureInfoManager = default!; [AutoInject] private ILogger authLogger = default!; + [AutoInject] private NavigationManager navigationManager = default!; protected async override Task OnInitAsync() { @@ -33,8 +34,9 @@ protected async override Task OnInitAsync() { if (CultureInfoManager.MultilingualEnabled) { - cultureInfoManager.SetCurrentCulture(await storageService.GetItem("Culture") ?? // 1- User settings - CultureInfo.CurrentUICulture.Name); // 2- OS settings + cultureInfoManager.SetCurrentCulture(new Uri(navigationManager.Uri).GetCulture() ?? // 1- Culture query string OR Route data request culture + await storageService.GetItem("Culture") ?? // 2- User settings + CultureInfo.CurrentUICulture.Name); // 3- OS settings } await SetupBodyClasses(); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootLayout.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootLayout.razor.cs index bfa7fa7078..ae0c352dcf 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootLayout.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/RootLayout.razor.cs @@ -106,7 +106,7 @@ private void SetCurrentDir() private void SetCurrentUrl() { - var path = navigationManager.GetPath(); + var path = navigationManager.GetUriPath(); currentUrl = Urls.All.SingleOrDefault(pageUrl => { diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/NavigationManagerExtensions.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/NavigationManagerExtensions.cs index fd4704e55b..6b7e621f42 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/NavigationManagerExtensions.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/NavigationManagerExtensions.cs @@ -1,74 +1,14 @@ -using System.Web; - -namespace Microsoft.AspNetCore.Components; +namespace Microsoft.AspNetCore.Components; public static partial class NavigationManagerExtensions { public static string GetUriWithoutQueryParameter(this NavigationManager navigationManager, string key) { - var url = navigationManager.Uri; - - var uri = new Uri(url); - - // this gets all the query string key value pairs as a collection - var newQueryString = HttpUtility.ParseQueryString(uri.Query); - - // this removes the key if exists - newQueryString.Remove(key); - - // this gets the page path from root without QueryString - string pagePathWithoutQueryString = uri.GetLeftPart(UriPartial.Path); - - return newQueryString.Count > 0 - ? string.Format("{0}?{1}", pagePathWithoutQueryString, newQueryString) - : pagePathWithoutQueryString; - } - - /// - /// Reads culture from either route segment or query string. - /// https://adminpanel.bitpaltform.dev/en-US/categories - /// https://adminpanel.bitpaltform.dev/categories?culture=en-US - /// - public static string? GetCultureFromUri(this NavigationManager navigationManager) - { - var uri = new Uri(navigationManager.Uri); - - var culture = HttpUtility.ParseQueryString(uri.Query)["culture"]; - - if (string.IsNullOrEmpty(culture) is false) - return culture; - - foreach (var segment in uri.Segments.Take(2)) - { - var segmentValue = segment.Trim('/'); - if (CultureInfoManager.SupportedCultures.Any(sc => string.Equals(sc.Culture.Name, segmentValue, StringComparison.InvariantCultureIgnoreCase))) - { - return segmentValue; - } - } - - return null; - } - - public static string GetUriWithoutCulture(this NavigationManager navigationManager) - { - var uri = navigationManager.GetUriWithoutQueryParameter("culture"); - - var culture = navigationManager.GetCultureFromUri(); - - if (string.IsNullOrEmpty(culture) is false) - { - uri = uri - .Replace($"{culture}/", string.Empty) - .Replace(culture, string.Empty); - } - - return uri; + return new Uri(navigationManager.Uri).GetUrlWithoutQueryParameter(key); } - public static string GetPath(this NavigationManager navigationManager) + public static string GetUriPath(this NavigationManager navigationManager) { - var uriBuilder = new UriBuilder(navigationManager.GetUriWithoutCulture()) { Query = string.Empty, Fragment = string.Empty }; - return uriBuilder.Path; + return new Uri(navigationManager.Uri).GetPath(); } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Routes.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Routes.razor.cs index e6cdbad4b5..3ace7d082d 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Routes.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Routes.razor.cs @@ -15,6 +15,14 @@ await Task.Run(async () => } }); + if (CultureInfoManager.MultilingualEnabled && forceLoad == false) + { + var currentCulture = CultureInfo.CurrentUICulture.Name; + var uri = new Uri(url); + var urlCulture = uri.GetCulture(); + forceLoad = urlCulture is not null && currentCulture != urlCulture; + } + universalLinksNavigationManager!.NavigateTo(url, forceLoad, replace); } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/CultureService.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/CultureService.cs index 933d4ce65a..9950a80c62 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/CultureService.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Services/CultureService.cs @@ -27,6 +27,6 @@ await cookie.Set(new() }); } - navigationManager.NavigateTo(navigationManager.GetUriWithoutCulture(), forceLoad: true, replace: true); + navigationManager.NavigateTo(new Uri(navigationManager.Uri).GetUrlWithoutCulture(), forceLoad: true, replace: true); } } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Program.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Program.cs index 397c731009..d35f930b83 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Program.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Program.cs @@ -45,7 +45,7 @@ public static async Task Main(string[] args) var navigationManager = host.Services.GetRequiredService(); - var culture = navigationManager.GetCultureFromUri() ?? // 1- Culture query string OR Route data request culture + var culture = new Uri(navigationManager.Uri).GetCulture() ?? // 1- Culture query string OR Route data request culture cultureCookie ?? // 2- User settings CultureInfo.CurrentUICulture.Name; // 3- OS/Browser settings diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Extensions/UriExtensions.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Extensions/UriExtensions.cs new file mode 100644 index 0000000000..ff5e466448 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Shared/Extensions/UriExtensions.cs @@ -0,0 +1,68 @@ +using System.Web; + +namespace System; + +public static partial class UriExtensions +{ + public static string GetUrlWithoutQueryParameter(this Uri uri, string key) + { + // this gets all the query string key value pairs as a collection + var newQueryString = HttpUtility.ParseQueryString(uri.Query); + + // this removes the key if exists + newQueryString.Remove(key); + + // this gets the page path from root without QueryString + string pagePathWithoutQueryString = uri.GetLeftPart(UriPartial.Path); + + return newQueryString.Count > 0 + ? string.Format("{0}?{1}", pagePathWithoutQueryString, newQueryString) + : pagePathWithoutQueryString; + } + + /// + /// Reads culture from either route segment or query string. + /// https://adminpanel.bitpaltform.dev/en-US/categories + /// https://adminpanel.bitpaltform.dev/categories?culture=en-US + /// + public static string? GetCulture(this Uri uri) + { + var culture = HttpUtility.ParseQueryString(uri.Query)["culture"]; + + if (string.IsNullOrEmpty(culture) is false) + return culture; + + foreach (var segment in uri.Segments.Take(2)) + { + var segmentValue = segment.Trim('/'); + if (CultureInfoManager.SupportedCultures.Any(sc => string.Equals(sc.Culture.Name, segmentValue, StringComparison.InvariantCultureIgnoreCase))) + { + return segmentValue; + } + } + + return null; + } + + public static string GetUrlWithoutCulture(this Uri uri) + { + uri = new Uri(uri.GetUrlWithoutQueryParameter("culture")); + + var culture = uri.GetCulture(); + + if (string.IsNullOrEmpty(culture) is false) + { + uri = new Uri(uri.ToString() + .Replace($"{culture}/", string.Empty) + .Replace(culture, string.Empty)); + } + + return uri.ToString(); + } + + public static string GetPath(this Uri uri) + { + var uriBuilder = new UriBuilder(uri.GetUrlWithoutCulture()) { Query = string.Empty, Fragment = string.Empty }; + return uriBuilder.Path; + } +}