8
8
using System . Collections . ObjectModel ;
9
9
using System . Linq ;
10
10
using System . Reflection ;
11
+ using System . Threading ;
11
12
using System . Threading . Tasks ;
12
- using Microsoft . AspNetCore . Components . Rendering ;
13
13
using Microsoft . Extensions . Logging ;
14
14
15
15
namespace Microsoft . AspNetCore . Components . Routing
@@ -29,6 +29,12 @@ static readonly ReadOnlyDictionary<string, object> _emptyParametersDictionary
29
29
bool _navigationInterceptionEnabled ;
30
30
ILogger < Router > _logger ;
31
31
32
+ private CancellationTokenSource _onNavigateCts ;
33
+
34
+ private readonly HashSet < Assembly > _assemblies = new HashSet < Assembly > ( ) ;
35
+
36
+ private bool _onNavigateCalled = false ;
37
+
32
38
[ Inject ] private NavigationManager NavigationManager { get ; set ; }
33
39
34
40
[ Inject ] private INavigationInterception NavigationInterception { get ; set ; }
@@ -56,6 +62,16 @@ static readonly ReadOnlyDictionary<string, object> _emptyParametersDictionary
56
62
/// </summary>
57
63
[ Parameter ] public RenderFragment < RouteData > Found { get ; set ; }
58
64
65
+ /// <summary>
66
+ /// Get or sets the content to display when asynchronous navigation is in progress.
67
+ /// </summary>
68
+ [ Parameter ] public RenderFragment Navigating { get ; set ; }
69
+
70
+ /// <summary>
71
+ /// Gets or sets a handler that should be called before navigating to a new page.
72
+ /// </summary>
73
+ [ Parameter ] public EventCallback < NavigationContext > OnNavigateAsync { get ; set ; }
74
+
59
75
private RouteTable Routes { get ; set ; }
60
76
61
77
/// <inheritdoc />
@@ -69,7 +85,7 @@ public void Attach(RenderHandle renderHandle)
69
85
}
70
86
71
87
/// <inheritdoc />
72
- public Task SetParametersAsync ( ParameterView parameters )
88
+ public async Task SetParametersAsync ( ParameterView parameters )
73
89
{
74
90
parameters . SetParameterProperties ( this ) ;
75
91
@@ -93,17 +109,20 @@ public Task SetParametersAsync(ParameterView parameters)
93
109
throw new InvalidOperationException ( $ "The { nameof ( Router ) } component requires a value for the parameter { nameof ( NotFound ) } .") ;
94
110
}
95
111
112
+ if ( ! _onNavigateCalled )
113
+ {
114
+ _onNavigateCalled = true ;
115
+ await RunOnNavigateAsync ( NavigationManager . ToBaseRelativePath ( _locationAbsolute ) ) ;
116
+ }
96
117
97
- var assemblies = AdditionalAssemblies == null ? new [ ] { AppAssembly } : new [ ] { AppAssembly } . Concat ( AdditionalAssemblies ) ;
98
- Routes = RouteTableFactory . Create ( assemblies ) ;
99
118
Refresh ( isNavigationIntercepted : false ) ;
100
- return Task . CompletedTask ;
101
119
}
102
120
103
121
/// <inheritdoc />
104
122
public void Dispose ( )
105
123
{
106
124
NavigationManager . LocationChanged -= OnLocationChanged ;
125
+ _onNavigateCts ? . Dispose ( ) ;
107
126
}
108
127
109
128
private static string StringUntilAny ( string str , char [ ] chars )
@@ -114,8 +133,24 @@ private static string StringUntilAny(string str, char[] chars)
114
133
: str . Substring ( 0 , firstIndex ) ;
115
134
}
116
135
136
+ private void RefreshRouteTable ( )
137
+ {
138
+ var assemblies = AdditionalAssemblies == null ? new [ ] { AppAssembly } : new [ ] { AppAssembly } . Concat ( AdditionalAssemblies ) ;
139
+ var assembliesSet = new HashSet < Assembly > ( assemblies ) ;
140
+
141
+ if ( ! _assemblies . SetEquals ( assembliesSet ) )
142
+ {
143
+ Routes = RouteTableFactory . Create ( assemblies ) ;
144
+ _assemblies . Clear ( ) ;
145
+ _assemblies . UnionWith ( assembliesSet ) ;
146
+ }
147
+
148
+ }
149
+
117
150
private void Refresh ( bool isNavigationIntercepted )
118
151
{
152
+ RefreshRouteTable ( ) ;
153
+
119
154
var locationPath = NavigationManager . ToBaseRelativePath ( _locationAbsolute ) ;
120
155
locationPath = StringUntilAny ( locationPath , _queryOrHashStartChar ) ;
121
156
var context = new RouteContext ( locationPath ) ;
@@ -155,12 +190,52 @@ private void Refresh(bool isNavigationIntercepted)
155
190
}
156
191
}
157
192
193
+ private async Task RunOnNavigateAsync ( string path )
194
+ {
195
+ // If this router instance does not provide an OnNavigateAsync parameter
196
+ // then we render the component associated with the route as per usual.
197
+ if ( ! OnNavigateAsync . HasDelegate )
198
+ {
199
+ return ;
200
+ }
201
+
202
+ // If we've already invoked a task and stored its CTS, then
203
+ // cancel the existing task.
204
+ _onNavigateCts ? . Dispose ( ) ;
205
+
206
+ // Create a new cancellation token source for this instance
207
+ _onNavigateCts = new CancellationTokenSource ( ) ;
208
+ var navigateContext = new NavigationContext ( path , _onNavigateCts . Token ) ;
209
+
210
+ // Create a cancellation task based on the cancellation token
211
+ // associated with the current running task.
212
+ var cancellationTaskSource = new TaskCompletionSource ( ) ;
213
+ navigateContext . CancellationToken . Register ( state =>
214
+ ( ( TaskCompletionSource ) state ) . SetResult ( ) , cancellationTaskSource ) ;
215
+
216
+ var task = OnNavigateAsync . InvokeAsync ( navigateContext ) ;
217
+
218
+ // If the user provided a Navigating render fragment, then show it.
219
+ if ( Navigating != null && task . Status != TaskStatus . RanToCompletion )
220
+ {
221
+ _renderHandle . Render ( Navigating ) ;
222
+ }
223
+
224
+ await Task . WhenAny ( task , cancellationTaskSource . Task ) ;
225
+ }
226
+
227
+ private async Task RunOnNavigateWithRefreshAsync ( string path , bool isNavigationIntercepted )
228
+ {
229
+ await RunOnNavigateAsync ( path ) ;
230
+ Refresh ( isNavigationIntercepted ) ;
231
+ }
232
+
158
233
private void OnLocationChanged ( object sender , LocationChangedEventArgs args )
159
234
{
160
235
_locationAbsolute = args . Location ;
161
236
if ( _renderHandle . IsInitialized && Routes != null )
162
237
{
163
- Refresh ( args . IsNavigationIntercepted ) ;
238
+ _ = RunOnNavigateWithRefreshAsync ( NavigationManager . ToBaseRelativePath ( _locationAbsolute ) , args . IsNavigationIntercepted ) ;
164
239
}
165
240
}
166
241
0 commit comments