Skip to content

Commit 7300751

Browse files
committed
Spurce up API and add tests for pre-rendering scenario
1 parent d71ffbf commit 7300751

File tree

16 files changed

+114
-103
lines changed

16 files changed

+114
-103
lines changed

src/Components/Components/ref/Microsoft.AspNetCore.Components.netcoreapp.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -246,10 +246,6 @@ public partial interface IHandleEvent
246246
{
247247
System.Threading.Tasks.Task HandleEventAsync(Microsoft.AspNetCore.Components.EventCallbackWorkItem item, object? arg);
248248
}
249-
public partial interface ILazyLoader
250-
{
251-
System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<System.Reflection.Assembly>> LoadAssembliesAsync(System.Collections.Generic.IEnumerable<string> assembliesToLoad);
252-
}
253249
[System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
254250
public sealed partial class InjectAttribute : System.Attribute
255251
{
@@ -560,9 +556,9 @@ public LocationChangedEventArgs(string location, bool isNavigationIntercepted) {
560556
public bool IsNavigationIntercepted { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
561557
public string Location { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
562558
}
563-
public partial class OnNavigateArgs
559+
public partial class NavigationContext
564560
{
565-
public OnNavigateArgs(string path, System.Threading.CancellationTokenSource cancellationTokenSource) { }
561+
internal NavigationContext() { }
566562
public System.Threading.CancellationTokenSource CancellationTokenSource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
567563
public string Path { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } }
568564
}
@@ -576,11 +572,11 @@ public Router() { }
576572
[Microsoft.AspNetCore.Components.ParameterAttribute]
577573
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.RouteData> Found { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
578574
[Microsoft.AspNetCore.Components.ParameterAttribute]
579-
public Microsoft.AspNetCore.Components.RenderFragment Loading { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
575+
public Microsoft.AspNetCore.Components.RenderFragment Navigating { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
580576
[Microsoft.AspNetCore.Components.ParameterAttribute]
581577
public Microsoft.AspNetCore.Components.RenderFragment NotFound { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
582578
[Microsoft.AspNetCore.Components.ParameterAttribute]
583-
public System.Func<Microsoft.AspNetCore.Components.Routing.OnNavigateArgs, System.Threading.Tasks.Task> OnNavigateAsync { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
579+
public System.Func<Microsoft.AspNetCore.Components.Routing.NavigationContext, System.Threading.Tasks.Task> OnNavigateAsync { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
584580
public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
585581
public void Dispose() { }
586582
System.Threading.Tasks.Task Microsoft.AspNetCore.Components.IHandleAfterRender.OnAfterRenderAsync() { throw null; }

src/Components/Components/src/ILazyLoader.cs

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/Components/Components/src/Routing/OnNavigateArgs.cs renamed to src/Components/Components/src/Routing/NavigationContext.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010

1111
namespace Microsoft.AspNetCore.Components.Routing
1212
{
13-
public class OnNavigateArgs
13+
public class NavigationContext
1414
{
15-
public OnNavigateArgs(string path, CancellationTokenSource cancellationTokenSource)
15+
internal NavigationContext(string path, CancellationTokenSource cancellationTokenSource)
1616
{
1717
Path = path;
1818
CancellationTokenSource = cancellationTokenSource;

src/Components/Components/src/Routing/Router.cs

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,14 @@ static readonly ReadOnlyDictionary<string, object> _emptyParametersDictionary
6262
[Parameter] public RenderFragment<RouteData> Found { get; set; }
6363

6464
/// <summary>
65-
/// Get or sets the content to display when the upcoming route is loading.
65+
/// Get or sets the content to display when asynchronous navigation is in progress.
6666
/// </summary>
67-
[Parameter] public RenderFragment Loading { get; set; }
67+
[Parameter] public RenderFragment Navigating { get; set; }
6868

6969
/// <summary>
7070
/// Gets or sets a handler that should be called before navigating to a new page.
7171
/// </summary>
72-
[Parameter] public Func<OnNavigateArgs, Task> OnNavigateAsync { get; set; }
72+
[Parameter] public Func<NavigationContext, Task> OnNavigateAsync { get; set; }
7373

7474
private RouteTable Routes { get; set; }
7575

@@ -134,6 +134,18 @@ private void RefreshRouteTable()
134134
Routes = RouteTableFactory.Create(assemblies);
135135
_assemblies = assembliesSet;
136136
}
137+
// If the new assemblies set is the same length as the previous one,
138+
// we check if they are equal-by-value and refresh the route table if
139+
// not. NOTE: This is an uncommon occurrence and a cold-code path so
140+
// we shouldn't end up needing to do an equal-by-value check too frequently.
141+
if (_assemblies.Count == assembliesSet.Count)
142+
{
143+
if (!_assemblies.SetEquals(assembliesSet))
144+
{
145+
Routes = RouteTableFactory.Create(assemblies);
146+
_assemblies = assembliesSet;
147+
}
148+
}
137149
}
138150

139151
private void Refresh(bool isNavigationIntercepted)
@@ -189,32 +201,28 @@ private async Task RunOnNavigateAsync(string path, bool isNavigationIntercepted)
189201
return;
190202
}
191203

192-
// Create a new CTS for this invocation of the task and
193-
// extract the reference to the current one.
194-
var pendingOnNavigateCts = _onNavigateCts;
195-
var onNavigateCts = new CancellationTokenSource();
196-
_onNavigateCts = onNavigateCts;
197-
198-
var navigateContext = new OnNavigateArgs(path, _onNavigateCts);
199-
var task = OnNavigateAsync(navigateContext);
200-
201204
// If we've already invoked a task and stored its CTS, then
202205
// cancel the existing task.
203-
if (pendingOnNavigateCts != null)
206+
if (_onNavigateCts != null && !_onNavigateCts.IsCancellationRequested)
204207
{
205-
pendingOnNavigateCts.Cancel();
208+
_onNavigateCts.Cancel();
206209
}
210+
// Create a new cancellation token source for this instance
211+
_onNavigateCts = new CancellationTokenSource();
212+
213+
var navigateContext = new NavigationContext(path, _onNavigateCts);
214+
var task = OnNavigateAsync(navigateContext);
207215

208216
// Create a cancellation task based on the cancellation token
209217
// associated with the current running task.
210218
var cancellationTaskSource = new TaskCompletionSource();
211219
navigateContext.CancellationTokenSource.Token.Register(() =>
212220
cancellationTaskSource.TrySetCanceled(navigateContext.CancellationTokenSource.Token));
213221

214-
// If the user provided a Loading render fragment, then show it.
215-
if (Loading != null)
222+
// If the user provided a Navigating render fragment, then show it.
223+
if (Navigating != null && task.Status != TaskStatus.RanToCompletion)
216224
{
217-
_renderHandle.Render(Loading);
225+
_renderHandle.Render(Navigating);
218226
}
219227

220228
await Task.WhenAny(task, cancellationTaskSource.Task);

src/Components/Server/ref/Microsoft.AspNetCore.Components.Server.netcoreapp.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,6 @@ public ServerAuthenticationStateProvider() { }
4141
public override System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Authorization.AuthenticationState> GetAuthenticationStateAsync() { throw null; }
4242
public void SetAuthenticationState(System.Threading.Tasks.Task<Microsoft.AspNetCore.Components.Authorization.AuthenticationState> authenticationStateTask) { }
4343
}
44-
public partial class ServerLazyAssemblyLoader : Microsoft.AspNetCore.Components.ILazyLoader
45-
{
46-
public ServerLazyAssemblyLoader() { }
47-
public System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<System.Reflection.Assembly>> LoadAssembliesAsync(System.Collections.Generic.IEnumerable<string> assembliesToLoad) { throw null; }
48-
}
4944
}
5045
namespace Microsoft.AspNetCore.Components.Server.Circuits
5146
{

src/Components/Server/src/Circuits/ServerLazyAssemblyLoader.cs

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ public static IServerSideBlazorBuilder AddServerSideBlazor(this IServiceCollecti
7474
services.AddScoped<IJSRuntime, RemoteJSRuntime>();
7575
services.AddScoped<INavigationInterception, RemoteNavigationInterception>();
7676
services.AddScoped<AuthenticationStateProvider, ServerAuthenticationStateProvider>();
77-
services.TryAddSingleton<ILazyLoader, ServerLazyAssemblyLoader>();
7877

7978
services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<CircuitOptions>, CircuitOptionsJSInteropDetailedErrorsConfiguration>());
8079

src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,6 @@ internal void InitializeDefaultServices()
191191
Services.AddSingleton<IJSRuntime>(DefaultWebAssemblyJSRuntime.Instance);
192192
Services.AddSingleton<NavigationManager>(WebAssemblyNavigationManager.Instance);
193193
Services.AddSingleton<INavigationInterception>(WebAssemblyNavigationInterception.Instance);
194-
Services.AddSingleton<ILazyLoader>(new WebAssemblyLazyAssemblyLoader(DefaultWebAssemblyJSRuntime.Instance));
195194
Services.AddLogging(builder => {
196195
builder.AddProvider(new WebAssemblyConsoleLoggerProvider(DefaultWebAssemblyJSRuntime.Instance));
197196
});

src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyLazyAssemblyLoader.cs renamed to src/Components/WebAssembly/WebAssembly/src/Services/LazyAssemblyLoader.cs

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,60 @@
77
using System.Linq;
88
using System.Reflection;
99
using System.Threading.Tasks;
10+
using System.Runtime.InteropServices;
1011
using System.Runtime.Loader;
1112
using Microsoft.JSInterop.WebAssembly;
13+
using Microsoft.Extensions.DependencyInjection;
14+
using Microsoft.JSInterop;
1215

13-
namespace Microsoft.AspNetCore.Components.WebAssembly
16+
namespace Microsoft.AspNetCore.Components.WebAssembly.Services
1417
{
15-
public class WebAssemblyLazyAssemblyLoader : ILazyLoader
18+
public class LazyAssemblyLoader
1619
{
1720
internal const string GetDynamicAssemblies = "window.Blazor._internal.getLazyAssemblies";
1821
internal const string ReadDynamicAssemblies = "window.Blazor._internal.readLazyAssemblies";
1922

2023
private List<string> _loadedAssemblyCache = new List<string>();
2124

22-
private readonly WebAssemblyJSRuntime _jsRuntime;
25+
private readonly IServiceProvider _provider;
2326

24-
internal WebAssemblyLazyAssemblyLoader(WebAssemblyJSRuntime jsRuntime)
27+
public LazyAssemblyLoader(IServiceProvider provider)
2528
{
26-
_jsRuntime = jsRuntime;
29+
_provider = provider;
2730
}
2831

2932
public async Task<IEnumerable<Assembly>> LoadAssembliesAsync(IEnumerable<string> assembliesToLoad)
3033
{
34+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Browser))
35+
{
36+
return await LoadAssembliesInClientAsync(assembliesToLoad);
37+
}
38+
39+
return await LoadAssembliesInServerAsync(assembliesToLoad);
40+
}
41+
42+
private Task<IEnumerable<Assembly>> LoadAssembliesInServerAsync(IEnumerable<string> assembliesToLoad)
43+
{
44+
var assemblies = AppDomain.CurrentDomain.GetAssemblies().Where(assembly =>
45+
assembliesToLoad.Contains(assembly.GetName().Name + ".dll"));
46+
47+
if (assemblies.Count() != assembliesToLoad.Count())
48+
{
49+
var unloadedAssemblies = assembliesToLoad.Except(assemblies.Select(assembly => assembly.GetName().Name + ".dll"));
50+
throw new FileNotFoundException($"Unable to find the following assemblies: {string.Join(",", unloadedAssemblies)}");
51+
}
52+
53+
return Task.FromResult(assemblies);
54+
}
55+
56+
private async Task<IEnumerable<Assembly>> LoadAssembliesInClientAsync(IEnumerable<string> assembliesToLoad)
57+
{
58+
var _jsRuntime = _provider.GetRequiredService<IJSRuntime>();
3159
// Only load assemblies that haven't already been lazily-loaded
3260
var newAssembliesToLoad = assembliesToLoad.Where(assembly => !_loadedAssemblyCache.Contains(assembly));
3361
var loadedAssemblies = new List<Assembly>();
3462

35-
var count = (int)await _jsRuntime.InvokeUnmarshalled<string[], object, object, Task<object>>(
63+
var count = (int)await ((WebAssemblyJSRuntime)_jsRuntime).InvokeUnmarshalled<string[], object, object, Task<object>>(
3664
GetDynamicAssemblies,
3765
assembliesToLoad.ToArray(),
3866
null,
@@ -43,7 +71,7 @@ public async Task<IEnumerable<Assembly>> LoadAssembliesAsync(IEnumerable<string>
4371
return loadedAssemblies;
4472
}
4573

46-
var assemblies = _jsRuntime.InvokeUnmarshalled<object, object, object, object[]>(
74+
var assemblies = ((WebAssemblyJSRuntime)_jsRuntime).InvokeUnmarshalled<object, object, object, object[]>(
4775
ReadDynamicAssemblies,
4876
null,
4977
null,

src/Components/test/E2ETest/ServerExecutionTests/PrerenderingTest.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,18 @@ public void CanUseJSInteropFromOnAfterRenderAsync()
6060
Browser.Equal("Hello from interop call", () => Browser.FindElement(By.Id("val-set-by-interop")).GetAttribute("value"));
6161
}
6262

63+
[Fact]
64+
public void IsCompatibleWithLazyLoadWebAssembly()
65+
{
66+
Navigate("/prerendered/WithLazyAssembly");
67+
68+
var button = Browser.FindElement(By.Id("use-package-button"));
69+
70+
button.Click();
71+
72+
AssertLogDoesNotContainCriticalMessages("Could not load file or assembly 'Newtonsoft.Json");
73+
}
74+
6375
[Fact]
6476
public void CanReadUrlHashOnlyOnceConnected()
6577
{
@@ -121,6 +133,19 @@ private void BeginInteractivity()
121133
Browser.FindElement(By.Id("load-boot-script")).Click();
122134
}
123135

136+
private void AssertLogDoesNotContainCriticalMessages(params string[] messages)
137+
{
138+
var log = Browser.Manage().Logs.GetLog(LogType.Browser);
139+
foreach (var message in messages)
140+
{
141+
Assert.DoesNotContain(log, entry =>
142+
{
143+
return entry.Level == LogLevel.Severe
144+
&& entry.Message.Contains(message);
145+
});
146+
}
147+
}
148+
124149
private void SignInAs(string userName, string roles, bool useSeparateTab = false) =>
125150
Browser.SignInAs(new Uri(_serverFixture.RootUri, "/prerendered/"), userName, roles, useSeparateTab);
126151
}

src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithLazyAssembly.razor

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
@using Microsoft.AspNetCore.Components.Routing
1+
@using Microsoft.AspNetCore.Components.Routing
22
@using System.Reflection
3+
@using Microsoft.AspNetCore.Components.WebAssembly.Services
34

4-
@inject ILazyLoader lazyLoader
5+
@inject LazyAssemblyLoader lazyLoader
56

67
<Router AppAssembly="@typeof(BasicTestApp.Program).Assembly" AdditionalAssemblies="@lazyLoadedAssemblies" OnNavigateAsync="@OnNavigateAsync">
7-
<Loading>
8+
<Navigating>
89
<div style="padding: 20px;background-color:blue;color:white;" id="loading-banner">
910
<p>Loading the requested page...</p>
1011
</div>
11-
</Loading>
12+
</Navigating>
1213
<Found Context="routeData">
1314
<RouteView RouteData="@routeData" />
1415
</Found>
@@ -22,7 +23,7 @@
2223
@code {
2324
private List<Assembly> lazyLoadedAssemblies = new List<Assembly>();
2425

25-
private async Task OnNavigateAsync(OnNavigateArgs args)
26+
private async Task OnNavigateAsync(NavigationContext args)
2627
{
2728
Console.WriteLine($"Running OnNavigate for {args.Path}...");
2829
await LoadAssemblies(args.Path);

src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithOnNavigate.razor

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
@using Microsoft.AspNetCore.Components.Routing
1+
@using Microsoft.AspNetCore.Components.Routing
22

33
<Router AppAssembly="@typeof(BasicTestApp.Program).Assembly" OnNavigateAsync="@OnNavigateAsync">
4-
<Loading>
4+
<Navigating>
55
<div style="padding: 20px;background-color:blue;color:white;" id="loading-banner">
66
<p>Loading the requested page...</p>
77
</div>
8-
</Loading>
8+
</Navigating>
99
<Found Context="routeData">
1010
<RouteView RouteData="@routeData" />
1111
</Found>
@@ -17,28 +17,28 @@
1717
</Router>
1818

1919
@code {
20-
private Dictionary<string, Func<OnNavigateArgs, Task>> preNavigateTasks = new Dictionary<string, Func<OnNavigateArgs, Task>>()
20+
private Dictionary<string, Func<NavigationContext, Task>> preNavigateTasks = new Dictionary<string, Func<NavigationContext, Task>>()
2121
{
22-
{ "LongPage1", new Func<OnNavigateArgs, Task>(TestLoadingPageShows) },
23-
{ "LongPage2", new Func<OnNavigateArgs, Task>(TestOnNavCancel) }
22+
{ "LongPage1", new Func<NavigationContext, Task>(TestLoadingPageShows) },
23+
{ "LongPage2", new Func<NavigationContext, Task>(TestOnNavCancel) }
2424
};
2525

26-
private async Task OnNavigateAsync(OnNavigateArgs args)
26+
private async Task OnNavigateAsync(NavigationContext args)
2727
{
2828
Console.WriteLine($"Running OnNavigate for {args.Path}...");
29-
Func<OnNavigateArgs, Task> task;
29+
Func<NavigationContext, Task> task;
3030
if (preNavigateTasks.TryGetValue(args.Path, out task))
3131
{
3232
await task.Invoke(args);
3333
}
3434
}
3535

36-
public static async Task TestLoadingPageShows(OnNavigateArgs args)
36+
public static async Task TestLoadingPageShows(NavigationContext args)
3737
{
3838
await Task.Delay(2000);
3939
}
4040

41-
public static async Task TestOnNavCancel(OnNavigateArgs args)
41+
public static async Task TestOnNavCancel(NavigationContext args)
4242
{
4343
await Task.Delay(2000, args.CancellationTokenSource.Token);
4444
Console.WriteLine("I'm not happening...");

0 commit comments

Comments
 (0)