77using Microsoft . Extensions . Options ;
88using SimpleIdServer . IdServer . Domains ;
99using System . Reflection ;
10+ using System . Runtime . ExceptionServices ;
1011using System . Text . RegularExpressions ;
1112
1213namespace SimpleIdServer . IdServer . Website . Infrastructures ;
1314
1415public class RealmRouter : IComponent , IHandleAfterRender , IDisposable
1516{
17+ string _baseUri ;
18+ string _locationAbsolute ;
19+ bool _navigationInterceptionEnabled ;
1620 private RenderHandle _renderHandle ;
1721 private Dictionary < Type , Dictionary < string , string > > _routeableComponents ;
1822 internal static IServiceProvider _serviceProvider ;
23+ private CancellationTokenSource _onNavigateCts ;
24+ private Task _previousOnNavigateTask = Task . CompletedTask ;
25+ private IRoutingStateProvider ? RoutingStateProvider { get ; set ; }
26+
1927 [ Inject ] private NavigationManager NavigationManager { get ; set ; }
28+ [ Inject ] IServiceProvider ServiceProvider { get ; set ; }
29+ [ Inject ] private IScrollToLocationHash ScrollToLocationHash { get ; set ; }
30+ [ Inject ] private INavigationInterception NavigationInterception { get ; set ; }
2031 [ Parameter ] public Assembly AppAssembly { get ; set ; }
2132 [ Parameter ] public RenderFragment < RouteData > Found { get ; set ; }
2233 [ Parameter ] public RenderFragment NotFound { get ; set ; }
34+ [ Parameter ] public RenderFragment ? Navigating { get ; set ; }
35+ [ Parameter ] public EventCallback < CustomNavigationContext > OnNavigateAsync { get ; set ; }
36+ private bool _onNavigateCalled ;
2337
2438 public void Attach ( RenderHandle renderHandle )
2539 {
2640 _renderHandle = renderHandle ;
27- NavigationManager . LocationChanged += OnLocationChanged ;
2841 _routeableComponents = GetRouteableComponents ( ) ;
42+ _baseUri = NavigationManager . BaseUri ;
43+ _locationAbsolute = NavigationManager . Uri ;
44+ NavigationManager . LocationChanged += OnLocationChanged ;
45+ RoutingStateProvider = ServiceProvider . GetService < IRoutingStateProvider > ( ) ;
2946 }
3047
3148 public async Task SetParametersAsync ( ParameterView parameters )
3249 {
3350 parameters . SetParameterProperties ( this ) ;
3451 var locationPath = NavigationManager . ToBaseRelativePath ( NavigationManager . Uri ) ;
35- await Navigate ( locationPath ) ;
52+ if ( ! _onNavigateCalled )
53+ {
54+ _onNavigateCalled = true ;
55+ await RunOnNavigateAsync ( locationPath , false ) ;
56+ }
57+ else
58+ {
59+ await Refresh ( locationPath , false ) ;
60+ }
3661 }
3762
3863 public void Dispose ( )
3964 {
4065 NavigationManager . LocationChanged -= OnLocationChanged ;
4166 }
4267
43- public Task OnAfterRenderAsync ( )
68+ public async Task OnAfterRenderAsync ( )
69+ {
70+ if ( ! _navigationInterceptionEnabled )
71+ {
72+ _navigationInterceptionEnabled = true ;
73+ await NavigationInterception . EnableNavigationInterceptionAsync ( ) ;
74+ }
75+ }
76+
77+ private async Task RunOnNavigateAsync ( string locationPath , bool isNavigationIntercepted )
4478 {
45- return Task . CompletedTask ;
79+ _onNavigateCts ? . Cancel ( ) ;
80+ await _previousOnNavigateTask ;
81+ var tcs = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
82+ _previousOnNavigateTask = tcs . Task ;
83+ if ( ! OnNavigateAsync . HasDelegate )
84+ {
85+ await Refresh ( locationPath , isNavigationIntercepted ) ;
86+ }
87+
88+ _onNavigateCts = new CancellationTokenSource ( ) ;
89+ var navigateContext = new CustomNavigationContext ( locationPath , _onNavigateCts . Token ) ;
90+ var cancellationTcs = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
91+ navigateContext . CancellationToken . Register ( state =>
92+ ( ( TaskCompletionSource ) state ) . SetResult ( ) , cancellationTcs ) ;
93+
94+ try
95+ {
96+ var task = await Task . WhenAny ( OnNavigateAsync . InvokeAsync ( navigateContext ) , cancellationTcs . Task ) ;
97+ await task ;
98+ tcs . SetResult ( ) ;
99+ await Refresh ( locationPath , isNavigationIntercepted ) ;
100+ }
101+ catch ( Exception e )
102+ {
103+ _renderHandle . Render ( builder => ExceptionDispatchInfo . Throw ( e ) ) ;
104+ }
46105 }
47106
48- private async Task Navigate ( string locationPath )
107+ private async Task Refresh ( string locationPath , bool isNavigationIntercepted )
49108 {
109+ if ( _previousOnNavigateTask . Status != TaskStatus . RanToCompletion )
110+ {
111+ if ( Navigating != null )
112+ {
113+ _renderHandle . Render ( Navigating ) ;
114+ }
115+ return ;
116+ }
117+
118+
50119 var options = _serviceProvider . GetRequiredService < IOptions < IdServerWebsiteOptions > > ( ) ;
51120 var routeParameters = new Dictionary < string , object > ( ) ;
52121 Type handlerContext = null ;
122+ var relativePath = NavigationManager . ToBaseRelativePath ( _locationAbsolute ) ;
53123 if ( ! options . Value . IsReamEnabled )
54124 {
55125 if ( ! locationPath . StartsWith ( "/" ) )
@@ -61,7 +131,7 @@ private async Task Navigate(string locationPath)
61131 }
62132
63133 var routeData = new RouteData ( handlerContext , routeParameters ) ;
64- _renderHandle . Render ( Found ( routeData ) ) ;
134+ _renderHandle . Render ( Found ( routeData ) ) ;
65135 return ;
66136 }
67137 else
@@ -85,7 +155,7 @@ private async void OnLocationChanged(object sender, LocationChangedEventArgs arg
85155 if ( _renderHandle . IsInitialized && _routeableComponents != null )
86156 {
87157 var locationPath = NavigationManager . ToBaseRelativePath ( args . Location ) ;
88- _ = Navigate ( locationPath ) ;
158+ _ = RunOnNavigateAsync ( ( locationPath ) , args . IsNavigationIntercepted ) ;
89159 }
90160 }
91161
@@ -144,3 +214,23 @@ private Dictionary<Type, Dictionary<string, string>> GetRouteableComponents()
144214 return dic ;
145215 }
146216}
217+
218+ public class CustomNavigationContext
219+ {
220+ public CustomNavigationContext ( string path , CancellationToken cancellationToken )
221+ {
222+ Path = path ;
223+ CancellationToken = cancellationToken ;
224+ }
225+
226+ /// <summary>
227+ /// The target path for the navigation.
228+ /// </summary>
229+ public string Path { get ; }
230+
231+ /// <summary>
232+ /// The <see cref="CancellationToken"/> to use to cancel navigation.
233+ /// </summary>
234+ public CancellationToken CancellationToken { get ; }
235+
236+ }
0 commit comments