Restore public API contract on WebAssemblyJSRuntime (#19968)

This commit is contained in:
Steve Sanderson 2020-03-19 11:56:49 +00:00 committed by GitHub
parent 56d22a817b
commit dbe63021a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 126 additions and 136 deletions

View File

@ -0,0 +1,31 @@
// 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 Microsoft.AspNetCore.Components.WebAssembly.Services;
namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
{
internal class TestWebAssemblyJSRuntimeInvoker : WebAssemblyJSRuntimeInvoker
{
private readonly string _environment;
public TestWebAssemblyJSRuntimeInvoker(string environment = "Production")
{
_environment = environment;
}
public override TResult InvokeUnmarshalled<T0, T1, T2, TResult>(string identifier, T0 arg0, T1 arg1, T2 arg2)
{
switch (identifier)
{
case "Blazor._internal.getApplicationEnvironment":
return (TResult)(object)_environment;
case "Blazor._internal.getConfig":
return (TResult)(object)null;
default:
throw new NotImplementedException($"{nameof(TestWebAssemblyJSRuntimeInvoker)} has no implementation for '{identifier}'.");
}
}
}
}

View File

@ -42,6 +42,39 @@ namespace Microsoft.JSInterop.WebAssembly
BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", args);
}
/// <summary>
/// Invokes the JavaScript function registered with the specified identifier.
/// </summary>
/// <typeparam name="TResult">The .NET type corresponding to the function's return value type.</typeparam>
/// <param name="identifier">The identifier used when registering the target function.</param>
/// <returns>The result of the function invocation.</returns>
public TResult InvokeUnmarshalled<TResult>(string identifier)
=> InvokeUnmarshalled<object, object, object, TResult>(identifier, null, null, null);
/// <summary>
/// Invokes the JavaScript function registered with the specified identifier.
/// </summary>
/// <typeparam name="T0">The type of the first argument.</typeparam>
/// <typeparam name="TResult">The .NET type corresponding to the function's return value type.</typeparam>
/// <param name="identifier">The identifier used when registering the target function.</param>
/// <param name="arg0">The first argument.</param>
/// <returns>The result of the function invocation.</returns>
public TResult InvokeUnmarshalled<T0, TResult>(string identifier, T0 arg0)
=> InvokeUnmarshalled<T0, object, object, TResult>(identifier, arg0, null, null);
/// <summary>
/// Invokes the JavaScript function registered with the specified identifier.
/// </summary>
/// <typeparam name="T0">The type of the first argument.</typeparam>
/// <typeparam name="T1">The type of the second argument.</typeparam>
/// <typeparam name="TResult">The .NET type corresponding to the function's return value type.</typeparam>
/// <param name="identifier">The identifier used when registering the target function.</param>
/// <param name="arg0">The first argument.</param>
/// <param name="arg1">The second argument.</param>
/// <returns>The result of the function invocation.</returns>
public TResult InvokeUnmarshalled<T0, T1, TResult>(string identifier, T0 arg0, T1 arg1)
=> InvokeUnmarshalled<T0, T1, object, TResult>(identifier, arg0, arg1, null);
/// <summary>
/// Invokes the JavaScript function registered with the specified identifier.
/// </summary>
@ -54,7 +87,7 @@ namespace Microsoft.JSInterop.WebAssembly
/// <param name="arg1">The second argument.</param>
/// <param name="arg2">The third argument.</param>
/// <returns>The result of the function invocation.</returns>
public virtual TResult InvokeUnmarshalled<T0, T1, T2, TResult>(string identifier, T0 arg0, T1 arg1, T2 arg2)
public TResult InvokeUnmarshalled<T0, T1, T2, TResult>(string identifier, T0 arg0, T1 arg1, T2 arg2)
{
var result = InternalCalls.InvokeJSUnmarshalled<T0, T1, T2, TResult>(out var exception, identifier, arg0, arg1, arg2);
return exception != null

View File

@ -1,70 +0,0 @@
// 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;
namespace Microsoft.JSInterop.WebAssembly
{
/// <summary>
/// Extension methods for <see cref="WebAssemblyJSRuntime"/>.
/// </summary>
public static class WebAssemblyJSRuntimeExtensions
{
/// <summary>
/// Invokes the JavaScript function registered with the specified identifier.
/// </summary>
/// <typeparam name="TResult">The .NET type corresponding to the function's return value type.</typeparam>
/// <param name="jsRuntime">The <see cref="WebAssemblyJSRuntime"/>.</param>
/// <param name="identifier">The identifier used when registering the target function.</param>
/// <returns>The result of the function invocation.</returns>
public static TResult InvokeUnmarshalled<TResult>(this WebAssemblyJSRuntime jsRuntime, string identifier)
{
if (jsRuntime is null)
{
throw new ArgumentNullException(nameof(jsRuntime));
}
return jsRuntime.InvokeUnmarshalled<object, object, object, TResult>(identifier, null, null, null);
}
/// <summary>
/// Invokes the JavaScript function registered with the specified identifier.
/// </summary>
/// <typeparam name="T0">The type of the first argument.</typeparam>
/// <typeparam name="TResult">The .NET type corresponding to the function's return value type.</typeparam>
/// <param name="jsRuntime">The <see cref="WebAssemblyJSRuntime"/>.</param>
/// <param name="identifier">The identifier used when registering the target function.</param>
/// <param name="arg0">The first argument.</param>
/// <returns>The result of the function invocation.</returns>
public static TResult InvokeUnmarshalled<T0, TResult>(this WebAssemblyJSRuntime jsRuntime, string identifier, T0 arg0)
{
if (jsRuntime is null)
{
throw new ArgumentNullException(nameof(jsRuntime));
}
return jsRuntime.InvokeUnmarshalled<T0, object, object, TResult>(identifier, arg0, null, null);
}
/// <summary>
/// Invokes the JavaScript function registered with the specified identifier.
/// </summary>
/// <typeparam name="T0">The type of the first argument.</typeparam>
/// <typeparam name="T1">The type of the second argument.</typeparam>
/// <typeparam name="TResult">The .NET type corresponding to the function's return value type.</typeparam>
/// <param name="jsRuntime">The <see cref="WebAssemblyJSRuntime"/>.</param>
/// <param name="identifier">The identifier used when registering the target function.</param>
/// <param name="arg0">The first argument.</param>
/// <param name="arg1">The second argument.</param>
/// <returns>The result of the function invocation.</returns>
public static TResult InvokeUnmarshalled<T0, T1, TResult>(this WebAssemblyJSRuntime jsRuntime, string identifier, T0 arg0, T1 arg1)
{
if (jsRuntime is null)
{
throw new ArgumentNullException(nameof(jsRuntime));
}
return jsRuntime.InvokeUnmarshalled<T0, T1, object, TResult>(identifier, arg0, arg1, null);
}
}
}

View File

@ -11,4 +11,8 @@
<Reference Include="Microsoft.CodeAnalysis.CSharp" />
</ItemGroup>
<ItemGroup>
<Compile Include="$(ComponentsSharedSourceRoot)\test\TestWebAssemblyJSRuntimeInvoker.cs" Link="Shared\TestWebAssemblyJSRuntimeInvoker.cs" />
</ItemGroup>
</Project>

View File

@ -3,6 +3,7 @@
using System;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.AspNetCore.Components.WebAssembly.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
@ -17,7 +18,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
[Fact]
public void CanResolve_AccessTokenProvider()
{
var builder = new WebAssemblyHostBuilder(GetJSRuntime());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
builder.Services.AddApiAuthorization();
var host = builder.Build();
@ -27,7 +28,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
[Fact]
public void CanResolve_IRemoteAuthenticationService()
{
var builder = new WebAssemblyHostBuilder(GetJSRuntime());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
builder.Services.AddApiAuthorization();
var host = builder.Build();
@ -37,7 +38,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
[Fact]
public void ApiAuthorizationOptions_ConfigurationDefaultsGetApplied()
{
var builder = new WebAssemblyHostBuilder(GetJSRuntime());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
builder.Services.AddApiAuthorization();
var host = builder.Build();
@ -71,7 +72,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
[Fact]
public void ApiAuthorizationOptions_DefaultsCanBeOverriden()
{
var builder = new WebAssemblyHostBuilder(GetJSRuntime());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
builder.Services.AddApiAuthorization(options =>
{
options.AuthenticationPaths = new RemoteAuthenticationApplicationPathsOptions
@ -131,7 +132,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
[Fact]
public void OidcOptions_ConfigurationDefaultsGetApplied()
{
var builder = new WebAssemblyHostBuilder(GetJSRuntime());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
builder.Services.Replace(ServiceDescriptor.Singleton<NavigationManager, TestNavigationManager>());
builder.Services.AddOidcAuthentication(options => { });
var host = builder.Build();
@ -169,7 +170,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
[Fact]
public void OidcOptions_DefaultsCanBeOverriden()
{
var builder = new WebAssemblyHostBuilder(GetJSRuntime());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
builder.Services.AddOidcAuthentication(options =>
{
options.AuthenticationPaths = new RemoteAuthenticationApplicationPathsOptions
@ -244,19 +245,5 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication
protected override void NavigateToCore(string uri, bool forceLoad) => throw new System.NotImplementedException();
}
private WebAssemblyJSRuntime GetJSRuntime(string environment = "Production")
{
var jsRuntime = new Mock<WebAssemblyJSRuntime>();
jsRuntime.Setup(j => j.InvokeUnmarshalled<object, object, object, string>("Blazor._internal.getApplicationEnvironment", null, null, null))
.Returns(environment)
.Verifiable();
jsRuntime.Setup(j => j.InvokeUnmarshalled<string, object, object, byte[]>("Blazor._internal.getConfig", It.IsAny<string>(), null, null))
.Returns((byte[])null)
.Verifiable();
return jsRuntime.Object;
}
}
}

View File

@ -13,7 +13,6 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.JSInterop;
using Microsoft.JSInterop.WebAssembly;
namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
{
@ -35,7 +34,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
// We don't use the args for anything right now, but we want to accept them
// here so that it shows up this way in the project templates.
args ??= Array.Empty<string>();
var builder = new WebAssemblyHostBuilder(DefaultWebAssemblyJSRuntime.Instance);
var builder = new WebAssemblyHostBuilder(WebAssemblyJSRuntimeInvoker.Instance);
// Right now we don't have conventions or behaviors that are specific to this method
// however, making this the default for the template allows us to add things like that
@ -47,7 +46,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
/// <summary>
/// Creates an instance of <see cref="WebAssemblyHostBuilder"/> with the minimal configuration.
/// </summary>
internal WebAssemblyHostBuilder(WebAssemblyJSRuntime jsRuntime)
internal WebAssemblyHostBuilder(WebAssemblyJSRuntimeInvoker jsRuntimeInvoker)
{
// Private right now because we don't have much reason to expose it. This can be exposed
// in the future if we want to give people a choice between CreateDefault and something
@ -58,7 +57,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
InitializeDefaultServices();
var hostEnvironment = InitializeEnvironment(jsRuntime);
var hostEnvironment = InitializeEnvironment(jsRuntimeInvoker);
_createServiceProvider = () =>
{
@ -66,9 +65,10 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
};
}
private WebAssemblyHostEnvironment InitializeEnvironment(WebAssemblyJSRuntime jsRuntime)
private WebAssemblyHostEnvironment InitializeEnvironment(WebAssemblyJSRuntimeInvoker jsRuntimeInvoker)
{
var applicationEnvironment = jsRuntime.InvokeUnmarshalled<string>("Blazor._internal.getApplicationEnvironment");
var applicationEnvironment = jsRuntimeInvoker.InvokeUnmarshalled<object, object, object, string>(
"Blazor._internal.getApplicationEnvironment", null, null, null);
var hostEnvironment = new WebAssemblyHostEnvironment(applicationEnvironment);
Services.AddSingleton<IWebAssemblyHostEnvironment>(hostEnvironment);
@ -81,9 +81,8 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
foreach (var configFile in configFiles)
{
var appSettingsJson = jsRuntime.InvokeUnmarshalled<string, byte[]>(
"Blazor._internal.getConfig",
configFile);
var appSettingsJson = jsRuntimeInvoker.InvokeUnmarshalled<string, object, object, byte[]>(
"Blazor._internal.getConfig", configFile, null, null);
if (appSettingsJson != null)
{

View File

@ -0,0 +1,27 @@
// 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 Microsoft.JSInterop.WebAssembly;
namespace Microsoft.AspNetCore.Components.WebAssembly.Services
{
/// <summary>
/// This class exists to enable unit testing for code that needs to call
/// <see cref="WebAssemblyJSRuntime.InvokeUnmarshalled{T0, T1, T2, TResult}(string, T0, T1, T2)"/>.
///
/// We should only use this in non-perf-critical code paths (for example, during hosting startup,
/// where we only call this a fixed number of times, and not during rendering where it might be
/// called arbitrarily frequently due to application logic). In perf-critical code paths, use
/// <see cref="DefaultWebAssemblyJSRuntime.Instance"/> and call it directly.
///
/// It might not ultimately make any difference but we won't know until we integrate AoT support.
/// When AoT is used, it's possible that virtual dispatch will force fallback on the interpreter.
/// </summary>
internal class WebAssemblyJSRuntimeInvoker
{
public static WebAssemblyJSRuntimeInvoker Instance = new WebAssemblyJSRuntimeInvoker();
public virtual TResult InvokeUnmarshalled<T0, T1, T2, TResult>(string identifier, T0 arg0, T1 arg1, T2 arg2)
=> DefaultWebAssemblyJSRuntime.Instance.InvokeUnmarshalled<T0, T1, T2, TResult>(identifier, arg0, arg1, arg2);
}
}

View File

@ -1,25 +0,0 @@
// 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 Microsoft.JSInterop.WebAssembly;
using Moq;
namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
{
public class TestWebAssemblyJSRuntime
{
public static WebAssemblyJSRuntime Create(string environment = "Production")
{
var jsRuntime = new Mock<WebAssemblyJSRuntime>();
jsRuntime.Setup(j => j.InvokeUnmarshalled<object, object, object, string>("Blazor._internal.getApplicationEnvironment", null, null, null))
.Returns(environment)
.Verifiable();
jsRuntime.Setup(j => j.InvokeUnmarshalled<string, object, object, byte[]>("Blazor._internal.getConfig", It.IsAny<string>(), null, null))
.Returns((byte[])null)
.Verifiable();
return jsRuntime.Object;
}
}
}

View File

@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
public void Build_AllowsConfiguringConfiguration()
{
// Arrange
var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
builder.Configuration.AddInMemoryCollection(new[]
{
@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
public void Build_AllowsConfiguringServices()
{
// Arrange
var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
// This test also verifies that we create a scope.
builder.Services.AddScoped<StringBuilder>();
@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
public void Build_AllowsConfiguringContainer()
{
// Arrange
var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
builder.Services.AddScoped<StringBuilder>();
var factory = new MyFakeServiceProviderFactory();
@ -73,7 +73,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
public void Build_AllowsConfiguringContainer_WithDelegate()
{
// Arrange
var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
builder.Services.AddScoped<StringBuilder>();
@ -96,7 +96,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
public void Build_InDevelopment_ConfiguresWithServiceProviderWithScopeValidation()
{
// Arrange
var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create(environment: "Development"));
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker(environment: "Development"));
builder.Services.AddScoped<StringBuilder>();
builder.Services.AddSingleton<TestServiceThatTakesStringBuilder>();
@ -113,7 +113,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
public void Build_InProduction_ConfiguresWithServiceProviderWithScopeValidation()
{
// Arrange
var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
builder.Services.AddScoped<StringBuilder>();
builder.Services.AddSingleton<TestServiceThatTakesStringBuilder>();
@ -165,7 +165,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
public void Build_AddsConfigurationToServices()
{
// Arrange
var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
builder.Configuration.AddInMemoryCollection(new[]
{
@ -200,7 +200,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
public void Constructor_AddsDefaultServices()
{
// Arrange & Act
var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
// Assert
Assert.Equal(DefaultServiceTypes.Count, builder.Services.Count);

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
public async Task RunAsync_CanExitBasedOnCancellationToken()
{
// Arrange
var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
var host = builder.Build();
var cts = new CancellationTokenSource();
@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
public async Task RunAsync_CallingTwiceCausesException()
{
// Arrange
var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
var host = builder.Build();
var cts = new CancellationTokenSource();
@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
public async Task DisposeAsync_CanDisposeAfterCallingRunAsync()
{
// Arrange
var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
builder.Services.AddSingleton<DisposableService>();
var host = builder.Build();

View File

@ -9,4 +9,8 @@
<Reference Include="Microsoft.CodeAnalysis.CSharp" />
</ItemGroup>
<ItemGroup>
<Compile Include="$(ComponentsSharedSourceRoot)\test\TestWebAssemblyJSRuntimeInvoker.cs" Link="Shared\TestWebAssemblyJSRuntimeInvoker.cs" />
</ItemGroup>
</Project>