Skip to content

Commit fa9f6f0

Browse files
committed
Refactor RouteTableFactory and add WebAssemblyDynamicResourceLoader
1 parent 2b313d5 commit fa9f6f0

File tree

14 files changed

+154
-105
lines changed

14 files changed

+154
-105
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,8 @@ public Router() { }
555555
public Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.RouteData> Found { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
556556
[Microsoft.AspNetCore.Components.ParameterAttribute]
557557
public Microsoft.AspNetCore.Components.RenderFragment NotFound { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
558+
[Microsoft.AspNetCore.Components.ParameterAttribute]
559+
public System.Func<string, bool> OnNavigate { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } }
558560
public void Attach(Microsoft.AspNetCore.Components.RenderHandle renderHandle) { }
559561
public void Dispose() { }
560562
System.Threading.Tasks.Task Microsoft.AspNetCore.Components.IHandleAfterRender.OnAfterRenderAsync() { throw null; }

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ static readonly ReadOnlyDictionary<string, object> _emptyParametersDictionary
2929
bool _navigationInterceptionEnabled;
3030
ILogger<Router> _logger;
3131

32+
private bool initialOnNavigateCalled = false;
33+
3234
[Inject] private NavigationManager NavigationManager { get; set; }
3335

3436
[Inject] private INavigationInterception NavigationInterception { get; set; }
@@ -56,6 +58,11 @@ static readonly ReadOnlyDictionary<string, object> _emptyParametersDictionary
5658
/// </summary>
5759
[Parameter] public RenderFragment<RouteData> Found { get; set; }
5860

61+
/// <summary>
62+
/// Gets or sets a handler that should be called before navigating to a new page.
63+
/// </summary>
64+
[Parameter] public Func<string, bool> OnNavigate { get; set; }
65+
5966
private RouteTable Routes { get; set; }
6067

6168
/// <inheritdoc />
@@ -93,10 +100,21 @@ public Task SetParametersAsync(ParameterView parameters)
93100
throw new InvalidOperationException($"The {nameof(Router)} component requires a value for the parameter {nameof(NotFound)}.");
94101
}
95102

96-
97103
var assemblies = AdditionalAssemblies == null ? new[] { AppAssembly } : new[] { AppAssembly }.Concat(AdditionalAssemblies);
98104
Routes = RouteTableFactory.Create(assemblies);
105+
106+
// If we're about to render the router for the first time, then
107+
// we need to call the `OnNavigate` handler to ensure that pre-processing
108+
// steps are completed before rendering the route. This way, it will work
109+
// if you navigate to /PageWithLazyLoadedAssemblies or visit it for the first time.
110+
if (OnNavigate != null && !initialOnNavigateCalled) {
111+
OnNavigate(NavigationManager.ToBaseRelativePath(_locationAbsolute));
112+
initialOnNavigateCalled = true;
113+
}
114+
99115
Refresh(isNavigationIntercepted: false);
116+
117+
100118
return Task.CompletedTask;
101119
}
102120

@@ -160,6 +178,12 @@ private void OnLocationChanged(object sender, LocationChangedEventArgs args)
160178
_locationAbsolute = args.Location;
161179
if (_renderHandle.IsInitialized && Routes != null)
162180
{
181+
if (OnNavigate != null) {
182+
var continueRender = OnNavigate(NavigationManager.ToBaseRelativePath(_locationAbsolute));
183+
if (!continueRender) {
184+
return;
185+
}
186+
}
163187
Refresh(args.IsNavigationIntercepted);
164188
}
165189
}

src/Components/Web.JS/dist/Release/blazor.webassembly.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -168,11 +168,6 @@ public void ConfigureContainer<TBuilder>(IServiceProviderFactory<TBuilder> facto
168168
};
169169
}
170170

171-
public void SetLazyLoadDefinition(WebAssemblyLazyLoadDefinition lazyLoadDefinition)
172-
{
173-
WebAssemblyNavigationManager.Instance.LazyLoadDefinition = lazyLoadDefinition;
174-
}
175-
176171
/// <summary>
177172
/// Builds a <see cref="WebAssemblyHost"/> instance based on the configuration of this builder.
178173
/// </summary>
@@ -196,6 +191,7 @@ internal void InitializeDefaultServices()
196191
Services.AddSingleton<IJSRuntime>(DefaultWebAssemblyJSRuntime.Instance);
197192
Services.AddSingleton<NavigationManager>(WebAssemblyNavigationManager.Instance);
198193
Services.AddSingleton<INavigationInterception>(WebAssemblyNavigationInterception.Instance);
194+
Services.AddSingleton<WebAssemblyDynamicResourceLoader>(new WebAssemblyDynamicResourceLoader(DefaultWebAssemblyJSRuntime.Instance));
199195
Services.AddLogging(builder => {
200196
builder.AddProvider(new WebAssemblyConsoleLoggerProvider(DefaultWebAssemblyJSRuntime.Instance));
201197
});

src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyLazyLoadDefinition.cs

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

