From 93127b39e824181d4f9b1a62b6fc8d10c481e2c8 Mon Sep 17 00:00:00 2001
From: Steve Sanderson <SteveSandersonMS@users.noreply.github.com>
Date: Fri, 14 Dec 2018 17:05:45 +0000
Subject: [PATCH] Overload UriHelper to forceLoad the page (imported from
 Blazor PR 1154) (#4786)

---
 .../src/Services/UriHelper.ts                 |  5 ++--
 .../Services/BrowserUriHelper.cs              |  4 +--
 .../Circuits/RemoteUriHelper.cs               | 10 +++----
 .../Services/IUriHelper.cs                    |  8 ++++++
 .../Services/UriHelperBase.cs                 | 16 ++++++++++--
 .../Tests/RoutingTest.cs                      | 26 ++++++++++++++++++-
 .../BasicTestApp/RouterTest/Links.cshtml      |  6 ++++-
 7 files changed, 60 insertions(+), 15 deletions(-)

diff --git a/src/Components/src/Microsoft.AspNetCore.Components.Browser.JS/src/Services/UriHelper.ts b/src/Components/src/Microsoft.AspNetCore.Components.Browser.JS/src/Services/UriHelper.ts
index 169916d8adc..d867adab0e7 100644
--- a/src/Components/src/Microsoft.AspNetCore.Components.Browser.JS/src/Services/UriHelper.ts
+++ b/src/Components/src/Microsoft.AspNetCore.Components.Browser.JS/src/Services/UriHelper.ts
@@ -41,9 +41,10 @@ function enableNavigationInterception(assemblyName: string, functionName: string
   window.addEventListener('popstate', handleInternalNavigation);
 }
 
-export function navigateTo(uri: string) {
+export function navigateTo(uri: string, forceLoad: boolean) {
   const absoluteUri = toAbsoluteUri(uri);
-  if (isWithinBaseUriSpace(absoluteUri)) {
+
+  if (!forceLoad && isWithinBaseUriSpace(absoluteUri)) {
     performInternalNavigation(absoluteUri);
   } else {
     location.href = uri;
diff --git a/src/Components/src/Microsoft.AspNetCore.Components.Browser/Services/BrowserUriHelper.cs b/src/Components/src/Microsoft.AspNetCore.Components.Browser/Services/BrowserUriHelper.cs
index 8cd50f9c545..cd4337b4f81 100644
--- a/src/Components/src/Microsoft.AspNetCore.Components.Browser/Services/BrowserUriHelper.cs
+++ b/src/Components/src/Microsoft.AspNetCore.Components.Browser/Services/BrowserUriHelper.cs
@@ -47,14 +47,14 @@ namespace Microsoft.AspNetCore.Components.Browser.Services
         }
 
         /// <inheritdoc />
-        protected override void NavigateToCore(string uri)
+        protected override void NavigateToCore(string uri, bool forceLoad)
         {
             if (uri == null)
             {
                 throw new ArgumentNullException(nameof(uri));
             }
 
-            ((IJSInProcessRuntime)JSRuntime.Current).Invoke<object>(Interop.NavigateTo, uri);
+            ((IJSInProcessRuntime)JSRuntime.Current).Invoke<object>(Interop.NavigateTo, uri, forceLoad);
         }
 
         /// <summary>
diff --git a/src/Components/src/Microsoft.AspNetCore.Components.Server/Circuits/RemoteUriHelper.cs b/src/Components/src/Microsoft.AspNetCore.Components.Server/Circuits/RemoteUriHelper.cs
index 9b7eaa7e844..85149fcf690 100644
--- a/src/Components/src/Microsoft.AspNetCore.Components.Server/Circuits/RemoteUriHelper.cs
+++ b/src/Components/src/Microsoft.AspNetCore.Components.Server/Circuits/RemoteUriHelper.cs
@@ -61,14 +61,10 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
             uriHelper.TriggerOnLocationChanged();
         }
 
-        /// <summary>
-        /// Navigates to the specified URI.
-        /// </summary>
-        /// <param name="uri">The destination URI. This can be absolute, or relative to the base URI
-        /// (as returned by <see cref="IUriHelper.GetBaseUri"/>).</param>
-        protected override void NavigateToCore(string uri)
+        /// <inheritdoc />
+        protected override void NavigateToCore(string uri, bool forceLoad)
         {
-            _jsRuntime.InvokeAsync<object>(Interop.NavigateTo, uri);
+            _jsRuntime.InvokeAsync<object>(Interop.NavigateTo, uri, forceLoad);
         }
     }
 }
diff --git a/src/Components/src/Microsoft.AspNetCore.Components/Services/IUriHelper.cs b/src/Components/src/Microsoft.AspNetCore.Components/Services/IUriHelper.cs
index e8deebaf346..b228570ffc3 100644
--- a/src/Components/src/Microsoft.AspNetCore.Components/Services/IUriHelper.cs
+++ b/src/Components/src/Microsoft.AspNetCore.Components/Services/IUriHelper.cs
@@ -51,5 +51,13 @@ namespace Microsoft.AspNetCore.Components.Services
         /// <param name="uri">The destination URI. This can be absolute, or relative to the base URI
         /// (as returned by <see cref="GetBaseUri"/>).</param>
         void NavigateTo(string uri);
+
+        /// <summary>
+        /// Navigates to the specified URI.
+        /// </summary>
+        /// <param name="uri">The destination URI. This can be absolute, or relative to the base URI
+        /// (as returned by <see cref="GetBaseUri"/>).</param>
+        /// <param name="forceLoad">If true, bypasses client-side routing and forces the browser to load the new page from the server, whether or not the URI would normally be handled by the client-side router.</param>
+        void NavigateTo(string uri, bool forceLoad);
     }
 }
diff --git a/src/Components/src/Microsoft.AspNetCore.Components/Services/UriHelperBase.cs b/src/Components/src/Microsoft.AspNetCore.Components/Services/UriHelperBase.cs
index 78f2c2fa9fa..aebf73aad55 100644
--- a/src/Components/src/Microsoft.AspNetCore.Components/Services/UriHelperBase.cs
+++ b/src/Components/src/Microsoft.AspNetCore.Components/Services/UriHelperBase.cs
@@ -45,9 +45,20 @@ namespace Microsoft.AspNetCore.Components.Services
         /// <param name="uri">The destination URI. This can be absolute, or relative to the base URI
         /// (as returned by <see cref="GetBaseUri"/>).</param>
         public void NavigateTo(string uri)
+        {
+            NavigateTo(uri, forceLoad: false);
+        }
+
+        /// <summary>
+        /// Navigates to the specified URI.
+        /// </summary>
+        /// <param name="uri">The destination URI. This can be absolute, or relative to the base URI
+        /// (as returned by <see cref="GetBaseUri"/>).</param>
+        /// <param name="forceLoad">If true, bypasses client-side routing and forces the browser to load the new page from the server, whether or not the URI would normally be handled by the client-side router.</param>
+        public void NavigateTo(string uri, bool forceLoad)
         {
             EnsureInitialized();
-            NavigateToCore(uri);
+            NavigateToCore(uri, forceLoad);
         }
 
         /// <summary>
@@ -55,7 +66,8 @@ namespace Microsoft.AspNetCore.Components.Services
         /// </summary>
         /// <param name="uri">The destination URI. This can be absolute, or relative to the base URI
         /// (as returned by <see cref="GetBaseUri"/>).</param>
-        protected abstract void NavigateToCore(string uri);
+        /// <param name="forceLoad">If true, bypasses client-side routing and forces the browser to load the new page from the server, whether or not the URI would normally be handled by the client-side router.</param>
+        protected abstract void NavigateToCore(string uri, bool forceLoad);
 
         /// <summary>
         /// Called to initialize BaseURI and current URI before those values the first time.
diff --git a/src/Components/test/Microsoft.AspNetCore.Components.E2ETest/Tests/RoutingTest.cs b/src/Components/test/Microsoft.AspNetCore.Components.E2ETest/Tests/RoutingTest.cs
index f7d0f69e633..82991d47cc4 100644
--- a/src/Components/test/Microsoft.AspNetCore.Components.E2ETest/Tests/RoutingTest.cs
+++ b/src/Components/test/Microsoft.AspNetCore.Components.E2ETest/Tests/RoutingTest.cs
@@ -268,9 +268,33 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
             SetUrlViaPushState("/");
 
             var app = MountTestComponent<TestRouter>();
-            app.FindElement(By.TagName("button")).Click();
+            var testSelector = WaitUntilTestSelectorReady();
+
+            app.FindElement(By.Id("do-navigation")).Click();
+            WaitAssert.True(() => Browser.Url.EndsWith("/Other"));
             WaitAssert.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text);
             AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)");
+
+            // Because this was client-side navigation, we didn't lose the state in the test selector
+            Assert.Equal(typeof(TestRouter).FullName, testSelector.SelectedOption.GetAttribute("value"));
+        }
+
+        [Fact]
+        public void CanNavigateProgrammaticallyWithForceLoad()
+        {
+            SetUrlViaPushState("/");
+
+            var app = MountTestComponent<TestRouter>();
+            var testSelector = WaitUntilTestSelectorReady();
+
+            app.FindElement(By.Id("do-navigation-forced")).Click();
+            WaitAssert.True(() => Browser.Url.EndsWith("/Other"));
+
+            // Because this was a full-page load, our element references should no longer be valid
+            Assert.Throws<StaleElementReferenceException>(() =>
+            {
+                testSelector.SelectedOption.GetAttribute("value");
+            });
         }
 
         [Fact]
diff --git a/src/Components/test/testapps/BasicTestApp/RouterTest/Links.cshtml b/src/Components/test/testapps/BasicTestApp/RouterTest/Links.cshtml
index 10c401898f7..1858b7e8327 100644
--- a/src/Components/test/testapps/BasicTestApp/RouterTest/Links.cshtml
+++ b/src/Components/test/testapps/BasicTestApp/RouterTest/Links.cshtml
@@ -13,10 +13,14 @@
     <li><NavLink href="/subdir/WithParameters/Name/Abc/LastName/McDef">With parameters</NavLink></li>
 </ul>
 
-<button onclick=@(x => uriHelper.NavigateTo("Other"))>
+<button id="do-navigation" onclick=@(x => uriHelper.NavigateTo("Other"))>
     Programmatic navigation
 </button>
 
+<button id="do-navigation-forced" onclick=@(x => uriHelper.NavigateTo("Other", true))>
+    Programmatic navigation with force-load
+</button>
+
 <a id="anchor-with-no-href">
     Anchor tag with no href attribute
 </a>
-- 
GitLab