Don't render route component if OnNavigateAsync task in-progress (#24225)
This commit is contained in:
parent
5e49fc336e
commit
f88034902a
|
|
@ -152,6 +152,19 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
|
|
||||||
internal virtual void Refresh(bool isNavigationIntercepted)
|
internal virtual void Refresh(bool isNavigationIntercepted)
|
||||||
{
|
{
|
||||||
|
// 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 (Navigating != null)
|
||||||
|
{
|
||||||
|
_renderHandle.Render(Navigating);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
RefreshRouteTable();
|
RefreshRouteTable();
|
||||||
|
|
||||||
var locationPath = NavigationManager.ToBaseRelativePath(_locationAbsolute);
|
var locationPath = NavigationManager.ToBaseRelativePath(_locationAbsolute);
|
||||||
|
|
@ -248,19 +261,15 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
var previousTask = _previousOnNavigateTask;
|
var previousTask = _previousOnNavigateTask;
|
||||||
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
_previousOnNavigateTask = tcs.Task;
|
_previousOnNavigateTask = tcs.Task;
|
||||||
try
|
|
||||||
|
// And pass an indicator for the previous task to the currently running one.
|
||||||
|
var shouldRefresh = await RunOnNavigateAsync(path, previousTask);
|
||||||
|
tcs.SetResult();
|
||||||
|
if (shouldRefresh)
|
||||||
{
|
{
|
||||||
// And pass an indicator for the previous task to the currently running one.
|
Refresh(isNavigationIntercepted);
|
||||||
var shouldRefresh = await RunOnNavigateAsync(path, previousTask);
|
|
||||||
if (shouldRefresh)
|
|
||||||
{
|
|
||||||
Refresh(isNavigationIntercepted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
tcs.SetResult();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLocationChanged(object sender, LocationChangedEventArgs args)
|
private void OnLocationChanged(object sender, LocationChangedEventArgs args)
|
||||||
|
|
|
||||||
|
|
@ -578,6 +578,29 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
Assert.NotNull(errorUiElem);
|
Assert.NotNull(errorUiElem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OnNavigate_DoesNotRenderWhileOnNavigateExecuting()
|
||||||
|
{
|
||||||
|
var app = Browser.MountTestComponent<TestRouterWithOnNavigate>();
|
||||||
|
|
||||||
|
// Navigate to a route
|
||||||
|
SetUrlViaPushState("/WithParameters/name/Abc");
|
||||||
|
|
||||||
|
// Click the button to trigger a re-render
|
||||||
|
var button = app.FindElement(By.Id("trigger-rerender"));
|
||||||
|
button.Click();
|
||||||
|
|
||||||
|
// Assert that the parameter route didn't render
|
||||||
|
Browser.DoesNotExist(By.Id("test-info"));
|
||||||
|
|
||||||
|
// Navigate to another page to cancel the previous `OnNavigateAsync`
|
||||||
|
// task and trigger a re-render on its completion
|
||||||
|
SetUrlViaPushState("/LongPage1");
|
||||||
|
|
||||||
|
// Confirm that the route was rendered
|
||||||
|
Browser.Equal("This is a long page you can scroll.", () => app.FindElement(By.Id("test-info")).Text);
|
||||||
|
}
|
||||||
|
|
||||||
private long BrowserScrollY
|
private long BrowserScrollY
|
||||||
{
|
{
|
||||||
get => (long)((IJavaScriptExecutor)Browser).ExecuteScript("return window.scrollY");
|
get => (long)((IJavaScriptExecutor)Browser).ExecuteScript("return window.scrollY");
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
@using Microsoft.AspNetCore.Components.Routing
|
@using Microsoft.AspNetCore.Components.Routing
|
||||||
|
|
||||||
|
@using System.Threading
|
||||||
|
|
||||||
|
<button @onclick="TriggerRerender" id="trigger-rerender">Trigger Rerender</button>
|
||||||
|
|
||||||
<Router AppAssembly="@typeof(BasicTestApp.Program).Assembly" OnNavigateAsync="@OnNavigateAsync">
|
<Router AppAssembly="@typeof(BasicTestApp.Program).Assembly" OnNavigateAsync="@OnNavigateAsync">
|
||||||
<Navigating>
|
<Navigating>
|
||||||
<div style="padding: 20px;background-color:blue;color:white;" id="loading-banner">
|
<div style="padding: 20px;background-color:blue;color:white;" id="loading-banner">
|
||||||
|
|
@ -21,7 +25,8 @@
|
||||||
{
|
{
|
||||||
{ "LongPage1", new Func<NavigationContext, Task>(TestLoadingPageShows) },
|
{ "LongPage1", new Func<NavigationContext, Task>(TestLoadingPageShows) },
|
||||||
{ "LongPage2", new Func<NavigationContext, Task>(TestOnNavCancel) },
|
{ "LongPage2", new Func<NavigationContext, Task>(TestOnNavCancel) },
|
||||||
{ "Other", new Func<NavigationContext, Task>(TestOnNavException) }
|
{ "Other", new Func<NavigationContext, Task>(TestOnNavException) },
|
||||||
|
{"WithParameters/name/Abc", new Func<NavigationContext, Task>(TestRefreshHandling)}
|
||||||
};
|
};
|
||||||
|
|
||||||
private async Task OnNavigateAsync(NavigationContext args)
|
private async Task OnNavigateAsync(NavigationContext args)
|
||||||
|
|
@ -50,4 +55,14 @@
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
throw new Exception("This is an uncaught exception.");
|
throw new Exception("This is an uncaught exception.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task TestRefreshHandling(NavigationContext args)
|
||||||
|
{
|
||||||
|
await Task.Delay(Timeout.Infinite, args.CancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TriggerRerender()
|
||||||
|
{
|
||||||
|
Console.WriteLine("Nothing to see here, just an even to trigger a re-render...");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue