Skip to content

Restore public API contract on WebAssemblyJSRuntime #19968

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/Components/Shared/test/TestWebAssemblyJSRuntimeInvoker.cs
Original file line number Diff line number Diff line change
@@ -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}'.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,39 @@ protected override void EndInvokeDotNet(DotNetInvocationInfo callInfo, in DotNet
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);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there an important reason to turn all these overloads into extension methods? If there was we can change it back.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It loosely follows the pattern we have with IJSRuntime - https://github.com/dotnet/aspnetcore/blob/master/src/JSInterop/Microsoft.JSInterop/src/JSRuntimeExtensions.cs#L13 where the most extensive overload was declared in the base class. It would have made sense to keep these as extensions if derived types had to implement every overload

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks. Since we’re no longer making these methods overridable, that no longer applies, so I’ll leave them as regular instance methods.


/// <summary>
/// Invokes the JavaScript function registered with the specified identifier.
/// </summary>
Expand All @@ -54,7 +87,7 @@ protected override void EndInvokeDotNet(DotNetInvocationInfo callInfo, in DotNet
/// <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
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@
<Reference Include="Microsoft.CodeAnalysis.CSharp" />
</ItemGroup>

<ItemGroup>
<Compile Include="$(ComponentsSharedSourceRoot)\test\TestWebAssemblyJSRuntimeInvoker.cs" Link="Shared\TestWebAssemblyJSRuntimeInvoker.cs" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,7 +18,7 @@ public class WebAssemblyAuthenticationServiceCollectionExtensionsTests
[Fact]
public void CanResolve_AccessTokenProvider()
{
var builder = new WebAssemblyHostBuilder(GetJSRuntime());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
builder.Services.AddApiAuthorization();
var host = builder.Build();

Expand All @@ -27,7 +28,7 @@ public void CanResolve_AccessTokenProvider()
[Fact]
public void CanResolve_IRemoteAuthenticationService()
{
var builder = new WebAssemblyHostBuilder(GetJSRuntime());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
builder.Services.AddApiAuthorization();
var host = builder.Build();

Expand All @@ -37,7 +38,7 @@ public void CanResolve_IRemoteAuthenticationService()
[Fact]
public void ApiAuthorizationOptions_ConfigurationDefaultsGetApplied()
{
var builder = new WebAssemblyHostBuilder(GetJSRuntime());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
builder.Services.AddApiAuthorization();
var host = builder.Build();

Expand Down Expand Up @@ -71,7 +72,7 @@ public void ApiAuthorizationOptions_ConfigurationDefaultsGetApplied()
[Fact]
public void ApiAuthorizationOptions_DefaultsCanBeOverriden()
{
var builder = new WebAssemblyHostBuilder(GetJSRuntime());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
builder.Services.AddApiAuthorization(options =>
{
options.AuthenticationPaths = new RemoteAuthenticationApplicationPathsOptions
Expand Down Expand Up @@ -131,7 +132,7 @@ public void ApiAuthorizationOptions_DefaultsCanBeOverriden()
[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();
Expand Down Expand Up @@ -169,7 +170,7 @@ public void OidcOptions_ConfigurationDefaultsGetApplied()
[Fact]
public void OidcOptions_DefaultsCanBeOverriden()
{
var builder = new WebAssemblyHostBuilder(GetJSRuntime());
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
builder.Services.AddOidcAuthentication(options =>
{
options.AuthenticationPaths = new RemoteAuthenticationApplicationPathsOptions
Expand Down Expand Up @@ -244,19 +245,5 @@ public TestNavigationManager()

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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.JSInterop;
using Microsoft.JSInterop.WebAssembly;

namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
{
Expand All @@ -35,7 +34,7 @@ public static WebAssemblyHostBuilder CreateDefault(string[] args = default)
// 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
Expand All @@ -47,7 +46,7 @@ public static WebAssemblyHostBuilder CreateDefault(string[] args = default)
/// <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
Expand All @@ -58,17 +57,18 @@ internal WebAssemblyHostBuilder(WebAssemblyJSRuntime jsRuntime)

InitializeDefaultServices();

var hostEnvironment = InitializeEnvironment(jsRuntime);
var hostEnvironment = InitializeEnvironment(jsRuntimeInvoker);

_createServiceProvider = () =>
{
return Services.BuildServiceProvider(validateScopes: hostEnvironment.Environment == "Development");
};
}

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);
Expand All @@ -81,9 +81,8 @@ private WebAssemblyHostEnvironment InitializeEnvironment(WebAssemblyJSRuntime js

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)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}

This file was deleted.

Loading