Merge in 'release/5.0-preview8' changes

This commit is contained in:
dotnet-bot 2020-07-22 21:49:25 +00:00
commit c88efce9a3
7 changed files with 98 additions and 59 deletions

View File

@ -68,12 +68,12 @@ namespace Microsoft.AspNetCore.Components.Routing
/// <summary> /// <summary>
/// Get or sets the content to display when asynchronous navigation is in progress. /// Get or sets the content to display when asynchronous navigation is in progress.
/// </summary> /// </summary>
[Parameter] public RenderFragment Navigating { get; set; } [Parameter] public RenderFragment? Navigating { get; set; }
/// <summary> /// <summary>
/// Gets or sets a handler that should be called before navigating to a new page. /// Gets or sets a handler that should be called before navigating to a new page.
/// </summary> /// </summary>
[Parameter] public Func<NavigationContext, Task> OnNavigateAsync { get; set; } [Parameter] public Func<NavigationContext, Task>? OnNavigateAsync { get; set; }
private RouteTable Routes { get; set; } private RouteTable Routes { get; set; }
@ -195,10 +195,6 @@ namespace Microsoft.AspNetCore.Components.Routing
private async ValueTask<bool> RunOnNavigateAsync(string path, Task previousOnNavigate) private async ValueTask<bool> RunOnNavigateAsync(string path, Task previousOnNavigate)
{ {
if (OnNavigateAsync == null)
{
return true;
}
// Cancel the CTS instead of disposing it, since disposing does not // Cancel the CTS instead of disposing it, since disposing does not
// actually cancel and can cause unintended Object Disposed Exceptions. // actually cancel and can cause unintended Object Disposed Exceptions.
@ -210,6 +206,11 @@ namespace Microsoft.AspNetCore.Components.Routing
// invocation. // invocation.
await previousOnNavigate; await previousOnNavigate;
if (OnNavigateAsync == null)
{
return true;
}
_onNavigateCts = new CancellationTokenSource(); _onNavigateCts = new CancellationTokenSource();
var navigateContext = new NavigationContext(path, _onNavigateCts.Token); var navigateContext = new NavigationContext(path, _onNavigateCts.Token);
@ -227,14 +228,12 @@ namespace Microsoft.AspNetCore.Components.Routing
if (e.CancellationToken != navigateContext.CancellationToken) if (e.CancellationToken != navigateContext.CancellationToken)
{ {
var rethrownException = new InvalidOperationException("OnNavigateAsync can only be cancelled via NavigateContext.CancellationToken.", e); var rethrownException = new InvalidOperationException("OnNavigateAsync can only be cancelled via NavigateContext.CancellationToken.", e);
_renderHandle.Render(builder => ExceptionDispatchInfo.Capture(rethrownException).Throw()); _renderHandle.Render(builder => ExceptionDispatchInfo.Throw(rethrownException));
return false;
} }
} }
catch (Exception e) catch (Exception e)
{ {
_renderHandle.Render(builder => ExceptionDispatchInfo.Capture(e).Throw()); _renderHandle.Render(builder => ExceptionDispatchInfo.Throw(e));
return false;
} }
return false; return false;

File diff suppressed because one or more lines are too long

View File

@ -324,9 +324,18 @@ function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourceLoade
const assembliesToLoad = BINDING.mono_array_to_js_array<System_String, string>(assembliesToLoadDotNetArray); const assembliesToLoad = BINDING.mono_array_to_js_array<System_String, string>(assembliesToLoadDotNetArray);
const lazyAssemblies = resourceLoader.bootConfig.resources.lazyAssembly; const lazyAssemblies = resourceLoader.bootConfig.resources.lazyAssembly;
if (lazyAssemblies) { if (!lazyAssemblies) {
const resourcePromises = Promise.all(assembliesToLoad throw new Error("No assemblies have been marked as lazy-loadable. Use the 'BlazorWebAssemblyLazyLoad' item group in your project file to enable lazy loading an assembly.");
.filter(assembly => lazyAssemblies.hasOwnProperty(assembly)) }
var assembliesMarkedAsLazy = assembliesToLoad.filter(assembly => lazyAssemblies.hasOwnProperty(assembly));
if (assembliesMarkedAsLazy.length != assembliesToLoad.length) {
var notMarked = assembliesToLoad.filter(assembly => !assembliesMarkedAsLazy.includes(assembly));
throw new Error(`${notMarked.join()} must be marked with 'BlazorWebAssemblyLazyLoad' item group in your project file to allow lazy-loading.`);
}
const resourcePromises = Promise.all(assembliesMarkedAsLazy
.map(assembly => resourceLoader.loadResource(assembly, `_framework/${assembly}`, lazyAssemblies[assembly], 'assembly')) .map(assembly => resourceLoader.loadResource(assembly, `_framework/${assembly}`, lazyAssemblies[assembly], 'assembly'))
.map(async resource => (await resource.response).arrayBuffer())); .map(async resource => (await resource.response).arrayBuffer()));
@ -345,8 +354,6 @@ function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourceLoade
return resourcesToLoad.length; return resourcesToLoad.length;
})); }));
} }
return BINDING.js_to_mono_obj(Promise.resolve(0));
}
}); });
module.postRun.push(() => { module.postRun.push(() => {

View File

@ -130,7 +130,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
/// <summary> /// <summary>
/// Gets the logging builder for configuring logging services. /// Gets the logging builder for configuring logging services.
/// </summary> /// </summary>
public ILoggingBuilder Logging { get; } public ILoggingBuilder Logging { get; }
/// <summary> /// <summary>
/// Registers a <see cref="IServiceProviderFactory{TBuilder}" /> instance to be used to create the <see cref="IServiceProvider" />. /// Registers a <see cref="IServiceProviderFactory{TBuilder}" /> instance to be used to create the <see cref="IServiceProvider" />.
@ -189,7 +189,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
Services.AddSingleton<IJSRuntime>(DefaultWebAssemblyJSRuntime.Instance); Services.AddSingleton<IJSRuntime>(DefaultWebAssemblyJSRuntime.Instance);
Services.AddSingleton<NavigationManager>(WebAssemblyNavigationManager.Instance); Services.AddSingleton<NavigationManager>(WebAssemblyNavigationManager.Instance);
Services.AddSingleton<INavigationInterception>(WebAssemblyNavigationInterception.Instance); Services.AddSingleton<INavigationInterception>(WebAssemblyNavigationInterception.Instance);
Services.AddSingleton(provider => new LazyAssemblyLoader(provider)); Services.AddSingleton(new LazyAssemblyLoader(DefaultWebAssemblyJSRuntime.Instance));
Services.AddLogging(builder => { Services.AddLogging(builder => {
builder.AddProvider(new WebAssemblyConsoleLoggerProvider(DefaultWebAssemblyJSRuntime.Instance)); builder.AddProvider(new WebAssemblyConsoleLoggerProvider(DefaultWebAssemblyJSRuntime.Instance));
}); });

View File

@ -9,7 +9,6 @@ using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Loader; using System.Runtime.Loader;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using Microsoft.JSInterop.WebAssembly; using Microsoft.JSInterop.WebAssembly;
@ -20,19 +19,18 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Services
/// ///
/// Supports finding pre-loaded assemblies in a server or pre-rendering context. /// Supports finding pre-loaded assemblies in a server or pre-rendering context.
/// </summary> /// </summary>
public class LazyAssemblyLoader public sealed class LazyAssemblyLoader
{ {
internal const string GetDynamicAssemblies = "window.Blazor._internal.getLazyAssemblies"; internal const string GetDynamicAssemblies = "window.Blazor._internal.getLazyAssemblies";
internal const string ReadDynamicAssemblies = "window.Blazor._internal.readLazyAssemblies"; internal const string ReadDynamicAssemblies = "window.Blazor._internal.readLazyAssemblies";
private List<Assembly> _loadedAssemblyCache = new List<Assembly>(); private readonly IJSRuntime _jsRuntime;
private readonly HashSet<string> _loadedAssemblyCache;
private readonly IServiceProvider _provider; public LazyAssemblyLoader(IJSRuntime jsRuntime)
public LazyAssemblyLoader(IServiceProvider provider)
{ {
_provider = provider; _jsRuntime = jsRuntime;
_loadedAssemblyCache = AppDomain.CurrentDomain.GetAssemblies().ToList(); _loadedAssemblyCache = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name + ".dll").ToHashSet();
} }
/// <summary> /// <summary>
@ -55,37 +53,45 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Services
private Task<IEnumerable<Assembly>> LoadAssembliesInServerAsync(IEnumerable<string> assembliesToLoad) private Task<IEnumerable<Assembly>> LoadAssembliesInServerAsync(IEnumerable<string> assembliesToLoad)
{ {
var loadedAssemblies = _loadedAssemblyCache.Where(assembly => var loadedAssemblies = new List<Assembly>();
assembliesToLoad.Contains(assembly.GetName().Name + ".dll"));
if (loadedAssemblies.Count() != assembliesToLoad.Count()) try
{ {
var unloadedAssemblies = assembliesToLoad.Except(loadedAssemblies.Select(a => a.GetName().Name + ".dll")); foreach (var assemblyName in assembliesToLoad)
throw new InvalidOperationException($"Unable to find the following assemblies: {string.Join(",", unloadedAssemblies)}. Make sure that the appplication is referencing the assemblies and that they are present in the output folder."); {
loadedAssemblies.Add(Assembly.Load(Path.GetFileNameWithoutExtension(assemblyName)));
}
}
catch (FileNotFoundException ex)
{
throw new InvalidOperationException($"Unable to find the following assembly: {ex.FileName}. Make sure that the appplication is referencing the assemblies and that they are present in the output folder.");
} }
return Task.FromResult(loadedAssemblies); return Task.FromResult<IEnumerable<Assembly>>(loadedAssemblies);
} }
private async Task<IEnumerable<Assembly>> LoadAssembliesInClientAsync(IEnumerable<string> assembliesToLoad) private async Task<IEnumerable<Assembly>> LoadAssembliesInClientAsync(IEnumerable<string> assembliesToLoad)
{ {
var jsRuntime = _provider.GetRequiredService<IJSRuntime>(); // Check to see if the assembly has already been loaded and avoids reloading it if so.
// Only load assemblies that haven't already been lazily-loaded // Note: in the future, as an extra precuation, we can call `Assembly.Load` and check
var newAssembliesToLoad = assembliesToLoad.Except(_loadedAssemblyCache.Select(a => a.GetName().Name + ".dll")); // to see if it throws FileNotFound to ensure that an assembly hasn't been loaded
// between when the cache of loaded assemblies was instantiated in the constructor
// and the invocation of this method.
var newAssembliesToLoad = assembliesToLoad.Where(assembly => !_loadedAssemblyCache.Contains(assembly));
var loadedAssemblies = new List<Assembly>(); var loadedAssemblies = new List<Assembly>();
var count = (int)await ((WebAssemblyJSRuntime)jsRuntime).InvokeUnmarshalled<string[], object, object, Task<object>>( var count = (int)await ((WebAssemblyJSRuntime)_jsRuntime).InvokeUnmarshalled<string[], object, object, Task<object>>(
GetDynamicAssemblies, GetDynamicAssemblies,
assembliesToLoad.ToArray(), newAssembliesToLoad.ToArray(),
null, null,
null); null);
if (count == 0) if (count == 0)
{ {
return loadedAssemblies; return loadedAssemblies;
} }
var assemblies = ((WebAssemblyJSRuntime)jsRuntime).InvokeUnmarshalled<object, object, object, object[]>( var assemblies = ((WebAssemblyJSRuntime)_jsRuntime).InvokeUnmarshalled<object, object, object, object[]>(
ReadDynamicAssemblies, ReadDynamicAssemblies,
null, null,
null, null,
@ -99,7 +105,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Services
// into the default app context. // into the default app context.
var loadedAssembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(assembly)); var loadedAssembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(assembly));
loadedAssemblies.Add(loadedAssembly); loadedAssemblies.Add(loadedAssembly);
_loadedAssemblyCache.Add(loadedAssembly); _loadedAssemblyCache.Add(loadedAssembly.GetName().Name + ".dll");
} }
return loadedAssemblies; return loadedAssemblies;

View File

@ -111,6 +111,21 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
Assert.True(renderedElement.Displayed); Assert.True(renderedElement.Displayed);
} }
[Fact]
public void ThrowsErrorForUnavailableAssemblies()
{
// Navigate to a page with lazy loaded assemblies for the first time
SetUrlViaPushState("/Other");
var app = Browser.MountTestComponent<TestRouterWithLazyAssembly>();
// Should've thrown an error for unhandled error
var errorUiElem = Browser.Exists(By.Id("blazor-error-ui"), TimeSpan.FromSeconds(10));
Assert.NotNull(errorUiElem);
AssertLogContainsCriticalMessages("DoesNotExist.dll must be marked with 'BlazorWebAssemblyLazyLoad' item group in your project file to allow lazy-loading.");
}
private string SetUrlViaPushState(string relativeUri) private string SetUrlViaPushState(string relativeUri)
{ {
var pathBaseWithoutHash = ServerPathBase.Split('#')[0]; var pathBaseWithoutHash = ServerPathBase.Split('#')[0];
@ -145,5 +160,18 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
}); });
} }
} }
void AssertLogContainsCriticalMessages(params string[] messages)
{
var log = Browser.Manage().Logs.GetLog(LogType.Browser);
foreach (var message in messages)
{
Assert.Contains(log, entry =>
{
return entry.Level == LogLevel.Severe
&& entry.Message.Contains(message);
});
}
}
} }
} }

View File

@ -1,6 +1,6 @@
@using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Routing
@using System.Reflection @using System.Reflection
@using Microsoft.AspNetCore.Components.WebAssembly.Services @using Microsoft.AspNetCore.Components.WebAssembly.Services
@inject LazyAssemblyLoader lazyLoader @inject LazyAssemblyLoader lazyLoader
@ -31,25 +31,24 @@
private async Task LoadAssemblies(string uri) private async Task LoadAssemblies(string uri)
{ {
try if (uri.EndsWith("WithLazyAssembly"))
{ {
if (uri.EndsWith("WithLazyAssembly")) Console.WriteLine($"Loading assemblies for WithLazyAssembly...");
{ var assemblies = await lazyLoader.LoadAssembliesAsync(new List<string>() { "Newtonsoft.Json.dll" });
Console.WriteLine($"Loading assemblies for WithLazyAssembly..."); lazyLoadedAssemblies.AddRange(assemblies);
var assemblies = await lazyLoader.LoadAssembliesAsync(new List<string>() { "Newtonsoft.Json.dll" });
lazyLoadedAssemblies.AddRange(assemblies);
}
if (uri.EndsWith("WithLazyLoadedRoutes"))
{
Console.WriteLine($"Loading assemblies for WithLazyLoadedRoutes...");
var assemblies = await lazyLoader.LoadAssembliesAsync(new List<string>() { "LazyTestContentPackage.dll" });
lazyLoadedAssemblies.AddRange(assemblies);
}
} }
catch (Exception e)
if (uri.EndsWith("WithLazyLoadedRoutes"))
{ {
Console.WriteLine($"Error when loading assemblies: {e}"); Console.WriteLine($"Loading assemblies for WithLazyLoadedRoutes...");
var assemblies = await lazyLoader.LoadAssembliesAsync(new List<string>() { "LazyTestContentPackage.dll" });
lazyLoadedAssemblies.AddRange(assemblies);
}
if (uri.EndsWith("Other")) {
Console.WriteLine($"Loading assemblies for Other...");
var assemblies = await lazyLoader.LoadAssembliesAsync(new List<string>() { "DoesNotExist.dll" });
lazyLoadedAssemblies.AddRange(assemblies);
} }
} }
} }