-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Add framework support for lazy-loading assemblies on route change #23290
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e2e452b
f9bb435
bd666cc
ac014bd
375f1c0
3c42ded
1449d4e
bcab73a
463a4d7
d71ffbf
7300751
7445f32
a888bef
f4b6e5b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System.Threading; | ||
|
||
namespace Microsoft.AspNetCore.Components.Routing | ||
{ | ||
/// <summary> | ||
/// Provides information about the current asynchronous navigation event | ||
/// including the target path and the cancellation token. | ||
/// </summary> | ||
public sealed class NavigationContext | ||
{ | ||
internal NavigationContext(string path, CancellationToken cancellationToken) | ||
{ | ||
Path = path; | ||
CancellationToken = cancellationToken; | ||
} | ||
|
||
public string Path { get; } | ||
|
||
public CancellationToken CancellationToken { get; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,8 +8,8 @@ | |
using System.Collections.ObjectModel; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Components.Rendering; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Microsoft.AspNetCore.Components.Routing | ||
|
@@ -29,6 +29,12 @@ static readonly ReadOnlyDictionary<string, object> _emptyParametersDictionary | |
bool _navigationInterceptionEnabled; | ||
ILogger<Router> _logger; | ||
|
||
private CancellationTokenSource _onNavigateCts; | ||
captainsafia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
private readonly HashSet<Assembly> _assemblies = new HashSet<Assembly>(); | ||
|
||
private bool _onNavigateCalled = false; | ||
|
||
[Inject] private NavigationManager NavigationManager { get; set; } | ||
|
||
[Inject] private INavigationInterception NavigationInterception { get; set; } | ||
|
@@ -56,6 +62,16 @@ static readonly ReadOnlyDictionary<string, object> _emptyParametersDictionary | |
/// </summary> | ||
[Parameter] public RenderFragment<RouteData> Found { get; set; } | ||
|
||
/// <summary> | ||
/// Get or sets the content to display when asynchronous navigation is in progress. | ||
/// </summary> | ||
[Parameter] public RenderFragment Navigating { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets a handler that should be called before navigating to a new page. | ||
/// </summary> | ||
[Parameter] public EventCallback<NavigationContext> OnNavigateAsync { get; set; } | ||
|
||
private RouteTable Routes { get; set; } | ||
|
||
/// <inheritdoc /> | ||
|
@@ -69,7 +85,7 @@ public void Attach(RenderHandle renderHandle) | |
} | ||
|
||
/// <inheritdoc /> | ||
public Task SetParametersAsync(ParameterView parameters) | ||
public async Task SetParametersAsync(ParameterView parameters) | ||
{ | ||
parameters.SetParameterProperties(this); | ||
|
||
|
@@ -93,17 +109,20 @@ public Task SetParametersAsync(ParameterView parameters) | |
throw new InvalidOperationException($"The {nameof(Router)} component requires a value for the parameter {nameof(NotFound)}."); | ||
} | ||
|
||
if (!_onNavigateCalled) | ||
{ | ||
_onNavigateCalled = true; | ||
await RunOnNavigateAsync(NavigationManager.ToBaseRelativePath(_locationAbsolute)); | ||
} | ||
|
||
var assemblies = AdditionalAssemblies == null ? new[] { AppAssembly } : new[] { AppAssembly }.Concat(AdditionalAssemblies); | ||
Routes = RouteTableFactory.Create(assemblies); | ||
Refresh(isNavigationIntercepted: false); | ||
return Task.CompletedTask; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public void Dispose() | ||
{ | ||
NavigationManager.LocationChanged -= OnLocationChanged; | ||
_onNavigateCts?.Dispose(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this ever be called when RunOnNavigateAsync is running? If so, this could lead to ODEs in RunOnNavigateAsync. |
||
} | ||
|
||
private static string StringUntilAny(string str, char[] chars) | ||
|
@@ -114,8 +133,24 @@ private static string StringUntilAny(string str, char[] chars) | |
: str.Substring(0, firstIndex); | ||
} | ||
|
||
private void RefreshRouteTable() | ||
{ | ||
var assemblies = AdditionalAssemblies == null ? new[] { AppAssembly } : new[] { AppAssembly }.Concat(AdditionalAssemblies); | ||
captainsafia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
var assembliesSet = new HashSet<Assembly>(assemblies); | ||
|
||
if (!_assemblies.SetEquals(assembliesSet)) | ||
{ | ||
Routes = RouteTableFactory.Create(assemblies); | ||
_assemblies.Clear(); | ||
_assemblies.UnionWith(assembliesSet); | ||
} | ||
|
||
} | ||
|
||
private void Refresh(bool isNavigationIntercepted) | ||
{ | ||
RefreshRouteTable(); | ||
|
||
var locationPath = NavigationManager.ToBaseRelativePath(_locationAbsolute); | ||
locationPath = StringUntilAny(locationPath, _queryOrHashStartChar); | ||
var context = new RouteContext(locationPath); | ||
|
@@ -155,12 +190,52 @@ private void Refresh(bool isNavigationIntercepted) | |
} | ||
} | ||
|
||
private async Task RunOnNavigateAsync(string path) | ||
{ | ||
// If this router instance does not provide an OnNavigateAsync parameter | ||
// then we render the component associated with the route as per usual. | ||
if (!OnNavigateAsync.HasDelegate) | ||
{ | ||
return; | ||
} | ||
|
||
// If we've already invoked a task and stored its CTS, then | ||
// cancel the existing task. | ||
_onNavigateCts?.Dispose(); | ||
|
||
// Create a new cancellation token source for this instance | ||
_onNavigateCts = new CancellationTokenSource(); | ||
var navigateContext = new NavigationContext(path, _onNavigateCts.Token); | ||
|
||
// Create a cancellation task based on the cancellation token | ||
// associated with the current running task. | ||
var cancellationTaskSource = new TaskCompletionSource(); | ||
navigateContext.CancellationToken.Register(state => | ||
((TaskCompletionSource)state).SetResult(), cancellationTaskSource); | ||
|
||
var task = OnNavigateAsync.InvokeAsync(navigateContext); | ||
|
||
// If the user provided a Navigating render fragment, then show it. | ||
if (Navigating != null && task.Status != TaskStatus.RanToCompletion) | ||
{ | ||
_renderHandle.Render(Navigating); | ||
} | ||
captainsafia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
await Task.WhenAny(task, cancellationTaskSource.Task); | ||
pranavkm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
private async Task RunOnNavigateWithRefreshAsync(string path, bool isNavigationIntercepted) | ||
{ | ||
await RunOnNavigateAsync(path); | ||
Refresh(isNavigationIntercepted); | ||
} | ||
|
||
private void OnLocationChanged(object sender, LocationChangedEventArgs args) | ||
{ | ||
_locationAbsolute = args.Location; | ||
if (_renderHandle.IsInitialized && Routes != null) | ||
{ | ||
Refresh(args.IsNavigationIntercepted); | ||
_ = RunOnNavigateWithRefreshAsync(NavigationManager.ToBaseRelativePath(_locationAbsolute), args.IsNavigationIntercepted); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it ok to ignore this task? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have a separate workitem to surface exceptions in this task, we don't want to tackle it on the current PR There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really. We have an open issue to track how we capture unhandled expectations and dispatch them to the renderer. It's tracked because the change is orthogonal to what we have here. |
||
} | ||
} | ||
|
||
|
Large diffs are not rendered by default.
Uh oh!
There was an error while loading. Please reload this page.