diff --git a/src/Components/Components/src/Routing/Router.cs b/src/Components/Components/src/Routing/Router.cs index ecb69fe2cf63..2afa52e99a40 100644 --- a/src/Components/Components/src/Routing/Router.cs +++ b/src/Components/Components/src/Routing/Router.cs @@ -220,17 +220,20 @@ private void ClearRouteCaches() internal virtual void Refresh(bool isNavigationIntercepted) { + // endpointRouterData is populated only in navigations that passed through SSR, including re-executions + var endpointRouteData = RoutingStateProvider?.RouteData; + // 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 && endpointRouteData is null) { if (Navigating != null) { _renderHandle.Render(Navigating); + return; } - return; } var relativePath = NavigationManager.ToBaseRelativePath(_locationAbsolute.AsSpan()); @@ -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 (endpointRouteData is not null) { activityHandle = RecordDiagnostics(endpointRouteData.PageType.FullName, endpointRouteData.Template); diff --git a/src/Components/test/E2ETest/ServerRenderingTests/NoInteractivityTest.cs b/src/Components/test/E2ETest/ServerRenderingTests/NoInteractivityTest.cs index a6421eb94689..aefef8da43ca 100644 --- a/src/Components/test/E2ETest/ServerRenderingTests/NoInteractivityTest.cs +++ b/src/Components/test/E2ETest/ServerRenderingTests/NoInteractivityTest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Net.Http; +using System; using Components.TestServer.RazorComponents; using Microsoft.AspNetCore.Components.E2ETest; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; @@ -125,6 +126,25 @@ public void BrowserNavigationToNotExistingPath_ReExecutesTo404(bool streaming) AssertReExecutionPageRendered(); } + [Fact] + public void BrowserNavigationToNotExistingPath_WithOnNavigateAsync_ReExecutesTo404() + { + AppContext.SetSwitch("Microsoft.AspNetCore.Components.Endpoints.NavigationManager.DisableThrowNavigationException", isEnabled: true); + + // using query for controlling router parameters does not work in re-execution scenario, we have to rely on other communication channel + const string useOnNavigateAsyncSwitch = "Components.TestServer.RazorComponents.UseOnNavigateAsync"; + AppContext.SetSwitch(useOnNavigateAsyncSwitch, true); + try + { + Navigate($"{ServerPathBase}/reexecution/not-existing-page"); + AssertReExecutionPageRendered(); + } + finally + { + AppContext.SetSwitch(useOnNavigateAsyncSwitch, false); + } + } + private void AssertReExecutionPageRendered() => Browser.Equal("Welcome On Page Re-executed After Not Found Event", () => Browser.Exists(By.Id("test-info")).Text); diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/App.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/App.razor index f547144c8fdd..1c6c272bf8ea 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponents/App.razor +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/App.razor @@ -3,6 +3,10 @@ @using Components.WasmMinimal.Pages.NotFound @using TestContentPackage.NotFound @using Components.TestServer.RazorComponents +@using Microsoft.AspNetCore.Components +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using System.Threading.Tasks @code { [Parameter] @@ -17,8 +21,14 @@ [SupplyParameterFromQuery(Name = "appSetsEventArgsPath")] public bool AppSetsEventArgsPath { get; set; } + [Parameter] + [SupplyParameterFromQuery(Name = "useOnNavigateAsync")] + public string? UseOnNavigateAsync { get; set; } + private Type? NotFoundPageType { get; set; } private NavigationManager _navigationManager = default!; + private bool ShouldDelayOnNavigateAsync + => bool.TryParse(UseOnNavigateAsync, out var result) && result; [Inject] private NavigationManager NavigationManager @@ -70,6 +80,21 @@ _navigationManager.OnNotFound -= OnNotFoundEvent; } } + + private Task HandleOnNavigateAsync(NavigationContext args) + { + if (!ShouldDelayOnNavigateAsync) + { + return Task.CompletedTask; + } + + return PerformOnNavigateAsyncWork(); + } + + private async Task PerformOnNavigateAsyncWork() + { + await Task.Yield(); + } } @@ -93,7 +118,7 @@ { @if (NotFoundPageType is not null) { - + @@ -102,7 +127,7 @@ } else { - +