Make OnNavigateAsync EventCallback and cancel previous navigation (#25011)
* Make OnNavigateAsync EventCallback and cancel previous navigation * Add more tests
This commit is contained in:
parent
5193f8f428
commit
8fc1419186
|
|
@ -73,7 +73,7 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a handler that should be called before navigating to a new page.
|
/// Gets or sets a handler that should be called before navigating to a new page.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Parameter] public Func<NavigationContext, Task>? OnNavigateAsync { get; set; }
|
[Parameter] public EventCallback<NavigationContext> OnNavigateAsync { get; set; }
|
||||||
|
|
||||||
private RouteTable Routes { get; set; }
|
private RouteTable Routes { get; set; }
|
||||||
|
|
||||||
|
|
@ -115,8 +115,7 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
if (!_onNavigateCalled)
|
if (!_onNavigateCalled)
|
||||||
{
|
{
|
||||||
_onNavigateCalled = true;
|
_onNavigateCalled = true;
|
||||||
await RunOnNavigateWithRefreshAsync(NavigationManager.ToBaseRelativePath(_locationAbsolute), isNavigationIntercepted: false);
|
await RunOnNavigateAsync(NavigationManager.ToBaseRelativePath(_locationAbsolute), isNavigationIntercepted: false);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Refresh(isNavigationIntercepted: false);
|
Refresh(isNavigationIntercepted: false);
|
||||||
|
|
@ -206,9 +205,8 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask<bool> RunOnNavigateAsync(string path, Task previousOnNavigate)
|
internal async ValueTask RunOnNavigateAsync(string path, bool isNavigationIntercepted)
|
||||||
{
|
{
|
||||||
|
|
||||||
// Cancel the CTS instead of disposing it, since disposing does not
|
// Cancel the CTS instead of disposing it, since disposing does not
|
||||||
// actually cancel and can cause unintended Object Disposed Exceptions.
|
// actually cancel and can cause unintended Object Disposed Exceptions.
|
||||||
// This effectivelly cancels the previously running task and completes it.
|
// This effectivelly cancels the previously running task and completes it.
|
||||||
|
|
@ -217,59 +215,35 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
// before starting the next one. This avoid race conditions where the cancellation
|
// before starting the next one. This avoid race conditions where the cancellation
|
||||||
// for the previous task was set but not fully completed by the time we get to this
|
// for the previous task was set but not fully completed by the time we get to this
|
||||||
// invocation.
|
// invocation.
|
||||||
await previousOnNavigate;
|
await _previousOnNavigateTask;
|
||||||
|
|
||||||
if (OnNavigateAsync == null)
|
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
_previousOnNavigateTask = tcs.Task;
|
||||||
|
|
||||||
|
if (!OnNavigateAsync.HasDelegate)
|
||||||
{
|
{
|
||||||
return true;
|
Refresh(isNavigationIntercepted);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onNavigateCts = new CancellationTokenSource();
|
_onNavigateCts = new CancellationTokenSource();
|
||||||
var navigateContext = new NavigationContext(path, _onNavigateCts.Token);
|
var navigateContext = new NavigationContext(path, _onNavigateCts.Token);
|
||||||
|
|
||||||
|
var cancellationTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
navigateContext.CancellationToken.Register(state =>
|
||||||
|
((TaskCompletionSource)state).SetResult(), cancellationTcs);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Navigating != null)
|
// Task.WhenAny returns a Task<Task> so we need to await twice to unwrap the exception
|
||||||
{
|
var task = await Task.WhenAny(OnNavigateAsync.InvokeAsync(navigateContext), cancellationTcs.Task);
|
||||||
_renderHandle.Render(Navigating);
|
await task;
|
||||||
}
|
tcs.SetResult();
|
||||||
await OnNavigateAsync(navigateContext);
|
Refresh(isNavigationIntercepted);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException e)
|
|
||||||
{
|
|
||||||
if (e.CancellationToken != navigateContext.CancellationToken)
|
|
||||||
{
|
|
||||||
var rethrownException = new InvalidOperationException("OnNavigateAsync can only be cancelled via NavigateContext.CancellationToken.", e);
|
|
||||||
_renderHandle.Render(builder => ExceptionDispatchInfo.Throw(rethrownException));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_renderHandle.Render(builder => ExceptionDispatchInfo.Throw(e));
|
_renderHandle.Render(builder => ExceptionDispatchInfo.Throw(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal async Task RunOnNavigateWithRefreshAsync(string path, bool isNavigationIntercepted)
|
|
||||||
{
|
|
||||||
// We cache the Task representing the previously invoked RunOnNavigateWithRefreshAsync
|
|
||||||
// that is stored. Then we create a new one that represents our current invocation and store it
|
|
||||||
// globally for the next invocation. This allows us to check inside `RunOnNavigateAsync` if the
|
|
||||||
// previous OnNavigateAsync task has fully completed before starting the next one.
|
|
||||||
var previousTask = _previousOnNavigateTask;
|
|
||||||
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
_previousOnNavigateTask = tcs.Task;
|
|
||||||
|
|
||||||
// And pass an indicator for the previous task to the currently running one.
|
|
||||||
var shouldRefresh = await RunOnNavigateAsync(path, previousTask);
|
|
||||||
tcs.SetResult();
|
|
||||||
if (shouldRefresh)
|
|
||||||
{
|
|
||||||
Refresh(isNavigationIntercepted);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLocationChanged(object sender, LocationChangedEventArgs args)
|
private void OnLocationChanged(object sender, LocationChangedEventArgs args)
|
||||||
|
|
@ -277,7 +251,7 @@ namespace Microsoft.AspNetCore.Components.Routing
|
||||||
_locationAbsolute = args.Location;
|
_locationAbsolute = args.Location;
|
||||||
if (_renderHandle.IsInitialized && Routes != null)
|
if (_renderHandle.IsInitialized && Routes != null)
|
||||||
{
|
{
|
||||||
_ = RunOnNavigateWithRefreshAsync(NavigationManager.ToBaseRelativePath(_locationAbsolute), args.IsNavigationIntercepted);
|
_ = RunOnNavigateAsync(NavigationManager.ToBaseRelativePath(_locationAbsolute), args.IsNavigationIntercepted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,7 @@ using Microsoft.AspNetCore.Components.Test.Helpers;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Moq;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Test.Routing
|
namespace Microsoft.AspNetCore.Components.Test.Routing
|
||||||
{
|
{
|
||||||
|
|
@ -42,49 +40,26 @@ namespace Microsoft.AspNetCore.Components.Test.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var called = false;
|
var called = false;
|
||||||
async Task OnNavigateAsync(NavigationContext args)
|
Action<NavigationContext> OnNavigateAsync = async (NavigationContext args) =>
|
||||||
{
|
{
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
called = true;
|
called = true;
|
||||||
}
|
};
|
||||||
_router.OnNavigateAsync = OnNavigateAsync;
|
_router.OnNavigateAsync = new EventCallback<NavigationContext>(null, OnNavigateAsync);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await _renderer.Dispatcher.InvokeAsync(() => _router.RunOnNavigateWithRefreshAsync("http://example.com/jan", false));
|
await _renderer.Dispatcher.InvokeAsync(() => _router.RunOnNavigateAsync("http://example.com/jan", false));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(called);
|
Assert.True(called);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task CanHandleSingleFailedOnNavigateAsync()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var called = false;
|
|
||||||
async Task OnNavigateAsync(NavigationContext args)
|
|
||||||
{
|
|
||||||
called = true;
|
|
||||||
await Task.CompletedTask;
|
|
||||||
throw new Exception("This is an uncaught exception.");
|
|
||||||
}
|
|
||||||
_router.OnNavigateAsync = OnNavigateAsync;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await _renderer.Dispatcher.InvokeAsync(() => _router.RunOnNavigateWithRefreshAsync("http://example.com/jan", false));
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.True(called);
|
|
||||||
Assert.Single(_renderer.HandledExceptions);
|
|
||||||
var unhandledException = _renderer.HandledExceptions[0];
|
|
||||||
Assert.Equal("This is an uncaught exception.", unhandledException.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CanceledFailedOnNavigateAsyncDoesNothing()
|
public async Task CanceledFailedOnNavigateAsyncDoesNothing()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var onNavigateInvoked = 0;
|
var onNavigateInvoked = 0;
|
||||||
async Task OnNavigateAsync(NavigationContext args)
|
Action<NavigationContext> OnNavigateAsync = async (NavigationContext args) =>
|
||||||
{
|
{
|
||||||
onNavigateInvoked += 1;
|
onNavigateInvoked += 1;
|
||||||
if (args.Path.EndsWith("jan"))
|
if (args.Path.EndsWith("jan"))
|
||||||
|
|
@ -92,22 +67,18 @@ namespace Microsoft.AspNetCore.Components.Test.Routing
|
||||||
await Task.Delay(Timeout.Infinite, args.CancellationToken);
|
await Task.Delay(Timeout.Infinite, args.CancellationToken);
|
||||||
throw new Exception("This is an uncaught exception.");
|
throw new Exception("This is an uncaught exception.");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
var refreshCalled = false;
|
var refreshCalled = 0;
|
||||||
_renderer.OnUpdateDisplay = (renderBatch) =>
|
_renderer.OnUpdateDisplay = (renderBatch) =>
|
||||||
{
|
{
|
||||||
if (!refreshCalled)
|
refreshCalled += 1;
|
||||||
{
|
return;
|
||||||
refreshCalled = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Assert.True(false, "OnUpdateDisplay called more than once.");
|
|
||||||
};
|
};
|
||||||
_router.OnNavigateAsync = OnNavigateAsync;
|
_router.OnNavigateAsync = new EventCallback<NavigationContext>(null, OnNavigateAsync);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var janTask = _renderer.Dispatcher.InvokeAsync(() => _router.RunOnNavigateWithRefreshAsync("http://example.com/jan", false));
|
var janTask = _renderer.Dispatcher.InvokeAsync(() => _router.RunOnNavigateAsync("http://example.com/jan", false));
|
||||||
var febTask = _renderer.Dispatcher.InvokeAsync(() => _router.RunOnNavigateWithRefreshAsync("http://example.com/feb", false));
|
var febTask = _renderer.Dispatcher.InvokeAsync(() => _router.RunOnNavigateAsync("http://example.com/feb", false));
|
||||||
|
|
||||||
await janTask;
|
await janTask;
|
||||||
await febTask;
|
await febTask;
|
||||||
|
|
@ -115,28 +86,7 @@ namespace Microsoft.AspNetCore.Components.Test.Routing
|
||||||
// Assert that we render the second route component and don't throw an exception
|
// Assert that we render the second route component and don't throw an exception
|
||||||
Assert.Empty(_renderer.HandledExceptions);
|
Assert.Empty(_renderer.HandledExceptions);
|
||||||
Assert.Equal(2, onNavigateInvoked);
|
Assert.Equal(2, onNavigateInvoked);
|
||||||
}
|
Assert.Equal(2, refreshCalled);
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task CanHandleSingleCancelledOnNavigateAsync()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
async Task OnNavigateAsync(NavigationContext args)
|
|
||||||
{
|
|
||||||
var tcs = new TaskCompletionSource<int>();
|
|
||||||
tcs.TrySetCanceled();
|
|
||||||
await tcs.Task;
|
|
||||||
}
|
|
||||||
_renderer.OnUpdateDisplay = (renderBatch) => Assert.True(false, "OnUpdateDisplay called more than once.");
|
|
||||||
_router.OnNavigateAsync = OnNavigateAsync;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
await _renderer.Dispatcher.InvokeAsync(() => _router.RunOnNavigateWithRefreshAsync("http://example.com/jan", false));
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Single(_renderer.HandledExceptions);
|
|
||||||
var unhandledException = _renderer.HandledExceptions[0];
|
|
||||||
Assert.Equal("OnNavigateAsync can only be cancelled via NavigateContext.CancellationToken.", unhandledException.Message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -144,7 +94,7 @@ namespace Microsoft.AspNetCore.Components.Test.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var triggerCancel = new TaskCompletionSource();
|
var triggerCancel = new TaskCompletionSource();
|
||||||
async Task OnNavigateAsync(NavigationContext args)
|
Action<NavigationContext> OnNavigateAsync = async (NavigationContext args) =>
|
||||||
{
|
{
|
||||||
if (args.Path.EndsWith("jan"))
|
if (args.Path.EndsWith("jan"))
|
||||||
{
|
{
|
||||||
|
|
@ -153,7 +103,7 @@ namespace Microsoft.AspNetCore.Components.Test.Routing
|
||||||
tcs.TrySetCanceled();
|
tcs.TrySetCanceled();
|
||||||
await tcs.Task;
|
await tcs.Task;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
var refreshCalled = false;
|
var refreshCalled = false;
|
||||||
_renderer.OnUpdateDisplay = (renderBatch) =>
|
_renderer.OnUpdateDisplay = (renderBatch) =>
|
||||||
{
|
{
|
||||||
|
|
@ -164,11 +114,11 @@ namespace Microsoft.AspNetCore.Components.Test.Routing
|
||||||
}
|
}
|
||||||
Assert.True(false, "OnUpdateDisplay called more than once.");
|
Assert.True(false, "OnUpdateDisplay called more than once.");
|
||||||
};
|
};
|
||||||
_router.OnNavigateAsync = OnNavigateAsync;
|
_router.OnNavigateAsync = new EventCallback<NavigationContext>(null, OnNavigateAsync);
|
||||||
|
|
||||||
// Act (start the operations then await them)
|
// Act (start the operations then await them)
|
||||||
var jan = _renderer.Dispatcher.InvokeAsync(() => _router.RunOnNavigateWithRefreshAsync("http://example.com/jan", false));
|
var jan = _renderer.Dispatcher.InvokeAsync(() => _router.RunOnNavigateAsync("http://example.com/jan", false));
|
||||||
var feb = _renderer.Dispatcher.InvokeAsync(() => _router.RunOnNavigateWithRefreshAsync("http://example.com/feb", false));
|
var feb = _renderer.Dispatcher.InvokeAsync(() => _router.RunOnNavigateAsync("http://example.com/feb", false));
|
||||||
triggerCancel.TrySetResult();
|
triggerCancel.TrySetResult();
|
||||||
|
|
||||||
await jan;
|
await jan;
|
||||||
|
|
@ -180,16 +130,16 @@ namespace Microsoft.AspNetCore.Components.Test.Routing
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var cancelled = "";
|
var cancelled = "";
|
||||||
async Task OnNavigateAsync(NavigationContext args)
|
Action<NavigationContext> OnNavigateAsync = async (NavigationContext args) =>
|
||||||
{
|
{
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
args.CancellationToken.Register(() => cancelled = args.Path);
|
args.CancellationToken.Register(() => cancelled = args.Path);
|
||||||
};
|
};
|
||||||
_router.OnNavigateAsync = OnNavigateAsync;
|
_router.OnNavigateAsync = new EventCallback<NavigationContext>(null, OnNavigateAsync);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
_ = _router.RunOnNavigateWithRefreshAsync("jan", false);
|
_ = _router.RunOnNavigateAsync("jan", false);
|
||||||
_ = _router.RunOnNavigateWithRefreshAsync("feb", false);
|
_ = _router.RunOnNavigateAsync("feb", false);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var expected = "jan";
|
var expected = "jan";
|
||||||
|
|
@ -200,7 +150,7 @@ namespace Microsoft.AspNetCore.Components.Test.Routing
|
||||||
public async Task RefreshesOnceOnCancelledOnNavigateAsync()
|
public async Task RefreshesOnceOnCancelledOnNavigateAsync()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
async Task OnNavigateAsync(NavigationContext args)
|
Action<NavigationContext> OnNavigateAsync = async (NavigationContext args) =>
|
||||||
{
|
{
|
||||||
if (args.Path.EndsWith("jan"))
|
if (args.Path.EndsWith("jan"))
|
||||||
{
|
{
|
||||||
|
|
@ -217,11 +167,11 @@ namespace Microsoft.AspNetCore.Components.Test.Routing
|
||||||
}
|
}
|
||||||
Assert.True(false, "OnUpdateDisplay called more than once.");
|
Assert.True(false, "OnUpdateDisplay called more than once.");
|
||||||
};
|
};
|
||||||
_router.OnNavigateAsync = OnNavigateAsync;
|
_router.OnNavigateAsync = new EventCallback<NavigationContext>(null, OnNavigateAsync);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var jan = _renderer.Dispatcher.InvokeAsync(() => _router.RunOnNavigateWithRefreshAsync("http://example.com/jan", false));
|
var jan = _renderer.Dispatcher.InvokeAsync(() => _router.RunOnNavigateAsync("http://example.com/jan", false));
|
||||||
var feb = _renderer.Dispatcher.InvokeAsync(() => _router.RunOnNavigateWithRefreshAsync("http://example.com/feb", false));
|
var feb = _renderer.Dispatcher.InvokeAsync(() => _router.RunOnNavigateAsync("http://example.com/feb", false));
|
||||||
|
|
||||||
await jan;
|
await jan;
|
||||||
await feb;
|
await feb;
|
||||||
|
|
|
||||||
|
|
@ -570,14 +570,24 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
{
|
{
|
||||||
var app = Browser.MountTestComponent<TestRouterWithOnNavigate>();
|
var app = Browser.MountTestComponent<TestRouterWithOnNavigate>();
|
||||||
|
|
||||||
// Navigating from one page to another should
|
|
||||||
// cancel the previous OnNavigate Task
|
|
||||||
SetUrlViaPushState("/Other");
|
SetUrlViaPushState("/Other");
|
||||||
|
|
||||||
var errorUiElem = Browser.Exists(By.Id("blazor-error-ui"), TimeSpan.FromSeconds(10));
|
var errorUiElem = Browser.Exists(By.Id("blazor-error-ui"), TimeSpan.FromSeconds(10));
|
||||||
Assert.NotNull(errorUiElem);
|
Assert.NotNull(errorUiElem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OnNavigate_CanRenderUIForSyncExceptions()
|
||||||
|
{
|
||||||
|
var app = Browser.MountTestComponent<TestRouterWithOnNavigate>();
|
||||||
|
|
||||||
|
// Should capture exception from synchronously thrown
|
||||||
|
SetUrlViaPushState("/WithLazyAssembly");
|
||||||
|
|
||||||
|
var errorUiElem = Browser.Exists(By.Id("blazor-error-ui"), TimeSpan.FromSeconds(10));
|
||||||
|
Assert.NotNull(errorUiElem);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void OnNavigate_DoesNotRenderWhileOnNavigateExecuting()
|
public void OnNavigate_DoesNotRenderWhileOnNavigateExecuting()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,15 @@
|
||||||
{ "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)}
|
{ "WithLazyAssembly", new Func<NavigationContext, Task>(TestOnNavException) },
|
||||||
|
{ "WithParameters/name/Abc", new Func<NavigationContext, Task>(TestRefreshHandling) }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
protected override void OnAfterRender(bool firstRender)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Render triggered...");
|
||||||
|
}
|
||||||
|
|
||||||
private async Task OnNavigateAsync(NavigationContext args)
|
private async Task OnNavigateAsync(NavigationContext args)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Running OnNavigate for {args.Path}...");
|
Console.WriteLine($"Running OnNavigate for {args.Path}...");
|
||||||
|
|
@ -56,6 +62,11 @@
|
||||||
throw new Exception("This is an uncaught exception.");
|
throw new Exception("This is an uncaught exception.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Task TestOnNavSyncException(NavigationContext args)
|
||||||
|
{
|
||||||
|
throw new Exception("This is an uncaught exception.");
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task TestRefreshHandling(NavigationContext args)
|
public static async Task TestRefreshHandling(NavigationContext args)
|
||||||
{
|
{
|
||||||
await Task.Delay(Timeout.Infinite, args.CancellationToken);
|
await Task.Delay(Timeout.Infinite, args.CancellationToken);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue