-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Blazor 404 code re-execution is compatible with OnNavigateAsync
#64034
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0317ecf
d662028
c90449b
64df78f
5ed75df
b8856ad
d444c4a
5f632fe
0f0cf6a
f2fb811
496d430
e97518a
dd17b60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -220,11 +220,14 @@ private void ClearRouteCaches() | |
|
|
||
| internal virtual void Refresh(bool isNavigationIntercepted) | ||
| { | ||
| var providerRouteData = RoutingStateProvider?.RouteData; | ||
| var allowRenderDuringPendingNavigation = TryConsumeAllowRenderDuringPendingNavigation(providerRouteData); | ||
|
|
||
| // If an `OnNavigateAsync` task is currently in progress, then wait | ||
| // for it to complete before rendering. Note: because _previousOnNavigateTask | ||
| // is initialized to a CompletedTask on initialization, this will still | ||
| // allow first-render to complete successfully. | ||
| if (_previousOnNavigateTask.Status != TaskStatus.RanToCompletion) | ||
| if (_previousOnNavigateTask.Status != TaskStatus.RanToCompletion && !allowRenderDuringPendingNavigation) | ||
| { | ||
| if (Navigating != null) | ||
| { | ||
|
Comment on lines
226
to
233
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The comment mentions waiting - where is the actual waiting taking place? |
||
|
|
@@ -239,7 +242,7 @@ internal virtual void Refresh(bool isNavigationIntercepted) | |
| ComponentsActivityHandle activityHandle; | ||
|
|
||
| // In order to avoid routing twice we check for RouteData | ||
| if (RoutingStateProvider?.RouteData is { } endpointRouteData) | ||
| if (providerRouteData is { } endpointRouteData) | ||
| { | ||
| activityHandle = RecordDiagnostics(endpointRouteData.PageType.FullName, endpointRouteData.Template); | ||
|
|
||
|
|
@@ -312,6 +315,17 @@ internal virtual void Refresh(bool isNavigationIntercepted) | |
| _renderHandle.ComponentActivitySource?.StopNavigateActivity(activityHandle, null); | ||
| } | ||
|
|
||
| private static bool TryConsumeAllowRenderDuringPendingNavigation(RouteData? routeData) | ||
| { | ||
| if (routeData?.RouteValues.TryGetValue(ComponentsConstants.AllowRenderDuringPendingNavigationKey, out var value) == true && value is true) | ||
| { | ||
| (routeData.RouteValues as IDictionary<string, object?>)?.Remove(ComponentsConstants.AllowRenderDuringPendingNavigationKey); | ||
| return true; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| private ComponentsActivityHandle RecordDiagnostics(string componentType, string template) | ||
| { | ||
| ComponentsActivityHandle activityHandle = default; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace Components.TestServer.RazorComponents; | ||
|
|
||
| internal static class NavigationCompletionTracker | ||
| { | ||
| internal const string GuardSwitchName = "Components.TestServer.RazorComponents.UseNavigationCompletionGuard"; | ||
|
|
||
| private const string TrackedPathSuffix = "with-lazy-assembly"; | ||
| private static int _isNavigationTracked; | ||
| private static int _isNavigationCompleted; | ||
|
|
||
| public static bool TryGetGuardTask(string? path, out Task guardTask) | ||
| { | ||
| if (!IsGuardEnabledForPath(path)) | ||
| { | ||
| guardTask = Task.CompletedTask; | ||
| return false; | ||
| } | ||
|
|
||
| guardTask = TrackNavigationAsync(); | ||
| return true; | ||
| } | ||
|
|
||
| public static void AssertNavigationCompleted() | ||
| { | ||
| if (Volatile.Read(ref _isNavigationTracked) == 1 && Volatile.Read(ref _isNavigationCompleted) == 0) | ||
| { | ||
| throw new InvalidOperationException("Navigation finished before OnNavigateAsync work completed."); | ||
| } | ||
|
|
||
| Volatile.Write(ref _isNavigationTracked, 0); | ||
| } | ||
|
|
||
| private static bool IsGuardEnabledForPath(string? path) | ||
| { | ||
| if (!AppContext.TryGetSwitch(GuardSwitchName, out var isEnabled) || !isEnabled) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| return path is not null && path.EndsWith(TrackedPathSuffix, StringComparison.OrdinalIgnoreCase); | ||
| } | ||
|
|
||
| private static async Task TrackNavigationAsync() | ||
| { | ||
| Volatile.Write(ref _isNavigationTracked, 1); | ||
| Volatile.Write(ref _isNavigationCompleted, 0); | ||
|
Comment on lines
+52
to
+53
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the volatile reads/writes instead of using a lock for the compound operation? (Here and in |
||
|
|
||
| try | ||
| { | ||
| await Task.Yield(); | ||
| await Task.Delay(TimeSpan.FromMilliseconds(50)).ConfigureAwait(false); | ||
| } | ||
| finally | ||
| { | ||
| Volatile.Write(ref _isNavigationCompleted, 1); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| @page "/routing/with-lazy-assembly" | ||
| @using Components.TestServer.RazorComponents; | ||
|
|
||
| <h1 id="lazy-route-status">Lazy route rendered</h1> | ||
|
|
||
| @code | ||
| { | ||
| protected override void OnInitialized() | ||
| { | ||
| NavigationCompletionTracker.AssertNavigationCompleted(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| namespace Microsoft.AspNetCore.Components; | ||
|
|
||
| internal static class ComponentsConstants | ||
| { | ||
| internal const string AllowRenderDuringPendingNavigationKey = "__BlazorAllowRenderDuringPendingNavigation"; | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we removing the flag here and in
TryConsumeAllowRenderDuringPendingNavigation? Is it important that only the first reader of the value sees it set?