File tree Expand file tree Collapse file tree 5 files changed +125
-5
lines changed
E2ETest/ServerRenderingTests
testassets/Components.TestServer
RazorComponents/Pages/Redirections Expand file tree Collapse file tree 5 files changed +125
-5
lines changed Original file line number Diff line number Diff line change @@ -229,9 +229,16 @@ async Task Execute()
229229 // Clear all pending work.
230230 _nonStreamingPendingTasks . Clear ( ) ;
231231
232- // new work might be added before we check again as a result of waiting for all
233- // the child components to finish executing SetParametersAsync
234- await pendingWork ;
232+ try
233+ {
234+ // new work might be added before we check again as a result of waiting for all
235+ // the child components to finish executing SetParametersAsync
236+ await pendingWork ;
237+ }
238+ catch ( NavigationException navigationException )
239+ {
240+ await HandleNavigationException ( _httpContext , navigationException ) ;
241+ }
235242 }
236243 }
237244 }
Original file line number Diff line number Diff line change @@ -221,6 +221,18 @@ public void RedirectEnhancedGetToInternalWithErrorBoundary()
221221 Assert . EndsWith ( "/subdir/redirect" , Browser . Url ) ;
222222 }
223223
224+ [ Fact ]
225+ public void NavigationException_InAsyncContext_DoesNotBecomeUnobservedTaskException ( )
226+ {
227+ AppContext . SetSwitch ( "Microsoft.AspNetCore.Components.Endpoints.NavigationManager.DisableThrowNavigationException" , false ) ;
228+
229+ // Navigate to the page that triggers the circular redirect.
230+ Navigate ( $ "{ ServerPathBase } /redirect/circular") ;
231+
232+ // The component will stop redirecting after 3 attempts and render the exception count.
233+ Browser . Equal ( "0" , ( ) => Browser . FindElement ( By . Id ( "unobserved-exceptions-count" ) ) . Text ) ;
234+ }
235+
224236 private void AssertElementRemoved ( IWebElement element )
225237 {
226238 Browser . True ( ( ) =>
Original file line number Diff line number Diff line change @@ -81,8 +81,16 @@ private static string[] CreateAdditionalArgs(string[] args) =>
8181
8282 public static IHost BuildWebHost ( string [ ] args ) => BuildWebHost < Startup > ( args ) ;
8383
84- public static IHost BuildWebHost < TStartup > ( string [ ] args ) where TStartup : class =>
85- Host . CreateDefaultBuilder ( args )
84+ public static IHost BuildWebHost < TStartup > ( string [ ] args ) where TStartup : class
85+ {
86+ var unobservedTaskExceptionObserver = new UnobservedTaskExceptionObserver ( ) ;
87+ TaskScheduler . UnobservedTaskException += unobservedTaskExceptionObserver . OnUnobservedTaskException ;
88+
89+ return Host . CreateDefaultBuilder ( args )
90+ . ConfigureServices ( services =>
91+ {
92+ services . AddSingleton ( unobservedTaskExceptionObserver ) ;
93+ } )
8694 . ConfigureLogging ( ( ctx , lb ) =>
8795 {
8896 TestSink sink = new TestSink ( ) ;
@@ -98,6 +106,7 @@ public static IHost BuildWebHost<TStartup>(string[] args) where TStartup : class
98106 webHostBuilder . UseStaticWebAssets ( ) ;
99107 } )
100108 . Build ( ) ;
109+ }
101110
102111 private static int GetNextChildAppPortNumber ( )
103112 {
Original file line number Diff line number Diff line change 1+ @page " /redirect/circular"
2+ @using System .Collections .Concurrent
3+ @inject NavigationManager Nav
4+ @inject UnobservedTaskExceptionObserver Observer
5+
6+ <h1 >Hello, world!</h1 >
7+
8+ @if (_shouldStopRedirecting )
9+ {
10+ <p id =" unobserved-exceptions-count" >@_unobservedExceptions.Count </p >
11+
12+ @if (_unobservedExceptions .Any ())
13+ {
14+ <h2 >Unobserved Exceptions (for debugging ): </h2 >
15+ <ul >
16+ @foreach ( var ex in _unobservedExceptions )
17+ {
18+ <li >@ex.ToString() </li >
19+ }
20+ </ul >
21+ }
22+ }
23+
24+ @code {
25+ private bool _shouldStopRedirecting ;
26+ private IReadOnlyCollection <Exception > _unobservedExceptions = Array .Empty <Exception >();
27+
28+ protected override async Task OnInitializedAsync ()
29+ {
30+ int visits = Observer .GetCircularRedirectCount ();
31+ if (visits == 0 )
32+ {
33+ // make sure we start with clean logs
34+ Observer .Clear ();
35+ }
36+
37+ // Force GC collection to trigger finalizers - this is what causes the issue
38+ GC .Collect ();
39+ GC .WaitForPendingFinalizers ();
40+ GC .Collect ();
41+ await Task .Yield ();
42+
43+ if (Observer .GetAndIncrementCircularRedirectCount () < 3 )
44+ {
45+ Nav .NavigateTo (" redirect/circular" );
46+ }
47+ else
48+ {
49+ _shouldStopRedirecting = true ;
50+ _unobservedExceptions = Observer .GetExceptions ();
51+ }
52+ }
53+ }
Original file line number Diff line number Diff line change 1+ // Licensed to the .NET Foundation under one or more agreements.
2+ // The .NET Foundation licenses this file to you under the MIT license.
3+
4+ using System . Collections . Concurrent ;
5+ using System . Threading ;
6+
7+ namespace TestServer ;
8+
9+ public class UnobservedTaskExceptionObserver
10+ {
11+ private readonly ConcurrentQueue < Exception > _exceptions = new ( ) ;
12+ private int _circularRedirectCount ;
13+
14+ public void OnUnobservedTaskException ( object sender , UnobservedTaskExceptionEventArgs e )
15+ {
16+ _exceptions . Enqueue ( e . Exception ) ;
17+ e . SetObserved ( ) ; // Mark as observed to prevent the process from crashing during tests
18+ }
19+
20+ public bool HasExceptions => ! _exceptions . IsEmpty ;
21+
22+ public IReadOnlyCollection < Exception > GetExceptions ( ) => _exceptions . ToArray ( ) ;
23+
24+ public void Clear ( )
25+ {
26+ _exceptions . Clear ( ) ;
27+ _circularRedirectCount = 0 ;
28+ }
29+
30+ public int GetCircularRedirectCount ( )
31+ {
32+ return _circularRedirectCount ;
33+ }
34+
35+ public int GetAndIncrementCircularRedirectCount ( )
36+ {
37+ return Interlocked . Increment ( ref _circularRedirectCount ) - 1 ;
38+ }
39+ }
You can’t perform that action at this time.
0 commit comments