Skip to content

Commit 0e255c5

Browse files
authored
[Blazor] Adds support state in NavigationManager (#42534)
* Exposes the history state from the page to the navigation manager. * Applications can pass in state during navigations with `NavigationManager.NavigateTo("url", new NavigationOptions{ HistoryEntryState = "entryState" })`. * Passing state is limited to internal navigations as defined by the underlying History API.
1 parent be68b79 commit 0e255c5

File tree

23 files changed

+153
-31
lines changed

23 files changed

+153
-31
lines changed

src/Components/Components/src/NavigationManager.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ public event EventHandler<LocationChangedEventArgs> LocationChanged
3737

3838
// The URI. Always represented an absolute URI.
3939
private string? _uri;
40-
4140
private bool _isInitialized;
4241

4342
/// <summary>
@@ -85,6 +84,14 @@ protected set
8584
}
8685
}
8786

87+
/// <summary>
88+
/// Gets or sets the state associated with the current navigation.
89+
/// </summary>
90+
/// <remarks>
91+
/// Setting <see cref="HistoryEntryState" /> will not trigger the <see cref="LocationChanged" /> event.
92+
/// </remarks>
93+
public string? HistoryEntryState { get; protected set; }
94+
8895
/// <summary>
8996
/// Navigates to the specified URI.
9097
/// </summary>
@@ -254,7 +261,12 @@ protected void NotifyLocationChanged(bool isInterceptedLink)
254261
{
255262
try
256263
{
257-
_locationChanged?.Invoke(this, new LocationChangedEventArgs(_uri!, isInterceptedLink));
264+
_locationChanged?.Invoke(
265+
this,
266+
new LocationChangedEventArgs(_uri!, isInterceptedLink)
267+
{
268+
HistoryEntryState = HistoryEntryState
269+
});
258270
}
259271
catch (Exception ex)
260272
{

src/Components/Components/src/NavigationOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,9 @@ public readonly struct NavigationOptions
1818
/// If false, appends the new entry to the history stack.
1919
/// </summary>
2020
public bool ReplaceHistoryEntry { get; init; }
21+
22+
/// <summary>
23+
/// Gets or sets the state to append to the history entry.
24+
/// </summary>
25+
public string? HistoryEntryState { get; init; }
2126
}

src/Components/Components/src/PublicAPI.Unshipped.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
#nullable enable
2+
Microsoft.AspNetCore.Components.NavigationManager.HistoryEntryState.get -> string?
3+
Microsoft.AspNetCore.Components.NavigationManager.HistoryEntryState.set -> void
4+
Microsoft.AspNetCore.Components.NavigationOptions.HistoryEntryState.get -> string?
5+
Microsoft.AspNetCore.Components.NavigationOptions.HistoryEntryState.init -> void
26
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddContent(int sequence, Microsoft.AspNetCore.Components.MarkupString? markupContent) -> void
7+
Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs.HistoryEntryState.get -> string?
38
static Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.CreateInferredEventCallback<T>(object! receiver, Microsoft.AspNetCore.Components.EventCallback<T> callback, T value) -> Microsoft.AspNetCore.Components.EventCallback<T>
49
static Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.InvokeAsynchronousDelegate(System.Action! callback) -> System.Threading.Tasks.Task!
510
static Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.InvokeAsynchronousDelegate(System.Func<System.Threading.Tasks.Task!>! callback) -> System.Threading.Tasks.Task!

src/Components/Components/src/Routing/LocationChangedEventArgs.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,9 @@ public LocationChangedEventArgs(string location, bool isNavigationIntercepted)
2828
/// Gets a value that determines if navigation for the link was intercepted.
2929
/// </summary>
3030
public bool IsNavigationIntercepted { get; }
31+
32+
/// <summary>
33+
/// Gets the state associated with the current history entry.
34+
/// </summary>
35+
public string? HistoryEntryState { get; internal init; }
3136
}

src/Components/Components/test/Routing/RouterTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ internal class TestNavigationManager : NavigationManager
203203
public TestNavigationManager() =>
204204
Initialize("https://www.example.com/subdir/", "https://www.example.com/subdir/jan");
205205

206-
public void NotifyLocationChanged(string uri, bool intercepted)
206+
public void NotifyLocationChanged(string uri, bool intercepted, string state = null)
207207
{
208208
Uri = uri;
209209
NotifyLocationChanged(intercepted);

src/Components/Server/src/Circuits/CircuitHost.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ public async Task<DotNetStreamReference> TryClaimPendingStream(long streamId)
493493

494494
// OnLocationChangedAsync is used in a fire-and-forget context, so it's responsible for its own
495495
// error handling.
496-
public async Task OnLocationChangedAsync(string uri, bool intercepted)
496+
public async Task OnLocationChangedAsync(string uri, string state, bool intercepted)
497497
{
498498
AssertInitialized();
499499
AssertNotDisposed();
@@ -504,7 +504,7 @@ await Renderer.Dispatcher.InvokeAsync(() =>
504504
{
505505
Log.LocationChange(_logger, uri, CircuitId);
506506
var navigationManager = (RemoteNavigationManager)Services.GetRequiredService<NavigationManager>();
507-
navigationManager.NotifyLocationChanged(uri, intercepted);
507+
navigationManager.NotifyLocationChanged(uri, state, intercepted);
508508
Log.LocationChangeSucceeded(_logger, uri, CircuitId);
509509
});
510510
}

src/Components/Server/src/Circuits/RemoteNavigationManager.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,12 @@ public void AttachJsRuntime(IJSRuntime jsRuntime)
5656
_jsRuntime = jsRuntime;
5757
}
5858

59-
public void NotifyLocationChanged(string uri, bool intercepted)
59+
public void NotifyLocationChanged(string uri, string state, bool intercepted)
6060
{
6161
Log.ReceivedLocationChangedNotification(_logger, uri, intercepted);
6262

6363
Uri = uri;
64+
HistoryEntryState = state;
6465
NotifyLocationChanged(intercepted);
6566
}
6667

src/Components/Server/src/ComponentHub.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,15 +285,15 @@ public async ValueTask OnRenderCompleted(long renderId, string errorMessageOrNul
285285
_ = circuitHost.OnRenderCompletedAsync(renderId, errorMessageOrNull);
286286
}
287287

288-
public async ValueTask OnLocationChanged(string uri, bool intercepted)
288+
public async ValueTask OnLocationChanged(string uri, string? state, bool intercepted)
289289
{
290290
var circuitHost = await GetActiveCircuitAsync();
291291
if (circuitHost == null)
292292
{
293293
return;
294294
}
295295

296-
_ = circuitHost.OnLocationChangedAsync(uri, intercepted);
296+
_ = circuitHost.OnLocationChangedAsync(uri, state, intercepted);
297297
}
298298

299299
// We store the CircuitHost through a *handle* here because Context.Items is tied to the lifetime

src/Components/Server/test/Circuits/ComponentHubTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public async Task CannotInvokeOnLocationChangedBeforeInitialization()
7979
{
8080
var (mockClientProxy, hub) = InitializeComponentHub();
8181

82-
await hub.OnLocationChanged("https://localhost:5000/subdir/page", false);
82+
await hub.OnLocationChanged("https://localhost:5000/subdir/page", null, false);
8383

8484
var errorMessage = "Circuit not initialized.";
8585
mockClientProxy.Verify(m => m.SendCoreAsync("JS.Error", new[] { errorMessage }, It.IsAny<CancellationToken>()), Times.Once());

src/Components/Web.JS/dist/Release/blazor.server.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)