src/Components/WebAssembly/WebAssembly/src/Infrastructure/JSInteropMethods.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ public static class JSInteropMethods
2222
/// For framework use only.
2323
/// </summary>
2424
[JSInvokable(nameof(NotifyLocationChanged))]
25-
public static Task NotifyLocationChanged(string uri, bool isInterceptedLink)
25+
public static void NotifyLocationChanged(string uri, bool isInterceptedLink)
2626
{
27-
return WebAssemblyNavigationManager.Instance.SetLocation(uri, isInterceptedLink);
27+
WebAssemblyNavigationManager.Instance.SetLocation(uri, isInterceptedLink);
2828
}
2929

3030
/// <summary>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.IO;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Reflection;
9+
using System.Threading.Tasks;
10+
using System.Runtime.Loader;
11+
using Microsoft.JSInterop.WebAssembly;
12+
13+
namespace Microsoft.AspNetCore.Components.WebAssembly.Services
14+
{
15+
public class WebAssemblyDynamicResourceLoader
16+
{
17+
internal const string GetDynamicAssemblies = "window.Blazor._internal.getDynamicAssemblies";
18+
internal const string ReadDynamicAssemblies = "window.Blazor._internal.readDynamicAssemblies";
19+
20+
private static List<string> _loadedAssemblyCache = new List<string>();
21+
22+
private readonly WebAssemblyJSRuntime _jsRuntime;
23+
24+
internal WebAssemblyDynamicResourceLoader(WebAssemblyJSRuntime jsRuntime)
25+
{
26+
_jsRuntime = jsRuntime;
27+
}
28+
29+
public async Task<IEnumerable<Assembly>> LoadDynamicAssemblies(IEnumerable<string> assembliesToLoad)
30+
{
31+
// Only load assemblies that haven't already been lazily-loaded
32+
var newAssembliesToLoad = assembliesToLoad.Where(assembly => !_loadedAssemblyCache.Contains(assembly));
33+
var loadedAssemblies = new List<Assembly>();
34+
35+
var count = (int)await _jsRuntime.InvokeUnmarshalled<string[], object, object, Task<object>>(
36+
GetDynamicAssemblies,
37+
assembliesToLoad.ToArray(),
38+
null,
39+
null);
40+
41+
if (count == 0)
42+
{
43+
return loadedAssemblies;
44+
}
45+
46+
var assemblies = _jsRuntime.InvokeUnmarshalled<object, object, object, object[]>(
47+
ReadDynamicAssemblies,
48+
null,
49+
null,
50+
null);
51+
52+
foreach (byte[] assembly in assemblies)
53+
{
54+
// The runtime loads assemblies into an isolated context by default. As a result,
55+
// assemblies that are loaded via Assembly.Load aren't available in the app's context
56+
// AKA the default context. To work around this, we explicitly load the assemblies
57+
// into the default app context.
58+
var loadedAssembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(assembly));
59+
loadedAssemblies.Add(loadedAssembly);
60+
_loadedAssemblyCache.Add(loadedAssembly.GetName().Name + ".dll");
61+
}
62+
63+
return loadedAssemblies;
64+
}
65+
}
66+
}

src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationManager.cs

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Collections.Generic;
6-
using System.Linq;
7-
using System.Reflection;
8-
using System.Threading.Tasks;
9-
using Microsoft.AspNetCore.Components.Routing;
10-
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
5+
using Microsoft.AspNetCore.Components;
116
using Interop = Microsoft.AspNetCore.Components.Web.BrowserNavigationManagerInterop;
127

138
namespace Microsoft.AspNetCore.Components.WebAssembly.Services
@@ -22,17 +17,14 @@ internal class WebAssemblyNavigationManager : NavigationManager
2217
/// </summary>
2318
public static WebAssemblyNavigationManager Instance { get; set; }
2419

25-
public WebAssemblyLazyLoadDefinition LazyLoadDefinition { get; set; }
26-
2720
public WebAssemblyNavigationManager(string baseUri, string uri)
2821
{
2922
Initialize(baseUri, uri);
3023
}
3124

32-
public async Task SetLocation(string uri, bool isInterceptedLink)
25+
public void SetLocation(string uri, bool isInterceptedLink)
3326
{
3427
Uri = uri;
35-
await BeforeLocationChangeAsync();
3628
NotifyLocationChanged(isInterceptedLink);
3729
}
3830

@@ -46,42 +38,5 @@ protected override void NavigateToCore(string uri, bool forceLoad)
4638

