Merge branch 'blazor-wasm' => 'master' (#17824)

This commit is contained in:
Doug Bunting 2019-12-14 17:26:32 -08:00 committed by GitHub
commit 7842b96d9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 394 additions and 32 deletions

View File

@ -393,10 +393,15 @@
<Uri>https://github.com/dotnet/core-setup</Uri>
<Sha>6fab00563d09dca0d2b777a4f0dbda59d19c8546</Sha>
</Dependency>
<Dependency Name="Microsoft.NETCore.App.Internal" Version="5.0.0-alpha.1.19562.8" CoherentParentDependency="Microsoft.Extensions.Logging">
<Uri>https://github.com/dotnet/core-setup</Uri>
<Sha>f3f2dd583fffa254015fc21ff0e45784b333256d</Sha>
</Dependency>
<Dependency Name="NETStandard.Library.Ref" Version="2.1.0-alpha.1.19562.8" CoherentParentDependency="Microsoft.Extensions.Logging">
<Uri>https://github.com/dotnet/core-setup</Uri>
<Sha>6fab00563d09dca0d2b777a4f0dbda59d19c8546</Sha>
</Dependency>
<!-- Keep these dependencies at the bottom of ProductDependencies, else they will be picked as the parent for CoherentParentDependencies -->
<Dependency Name="Microsoft.Bcl.AsyncInterfaces" Version="1.0.0" Pinned="true">
<Uri>https://github.com/dotnet/corefx</Uri>
<Sha>4ac4c0367003fe3973a3648eb0715ddb0e3bbcea</Sha>

View File

@ -65,6 +65,7 @@
<MicrosoftNetCompilersToolsetPackageVersion>3.4.0-beta1-19456-03</MicrosoftNetCompilersToolsetPackageVersion>
<!-- Packages from dotnet/core-setup -->
<MicrosoftExtensionsDependencyModelPackageVersion>5.0.0-alpha.1.19562.8</MicrosoftExtensionsDependencyModelPackageVersion>
<MicrosoftNETCoreAppInternalPackageVersion>5.0.0-alpha.1.19562.8</MicrosoftNETCoreAppInternalPackageVersion>
<MicrosoftNETCoreAppRefPackageVersion>5.0.0-alpha.1.19562.8</MicrosoftNETCoreAppRefPackageVersion>
<MicrosoftNETCoreAppRuntimewinx64PackageVersion>5.0.0-alpha.1.19562.8</MicrosoftNETCoreAppRuntimewinx64PackageVersion>
<NETStandardLibraryRefPackageVersion>2.1.0-alpha.1.19562.8</NETStandardLibraryRefPackageVersion>

View File

@ -5,11 +5,11 @@
"tools": {
"dotnet": "5.0.100-alpha1-015752",
"runtimes": {
"dotnet": [
"$(MicrosoftNETCoreAppRuntimeVersion)"
"dotnet/x64": [
"$(MicrosoftNETCoreAppInternalPackageVersion)"
],
"dotnet/x86": [
"$(MicrosoftNETCoreAppRuntimeVersion)"
"$(MicrosoftNETCoreAppInternalPackageVersion)"
]
},
"Git": "2.22.0",

View File

@ -0,0 +1,89 @@
// 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;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Blazor.Hosting
{
internal static class EntrypointInvoker
{
// This method returns void because currently the JS side is not listening to any result,
// nor will it handle any exceptions. We handle all exceptions internally to this method.
// In the future we may want Blazor.start to return something that exposes the possibly-async
// entrypoint result to the JS caller. There's no requirement to do that today, and if we
// do change this it will be non-breaking.
public static void InvokeEntrypoint(string assemblyName, string[] args)
{
object entrypointResult;
try
{
var assembly = Assembly.Load(assemblyName);
var entrypoint = FindUnderlyingEntrypoint(assembly);
var @params = entrypoint.GetParameters().Length == 1 ? new object[] { args } : new object[] { };
entrypointResult = entrypoint.Invoke(null, @params);
}
catch (Exception syncException)
{
HandleStartupException(syncException);
return;
}
// If the entrypoint is async, handle async exceptions in the same way that we would
// have handled sync ones
if (entrypointResult is Task entrypointTask)
{
entrypointTask.ContinueWith(task =>
{
if (task.Exception != null)
{
HandleStartupException(task.Exception);
}
});
}
}
private static MethodBase FindUnderlyingEntrypoint(Assembly assembly)
{
// This is the entrypoint declared in .NET metadata. In the case of async main, it's the
// compiler-generated wrapper method. Otherwise it's the developer-defined method.
var metadataEntrypointMethodBase = assembly.EntryPoint;
// For "async Task Main", the C# compiler generates a method called "<Main>"
// that is marked as the assembly entrypoint. Detect this case, and instead of
// calling "<Whatever>", call the sibling "Whatever".
if (metadataEntrypointMethodBase.IsSpecialName)
{
var origName = metadataEntrypointMethodBase.Name;
var origNameLength = origName.Length;
if (origNameLength > 2)
{
var candidateMethodName = origName.Substring(1, origNameLength - 2);
var candidateMethod = metadataEntrypointMethodBase.DeclaringType.GetMethod(
candidateMethodName,
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
null,
metadataEntrypointMethodBase.GetParameters().Select(p => p.ParameterType).ToArray(),
null);
if (candidateMethod != null)
{
return candidateMethod;
}
}
}
// Either it's not async main, or for some reason we couldn't locate the underlying entrypoint,
// so use the one from assembly metadata.
return metadataEntrypointMethodBase;
}
private static void HandleStartupException(Exception exception)
{
// Logs to console, and causes the error UI to appear
Console.Error.WriteLine(exception);
}
}
}

View File

@ -0,0 +1,153 @@
// 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;
using System.IO;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Hosting
{
public class EntrypointInvokerTest
{
[Theory]
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(true, true)]
public void InvokesEntrypoint_Sync_Success(bool hasReturnValue, bool hasParams)
{
// Arrange
var returnType = hasReturnValue ? "int" : "void";
var paramsDecl = hasParams ? "string[] args" : string.Empty;
var returnStatement = hasReturnValue ? "return 123;" : "return;";
var assembly = CompileToAssembly(@"
static " + returnType + @" Main(" + paramsDecl + @")
{
DidMainExecute = true;
" + returnStatement + @"
}", out var didMainExecute);
// Act
EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { });
// Assert
Assert.True(didMainExecute());
}
[Theory]
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(true, true)]
public void InvokesEntrypoint_Async_Success(bool hasReturnValue, bool hasParams)
{
// Arrange
var returnTypeGenericParam = hasReturnValue ? "<int>" : string.Empty;
var paramsDecl = hasParams ? "string[] args" : string.Empty;
var returnStatement = hasReturnValue ? "return 123;" : "return;";
var assembly = CompileToAssembly(@"
public static TaskCompletionSource<object> ContinueTcs { get; } = new TaskCompletionSource<object>();
static async Task" + returnTypeGenericParam + @" Main(" + paramsDecl + @")
{
await ContinueTcs.Task;
DidMainExecute = true;
" + returnStatement + @"
}", out var didMainExecute);
// Act/Assert 1: Waits for task
// The fact that we're not blocking here proves that we're not executing the
// metadata-declared entrypoint, as that would block
EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { });
Assert.False(didMainExecute());
// Act/Assert 2: Continues
var tcs = (TaskCompletionSource<object>)assembly.GetType("SomeApp.Program").GetProperty("ContinueTcs").GetValue(null);
tcs.SetResult(null);
Assert.True(didMainExecute());
}
[Fact]
public void InvokesEntrypoint_Sync_Exception()
{
// Arrange
var assembly = CompileToAssembly(@"
public static void Main()
{
DidMainExecute = true;
throw new InvalidTimeZoneException(""Test message"");
}", out var didMainExecute);
// Act/Assert
// The fact that this doesn't throw shows that EntrypointInvoker is doing something
// to handle the exception. We can't assert about what it does here, because that
// would involve capturing console output, which isn't safe in unit tests. Instead
// we'll check this in E2E tests.
EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { });
Assert.True(didMainExecute());
}
[Fact]
public void InvokesEntrypoint_Async_Exception()
{
// Arrange
var assembly = CompileToAssembly(@"
public static TaskCompletionSource<object> ContinueTcs { get; } = new TaskCompletionSource<object>();
public static async Task Main()
{
await ContinueTcs.Task;
DidMainExecute = true;
throw new InvalidTimeZoneException(""Test message"");
}", out var didMainExecute);
// Act/Assert 1: Waits for task
EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { });
Assert.False(didMainExecute());
// Act/Assert 2: Continues
// As above, we can't directly observe the exception handling behavior here,
// so this is covered in E2E tests instead.
var tcs = (TaskCompletionSource<object>)assembly.GetType("SomeApp.Program").GetProperty("ContinueTcs").GetValue(null);
tcs.SetResult(null);
Assert.True(didMainExecute());
}
private static Assembly CompileToAssembly(string mainMethod, out Func<bool> didMainExecute)
{
var syntaxTree = CSharpSyntaxTree.ParseText(@"
using System;
using System.Threading.Tasks;
namespace SomeApp
{
public static class Program
{
public static bool DidMainExecute { get; private set; }
" + mainMethod + @"
}
}");
var compilation = CSharpCompilation.Create(
$"TestAssembly-{Guid.NewGuid().ToString("D")}",
new[] { syntaxTree },
new[] { MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location) },
new CSharpCompilationOptions(OutputKind.ConsoleApplication));
using var ms = new MemoryStream();
var compilationResult = compilation.Emit(ms);
ms.Seek(0, SeekOrigin.Begin);
var assembly = AssemblyLoadContext.Default.LoadFromStream(ms);
var didMainExecuteProp = assembly.GetType("SomeApp.Program").GetProperty("DidMainExecute");
didMainExecute = () => (bool)didMainExecuteProp.GetValue(null);
return assembly;
}
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@ -6,6 +6,7 @@
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Blazor" />
<Reference Include="Microsoft.CodeAnalysis.CSharp" />
</ItemGroup>
</Project>

View File

@ -168,7 +168,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Tasks
protected override bool HandleTaskExecutionErrors()
{
// Show a slightly better error than the standard ToolTask message that says "dotnet" failed.
Log.LogError($"ILLink failed with exited code {ExitCode}.");
Log.LogError($"ILLink failed with exit code {ExitCode}.");
return false;
}

View File

@ -1,4 +1,4 @@
<!DOCTYPE html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
@ -11,6 +11,12 @@
<body>
<app>Loading...</app>
<div id="blazor-error-ui">
An unhandled exception has occurred. See browser dev tools for details.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
</body>
</html>

View File

@ -248,6 +248,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mono.WebAssembly.Interop",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.WebAssembly.Interop", "Blazor\Mono.WebAssembly.Interop\src\Mono.WebAssembly.Interop.csproj", "{D141CFEE-D10A-406B-8963-F86FA13732E3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComponentsApp.Server", "test\testassets\ComponentsApp.Server\ComponentsApp.Server.csproj", "{F2E27E1C-2E47-42C1-9AC7-36265A381717}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -1518,6 +1520,18 @@ Global
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x64.Build.0 = Release|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x86.ActiveCfg = Release|Any CPU
{D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x86.Build.0 = Release|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|x64.ActiveCfg = Debug|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|x64.Build.0 = Debug|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|x86.ActiveCfg = Debug|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|x86.Build.0 = Debug|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Release|Any CPU.Build.0 = Release|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Release|x64.ActiveCfg = Release|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Release|x64.Build.0 = Release|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Release|x86.ActiveCfg = Release|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1632,6 +1646,7 @@ Global
{A5617A9D-C71E-44DE-936C-27611EB40A02} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{D141CFEE-D10A-406B-8963-F86FA13732E3} = {21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05}
{F2E27E1C-2E47-42C1-9AC7-36265A381717} = {44E0D4F3-4430-4175-B482-0D1AEE4BB699}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CC3C47E1-AD1A-4619-9CD3-E08A0148E5CE}

File diff suppressed because one or more lines are too long

View File

@ -73,5 +73,7 @@ interface BootJsonData {
window['Blazor'].start = boot;
if (shouldAutoStart()) {
boot();
boot().catch(error => {
Module.printErr(error); // Logs it, and causes the error UI to appear
});
}

View File

@ -11,8 +11,7 @@ let assembly_load: (assemblyName: string) => number;
let find_class: (assemblyHandle: number, namespace: string, className: string) => number;
let find_method: (typeHandle: number, methodName: string, unknownArg: number) => MethodHandle;
let invoke_method: (method: MethodHandle, target: System_Object, argsArrayPtr: number, exceptionFlagIntPtr: number) => System_Object;
let mono_call_assembly_entry_point: (assemblyName: string, args: System_Object[]) => System_Object;
let mono_obj_array_new: (length: number) => System_Object;
let mono_string_array_new: (length: number) => System_Array<System_String>;
let mono_string_get_utf8: (managedString: System_String) => Mono.Utf8Ptr;
let mono_string: (jsString: string) => System_String;
const appBinDirName = 'appBinDir';
@ -41,9 +40,18 @@ export const monoPlatform: Platform = {
findMethod: findMethod,
callEntryPoint: function callEntryPoint(assemblyName: string): System_Object {
const empty_array = mono_obj_array_new(0);
return mono_call_assembly_entry_point(assemblyName, [empty_array]);
callEntryPoint: function callEntryPoint(assemblyName: string) {
// Instead of using Module.mono_call_assembly_entry_point, we have our own logic for invoking
// the entrypoint which adds support for async main.
// Currently we disregard the return value from the entrypoint, whether it's sync or async.
// In the future, we might want Blazor.start to return a Promise<Promise<value>>, where the
// outer promise reflects the startup process, and the inner one reflects the possibly-async
// .NET entrypoint method.
const invokeEntrypoint = findMethod('Microsoft.AspNetCore.Blazor', 'Microsoft.AspNetCore.Blazor.Hosting', 'EntrypointInvoker', 'InvokeEntrypoint');
this.callMethod(invokeEntrypoint, null, [
this.toDotNetString(assemblyName),
mono_string_array_new(0) // In the future, we may have a way of supplying arg strings. For now, we always supply an empty string[].
]);
},
callMethod: function callMethod(method: MethodHandle, target: System_Object, args: System_Object[]): System_Object {
@ -263,11 +271,9 @@ function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: ()
'number',
]);
mono_call_assembly_entry_point = Module.mono_call_assembly_entry_point;
mono_string_get_utf8 = Module.cwrap('mono_wasm_string_get_utf8', 'number', ['number']);
mono_string = Module.cwrap('mono_wasm_string_from_js', 'number', ['string']);
mono_obj_array_new = Module.cwrap ('mono_wasm_obj_array_new', 'number', ['number']);
mono_string_array_new = Module.cwrap('mono_wasm_string_array_new', 'number', ['number']);
MONO.loaded_files = [];

View File

@ -1,7 +1,7 @@
export interface Platform {
start(loadAssemblyUrls: string[]): Promise<void>;
callEntryPoint(assemblyName: string): System_Object;
callEntryPoint(assemblyName: string): void;
findMethod(assemblyName: string, namespace: string, className: string, methodName: string): MethodHandle;
callMethod(method: MethodHandle, target: System_Object | null, args: (System_Object | null)[]): System_Object;

View File

@ -5,16 +5,15 @@ using BasicTestApp;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests;
using Microsoft.AspNetCore.E2ETesting;
using OpenQA.Selenium;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
{
[Collection("ErrorNotification")] // When the clientside and serverside tests run together it seems to cause failures, possibly due to connection lose on exception.
public class ErrorNotificationServerSideTest : ErrorNotificationClientSideTest
public class ServerErrorNotificationTest : ErrorNotificationTest
{
public ErrorNotificationServerSideTest(
public ServerErrorNotificationTest(
BrowserFixture browserFixture,
ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output)

View File

@ -13,9 +13,9 @@ using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
{
[Collection("ErrorNotification")] // When the clientside and serverside tests run together it seems to cause failures, possibly due to connection lose on exception.
public class ErrorNotificationClientSideTest : ServerTestBase<ToggleExecutionModeServerFixture<Program>>
public class ErrorNotificationTest : ServerTestBase<ToggleExecutionModeServerFixture<Program>>
{
public ErrorNotificationClientSideTest(
public ErrorNotificationTest(
BrowserFixture browserFixture,
ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output)

View File

@ -0,0 +1,40 @@
// 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;
using BasicTestApp;
using Microsoft.AspNetCore.E2ETesting;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using OpenQA.Selenium;
using Xunit.Abstractions;
using Xunit;
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
{
public class StartupErrorNotificationTest : ServerTestBase<DevHostServerFixture<Program>>
{
public StartupErrorNotificationTest(
BrowserFixture browserFixture,
DevHostServerFixture<Program> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
_serverFixture.PathBase = ServerPathBase;
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void DisplaysNotificationForStartupException(bool errorIsAsync)
{
var url = $"{ServerPathBase}?error={(errorIsAsync ? "async" : "sync")}";
Navigate(url, noReload: true);
var errorUiElem = Browser.Exists(By.Id("blazor-error-ui"), TimeSpan.FromSeconds(10));
Assert.NotNull(errorUiElem);
Browser.Equal("block", () => errorUiElem.GetCssValue("display"));
}
}
}

View File

@ -87,12 +87,6 @@
@((RenderFragment)RenderSelectedComponent)
</app>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href class='reload'>Reload</a>
<a class='dismiss' style="cursor: pointer;">🗙</a>
</div>
@code {
string SelectedComponentTypeName { get; set; } = "none";

View File

@ -1,15 +1,20 @@
// 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;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Blazor.Hosting;
using Mono.WebAssembly.Interop;
namespace BasicTestApp
{
public class Program
{
public static void Main(string[] args)
public static async Task Main(string[] args)
{
await SimulateErrorsIfNeededForTest();
// We want the culture to be en-US so that the tests for bind can work consistently.
CultureInfo.CurrentCulture = new CultureInfo("en-US");
@ -19,5 +24,22 @@ namespace BasicTestApp
public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
BlazorWebAssemblyHost.CreateDefaultBuilder()
.UseBlazorStartup<Startup>();
// Supports E2E tests in StartupErrorNotificationTest
private static async Task SimulateErrorsIfNeededForTest()
{
var currentUrl = new MonoWebAssemblyJSRuntime().Invoke<string>("getCurrentUrl");
if (currentUrl.Contains("error=sync"))
{
throw new InvalidTimeZoneException("This is a synchronous startup exception");
}
await Task.Yield();
if (currentUrl.Contains("error=async"))
{
throw new InvalidTimeZoneException("This is an asynchronous startup exception");
}
}
}
}

View File

@ -14,6 +14,13 @@
<body>
<root>Loading...</root>
<!-- Explicit display:none required so StartupErrorNotificationTest can observe it change -->
<div id="blazor-error-ui" style="display: none;">
An unhandled error has occurred.
<a href class='reload'>Reload</a>
<a class='dismiss' style="cursor: pointer;">🗙</a>
</div>
<!-- Used for testing interop scenarios between JS and .NET -->
<script src="js/jsinteroptests.js"></script>
@ -27,6 +34,10 @@
function navigationManagerNavigate() {
Blazor.navigateTo('/subdir/some-path');
}
function getCurrentUrl() {
return location.href;
}
</script>
<script src="_framework/blazor.webassembly.js"></script>

View File

@ -7,11 +7,23 @@
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
box-sizing: border-box;
}
#blazor-error-ui dismiss {
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
.validation-message {

View File

@ -17,6 +17,12 @@
<!-- Used for testing interop scenarios between JS and .NET -->
<script src="js/jsinteroptests.js"></script>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href class='reload'>Reload</a>
<a class='dismiss' style="cursor: pointer;">🗙</a>
</div>
<script>
// Used by ElementRefComponent
function setElementValue(element, newValue) {

View File

@ -26,10 +26,10 @@
</PropertyGroup>
<ItemGroup>
<RemoteAsset Include="$(DotNetAssetRootUrl)Runtime/$(MicrosoftNETCoreAppRuntimeVersion)/dotnet-runtime-$(MicrosoftNETCoreAppRuntimeVersion)-win-x64.exe$(DotNetAssetRootAccessTokenSuffix)">
<RemoteAsset Include="$(DotNetAssetRootUrl)Runtime/$(MicrosoftNETCoreAppInternalPackageVersion)/dotnet-runtime-$(MicrosoftNETCoreAppRuntimeVersion)-win-x64.exe$(DotNetAssetRootAccessTokenSuffix)">
<TargetFileName>dotnet-runtime-$(MicrosoftNETCoreAppRuntimeVersion)-win-x64.exe</TargetFileName>
</RemoteAsset>
<RemoteAsset Include="$(DotNetAssetRootUrl)Runtime/$(MicrosoftNETCoreAppRuntimeVersion)/dotnet-runtime-$(MicrosoftNETCoreAppRuntimeVersion)-win-x86.exe$(DotNetAssetRootAccessTokenSuffix)">
<RemoteAsset Include="$(DotNetAssetRootUrl)Runtime/$(MicrosoftNETCoreAppInternalPackageVersion)/dotnet-runtime-$(MicrosoftNETCoreAppRuntimeVersion)-win-x86.exe$(DotNetAssetRootAccessTokenSuffix)">
<TargetFileName>dotnet-runtime-$(MicrosoftNETCoreAppRuntimeVersion)-win-x86.exe</TargetFileName>
</RemoteAsset>
</ItemGroup>