diff --git a/src/Components/Components/test/NavigationManagerTest.cs b/src/Components/Components/test/NavigationManagerTest.cs index 3f6e680d0c8b..081165aa81b1 100644 --- a/src/Components/Components/test/NavigationManagerTest.cs +++ b/src/Components/Components/test/NavigationManagerTest.cs @@ -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() @@ -932,4 +967,63 @@ protected override void HandleLocationChangingHandlerException(Exception ex, Loc _exceptionsThrownFromLocationChangingHandlers.Add(ex); } } + + private class TestNavigationManagerWithExceptionHandling : TestNavigationManager, IHostEnvironmentNavigationManager + { + private Func _onNavigateToCallback; + + public List HandledExceptions { get; } = new(); + + public TestNavigationManagerWithExceptionHandling(string baseUri = null, string uri = null) + : base(baseUri, uri) + { + } + + public void Initialize(string baseUri, string uri, Func 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(); + } + } + } } diff --git a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs index f5d0699e1efe..3fb8c965ad27 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs @@ -83,7 +83,10 @@ internal async Task InitializeStandardComponentServicesAsync( IFormCollection? form = null) { var navigationManager = httpContext.RequestServices.GetRequiredService(); - ((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) => {