4739
DefaultWebAssemblyJSRuntime.Instance.Invoke<object>(Interop.NavigateTo, uri, forceLoad);
4840
}
49-
50-
public async Task BeforeLocationChangeAsync()
51-
{
52-
if (LazyLoadDefinition == null) {
53-
return;
54-
}
55-
56-
var path = Uri.Replace(BaseUri, "");
57-
var assembliesToLoad = LazyLoadDefinition.GetLazyAssembliesForRoute(path);
58-
59-
if (assembliesToLoad == null)
60-
{
61-
return;
62-
}
63-
64-
var count = (int)await DefaultWebAssemblyJSRuntime.Instance.InvokeUnmarshalled<string[], object, object, Task<object>>(
65-
"window.Blazor._internal.getDynamicAssemblies",
66-
assembliesToLoad.ToArray(),
67-
null,
68-
null);
69-
70-
if (count == 0)
71-
{
72-
return;
73-
}
74-
75-
var assemblies = DefaultWebAssemblyJSRuntime.Instance.InvokeUnmarshalled<object, object, object, object[]>(
76-
"window.Blazor._internal.readDynamicAssemblies",
77-
null,
78-
null,
79-
null);
80-
81-
foreach (byte[] assembly in assemblies)
82-
{
83-
Assembly.Load(assembly);
84-
}
85-
}
8641
}
8742
}

src/Components/test/E2ETest/Tests/RoutingTest.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -522,8 +522,6 @@ public void PreventDefault_CanBlockNavigation(string navigationType, string wher
522522
}
523523
}
524524

525-
526-
527525
private long BrowserScrollY
528526
{
529527
get => (long)((IJavaScriptExecutor)Browser).ExecuteScript("return window.scrollY");

src/Components/test/E2ETest/Tests/WebAssemblyLazyLoadTest.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using BasicTestApp;
6+
using BasicTestApp.RouterTest;
67
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
78
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
89
using Microsoft.AspNetCore.E2ETesting;
@@ -25,7 +26,7 @@ public WebAssemblyLazyLoadTest(
2526
protected override void InitializeAsyncCore()
2627
{
2728
Navigate(ServerPathBase, noReload: false);
28-
Browser.MountTestComponent<TestRouter>();
29+
Browser.MountTestComponent<TestRouterWithDynamicAssembly>();
2930
Browser.Exists(By.Id("blazor-error-ui"));
3031

3132
var errorUi = Browser.FindElement(By.Id("blazor-error-ui"));
@@ -37,7 +38,7 @@ public void CanLazyLoadOnRouteChange()
3738
{
3839
// Navigate to a page without any lazy-loaded dependencies
3940
SetUrlViaPushState("/");
40-
var app = Browser.MountTestComponent<TestRouter>();
41+
var app = Browser.MountTestComponent<TestRouterWithDynamicAssembly>();
4142

4243
// Ensure that we haven't requested the lazy loaded assembly
4344
Assert.False(HasLoadedAssembly("Newtonsoft.Json.dll"));
@@ -61,7 +62,7 @@ public void CanLazyLoadOnFirstVisit()
6162
{
6263
// Navigate to a page with lazy loaded assemblies for the first time
6364
SetUrlViaPushState("/WithDynamicAssembly");
64-
var app = Browser.MountTestComponent<TestRouter>();
65+
var app = Browser.MountTestComponent<TestRouterWithDynamicAssembly>();
6566
var button = app.FindElement(By.Id("use-package-button"));
6667

6768
// We should have requested the DLL

src/Components/test/testassets/BasicTestApp/Index.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
<option value="BasicTestApp.ReorderingFocusComponent">Reordering focus retention</option>
7070
<option value="BasicTestApp.RouterTest.NavigationManagerComponent">NavigationManager Test</option>
7171
<option value="BasicTestApp.RouterTest.TestRouter">Router</option>
72+
<option value="BasicTestApp.RouterTest.TestRouterWithDynamicAssembly">Router with dynamic assembly</option>
7273
<option value="BasicTestApp.RouterTest.TestRouterWithAdditionalAssembly">Router with additional assembly</option>
7374
<option value="BasicTestApp.StringComparisonComponent">StringComparison</option>
7475
<option value="BasicTestApp.SvgComponent">SVG</option>

src/Components/test/testassets/BasicTestApp/Program.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
using Microsoft.Extensions.Logging;
1919
using Microsoft.Extensions.Logging.Configuration;
2020
using Microsoft.JSInterop;
21-
using System.Collections.Generic;
2221

2322
namespace BasicTestApp
2423
{
@@ -31,11 +30,6 @@ public static async Task Main(string[] args)
3130
var builder = WebAssemblyHostBuilder.CreateDefault(args);
3231
builder.RootComponents.Add<Index>("root");
3332

34-
// Set lazy-load definition for a single path
35-
WebAssemblyLazyLoadDefinition lazyLoadDefinition = new WebAssemblyLazyLoadDefinition();
36-
lazyLoadDefinition.AddRouteDefinition("WithDynamicAssembly", new List<string>() { "Newtonsoft.Json.dll" });
37-
builder.SetLazyLoadDefinition(lazyLoadDefinition);
38-
3933
builder.Services.AddSingleton(new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
4034
builder.Services.AddSingleton<AuthenticationStateProvider, ServerAuthenticationStateProvider>();
4135
builder.Services.AddAuthorizationCore(options =>

0 commit comments

Comments
 (0)