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()
229
229
// Clear all pending work.
230
230
_nonStreamingPendingTasks . Clear ( ) ;
231
231
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
+ }
235
242
}
236
243
}
237
244
}
Original file line number Diff line number Diff line change @@ -221,6 +221,18 @@ public void RedirectEnhancedGetToInternalWithErrorBoundary()
221
221
Assert . EndsWith ( "/subdir/redirect" , Browser . Url ) ;
222
222
}
223
223
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
+
224
236
private void AssertElementRemoved ( IWebElement element )
225
237
{
226
238
Browser . True ( ( ) =>
Original file line number Diff line number Diff line change @@ -81,8 +81,16 @@ private static string[] CreateAdditionalArgs(string[] args) =>
81
81
82
82
public static IHost BuildWebHost ( string [ ] args ) => BuildWebHost < Startup > ( args ) ;
83
83
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
+ } )
86
94
. ConfigureLogging ( ( ctx , lb ) =>
87
95
{
88
96
TestSink sink = new TestSink ( ) ;
@@ -98,6 +106,7 @@ public static IHost BuildWebHost<TStartup>(string[] args) where TStartup : class
98
106
webHostBuilder . UseStaticWebAssets ( ) ;
99
107
} )
100
108
. Build ( ) ;
109
+ }
101
110
102
111
private static int GetNextChildAppPortNumber ( )
103
112
{
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