Skip to content

Wrap OnNavigateTo callback with GetErrorHandledTask for proper exception logging #62414

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 95 additions & 1 deletion src/Components/Components/test/NavigationManagerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -886,7 +886,42 @@ public void OnNotFoundSubscriptionIsTriggeredWhenNotFoundCalled()
// Assert
Assert.True(notFoundTriggered, "The OnNotFound event was not triggered as expected.");
}


[Fact]
public async Task OnNavigateToCallback_WhenThrows_ShouldBeHandledGracefully()
{
#nullable enable
// Arrange
var baseUri = "scheme://host/";
var uri = "scheme://host/test";
var testNavManager = new TestNavigationManagerWithExceptionHandling(baseUri);
var expectedException = new InvalidOperationException("Test exception from OnNavigateTo");
var exceptionHandledTcs = new TaskCompletionSource();

// First test: Initialize with a callback that throws exceptions
testNavManager.Initialize(baseUri, uri, uri => testNavManager.GetErrorHandledTask(ThrowingMethod(uri), exceptionHandledTcs));

// Act & Assert
// Verify that the wrapped callback handles the exception gracefully
var wrappedException = testNavManager.TriggerOnNavigateToCallback(uri);

// Should be null because the exception was handled gracefully
Assert.Null(wrappedException);

await exceptionHandledTcs.Task;

// Verify that the exception was logged
Assert.Single(testNavManager.HandledExceptions);
Assert.Same(expectedException, testNavManager.HandledExceptions[0]);

async Task ThrowingMethod(string param)
{
await Task.Yield();
throw expectedException;
}
#nullable restore
}

private class TestNavigationManager : NavigationManager
{
public TestNavigationManager()
Expand Down Expand Up @@ -932,4 +967,63 @@ protected override void HandleLocationChangingHandlerException(Exception ex, Loc
_exceptionsThrownFromLocationChangingHandlers.Add(ex);
}
}

private class TestNavigationManagerWithExceptionHandling : TestNavigationManager, IHostEnvironmentNavigationManager
{
private Func<string, Task> _onNavigateToCallback;

public List<Exception> HandledExceptions { get; } = new();

public TestNavigationManagerWithExceptionHandling(string baseUri = null, string uri = null)
: base(baseUri, uri)
{
}

public void Initialize(string baseUri, string uri, Func<string, Task> onNavigateTo)
{
_onNavigateToCallback = onNavigateTo;
}

#nullable enable
public Exception? TriggerOnNavigateToCallback(string uri)
#nullable restore
{
try
{
// Simulate the fire-and-forget pattern of RemoteNavigationManager
_ = _onNavigateToCallback(uri);
return null;
}
catch (Exception ex)
{
return ex;
}
}

protected override void NavigateToCore(string uri, bool forceLoad)
{
// Simulate the behavior where NavigateToCore calls the onNavigateTo callback
// in a fire-and-forget manner when JSRuntime is not available
if (_onNavigateToCallback is not null)
{
_ = _onNavigateToCallback(uri);
}
}

public async Task GetErrorHandledTask(Task taskToHandle, TaskCompletionSource completionSource = null)
{
try
{
await taskToHandle;
}
catch (Exception ex)
{
HandledExceptions.Add(ex);
}
finally
{
completionSource?.SetResult();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ internal async Task InitializeStandardComponentServicesAsync(
IFormCollection? form = null)
{
var navigationManager = httpContext.RequestServices.GetRequiredService<NavigationManager>();
((IHostEnvironmentNavigationManager)navigationManager)?.Initialize(GetContextBaseUri(httpContext.Request), GetFullUri(httpContext.Request), OnNavigateTo);
((IHostEnvironmentNavigationManager)navigationManager)?.Initialize(
GetContextBaseUri(httpContext.Request),
GetFullUri(httpContext.Request),
uri => GetErrorHandledTask(OnNavigateTo(uri)));

navigationManager?.OnNotFound += (sender, args) =>
{
Expand Down
Loading