From d0731c337d1fa73eb65740666ca712a72b472f10 Mon Sep 17 00:00:00 2001
From: Steve Sanderson <SteveSandersonMS@users.noreply.github.com>
Date: Fri, 16 Mar 2018 11:19:09 +0000
Subject: [PATCH] Add IUriHelper.NavigateTo

---
 .../src/Services/UriHelper.ts                 | 19 ++++++++++++++++---
 .../Services/BrowserUriHelper.cs              | 11 +++++++++++
 .../Services/IUriHelper.cs                    |  9 ++++++++-
 .../Tests/RoutingTest.cs                      | 10 ++++++++++
 .../BasicTestApp/RouterTest/Links.cshtml      |  8 +++++++-
 5 files changed, 52 insertions(+), 5 deletions(-)

diff --git a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Services/UriHelper.ts b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Services/UriHelper.ts
index 53442dae621..8e77f1a6b5d 100644
--- a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Services/UriHelper.ts
+++ b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Services/UriHelper.ts
@@ -1,6 +1,6 @@
 import { registerFunction } from '../Interop/RegisteredFunction';
 import { platform } from '../Environment';
-import { MethodHandle } from '../Platform/Platform';
+import { MethodHandle, System_String } from '../Platform/Platform';
 const registeredFunctionPrefix = 'Microsoft.AspNetCore.Blazor.Browser.Services.BrowserUriHelper';
 let notifyLocationChangedMethod: MethodHandle;
 let hasRegisteredEventListeners = false;
@@ -24,8 +24,7 @@ registerFunction(`${registeredFunctionPrefix}.enableNavigationInteception`, () =
       const href = anchorTarget.getAttribute('href');
       if (isWithinBaseUriSpace(toAbsoluteUri(href))) {
         event.preventDefault();
-        history.pushState(null, /* ignored title */ '', href);
-        handleInternalNavigation();
+        performInternalNavigation(href);
       }
     }
   });
@@ -33,6 +32,20 @@ registerFunction(`${registeredFunctionPrefix}.enableNavigationInteception`, () =
   window.addEventListener('popstate', handleInternalNavigation);
 });
 
+registerFunction(`${registeredFunctionPrefix}.navigateTo`, (uriDotNetString: System_String) => {
+  const href = platform.toJavaScriptString(uriDotNetString);
+  if (isWithinBaseUriSpace(toAbsoluteUri(href))) {
+    performInternalNavigation(href);
+  } else {
+    location.href = href;
+  }
+});
+
+function performInternalNavigation(href: string) {
+  history.pushState(null, /* ignored title */ '', href);
+  handleInternalNavigation();
+}
+
 function handleInternalNavigation() {
   if (!notifyLocationChangedMethod) {
     notifyLocationChangedMethod = platform.findMethod(
diff --git a/src/Microsoft.AspNetCore.Blazor.Browser/Services/BrowserUriHelper.cs b/src/Microsoft.AspNetCore.Blazor.Browser/Services/BrowserUriHelper.cs
index 28cf8e497fb..171679febb9 100644
--- a/src/Microsoft.AspNetCore.Blazor.Browser/Services/BrowserUriHelper.cs
+++ b/src/Microsoft.AspNetCore.Blazor.Browser/Services/BrowserUriHelper.cs
@@ -104,6 +104,17 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services
             throw new ArgumentException($"The URI '{absoluteUri}' is not contained by the base URI '{baseUriPrefix}'.");
         }
 
+        /// <inheritdoc />
+        public void NavigateTo(string uri)
+        {
+            if (uri == null)
+            {
+                throw new ArgumentNullException(nameof(uri));
+            }
+
+            RegisteredFunction.InvokeUnmarshalled<object>($"{_functionPrefix}.navigateTo", uri);
+        }
+
         private static void EnsureBaseUriPopulated()
         {
             // The <base href> is fixed for the lifetime of the page, so just cache it
diff --git a/src/Microsoft.AspNetCore.Blazor/Services/IUriHelper.cs b/src/Microsoft.AspNetCore.Blazor/Services/IUriHelper.cs
index 12e8fd0dade..472b7956f56 100644
--- a/src/Microsoft.AspNetCore.Blazor/Services/IUriHelper.cs
+++ b/src/Microsoft.AspNetCore.Blazor/Services/IUriHelper.cs
@@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Blazor.Services
         /// <summary>
         /// Gets the current absolute URI.
         /// </summary>
-        /// <returns>The browser's current absolute URI.</returns>
+        /// <returns>The current absolute URI.</returns>
         string GetAbsoluteUri();
 
         /// <summary>
@@ -44,5 +44,12 @@ namespace Microsoft.AspNetCore.Blazor.Services
         /// <param name="absoluteUri">An absolute URI that is within the space of the base URI prefix.</param>
         /// <returns>A relative URI path.</returns>
         string ToBaseRelativePath(string baseUriPrefix, string locationAbsolute);
+
+        /// <summary>
+        /// Navigates to the specified URI.
+        /// </summary>
+        /// <param name="uri">The destination URI. This can be absolute, or relative to the base URI
+        /// prefix (as returned by <see cref="GetBaseUriPrefix"/>).</param>
+        void NavigateTo(string uri);
     }
 }
diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/RoutingTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/RoutingTest.cs
index 8970de915cd..9b6cfd15768 100644
--- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/RoutingTest.cs
+++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/RoutingTest.cs
@@ -110,6 +110,16 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
             Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
         }
 
+        [Fact]
+        public void CanNavigateProgrammatically()
+        {
+            SetUrlViaPushState($"{ServerPathBase}/RouterTest/");
+
+            var app = MountTestComponent<TestRouter>();
+            app.FindElement(By.TagName("button")).Click();
+            Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
+        }
+
         public void Dispose()
         {
             // Clear any existing state
diff --git a/test/testapps/BasicTestApp/RouterTest/Links.cshtml b/test/testapps/BasicTestApp/RouterTest/Links.cshtml
index e7bbca5d120..9490da17253 100644
--- a/test/testapps/BasicTestApp/RouterTest/Links.cshtml
+++ b/test/testapps/BasicTestApp/RouterTest/Links.cshtml
@@ -1,4 +1,6 @@
-<ul>
+@inject Microsoft.AspNetCore.Blazor.Services.IUriHelper uriHelper
+
+<ul>
     <li><a href="/subdir/RouterTest/">Default</a></li>
     <li><a href="/subdir/RouterTest/?abc=123">Default with query</a></li>
     <li><a href="/subdir/RouterTest/#blah">Default with hash</a></li>
@@ -7,3 +9,7 @@
     <li><a href="/subdir/RouterTest/Other?abc=123">Other with query</a></li>
     <li><a href="/subdir/RouterTest/Other#blah">Other with hash</a></li>
 </ul>
+
+<button onclick=@{ uriHelper.NavigateTo("RouterTest/Other"); }>
+    Programmatic navigation
+</button>
-- 
GitLab