From c37f3fefce7a66c57ee6c8b526e3a9be7fe7d7a3 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 16 Mar 2020 16:01:09 -0700 Subject: [PATCH 1/2] Turn on scope validation for service provider Fixes https://github.com/dotnet/aspnetcore/issues/9365 --- .../JSInterop/src/WebAssemblyJSRuntime.cs | 2 +- ...icationServiceCollectionExtensionsTests.cs | 28 +++++++-- .../src/Hosting/WebAssemblyHostBuilder.cs | 29 +++++----- ...t.AspNetCore.Components.WebAssembly.csproj | 1 + .../test/Hosting/TestWebAssemblyJSRuntime.cs | 25 ++++++++ .../Hosting/WebAssemblyHostBuilderTest.cs | 57 ++++++++++++++++--- .../test/Hosting/WebAssemblyHostTest.cs | 6 +- 7 files changed, 114 insertions(+), 34 deletions(-) create mode 100644 src/Components/WebAssembly/WebAssembly/test/Hosting/TestWebAssemblyJSRuntime.cs diff --git a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs index 020052115a..435c49afb2 100644 --- a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs +++ b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs @@ -89,7 +89,7 @@ namespace Microsoft.JSInterop.WebAssembly /// The second argument. /// The third argument. /// The result of the function invocation. - public TResult InvokeUnmarshalled(string identifier, T0 arg0, T1 arg1, T2 arg2) + public virtual TResult InvokeUnmarshalled(string identifier, T0 arg0, T1 arg1, T2 arg2) { var result = InternalCalls.InvokeJSUnmarshalled(out var exception, identifier, arg0, arg1, arg2); return exception != null diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/test/WebAssemblyAuthenticationServiceCollectionExtensionsTests.cs b/src/Components/WebAssembly/WebAssembly.Authentication/test/WebAssemblyAuthenticationServiceCollectionExtensionsTests.cs index b649cc5e2c..b23090b1c2 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/test/WebAssemblyAuthenticationServiceCollectionExtensionsTests.cs +++ b/src/Components/WebAssembly/WebAssembly.Authentication/test/WebAssemblyAuthenticationServiceCollectionExtensionsTests.cs @@ -6,6 +6,8 @@ using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; +using Microsoft.JSInterop.WebAssembly; +using Moq; using Xunit; namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication @@ -15,7 +17,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication [Fact] public void CanResolve_AccessTokenProvider() { - var builder = WebAssemblyHostBuilder.CreateDefault(); + var builder = new WebAssemblyHostBuilder(GetJSRuntime()); builder.Services.AddApiAuthorization(); var host = builder.Build(); @@ -25,7 +27,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication [Fact] public void CanResolve_IRemoteAuthenticationService() { - var builder = WebAssemblyHostBuilder.CreateDefault(); + var builder = new WebAssemblyHostBuilder(GetJSRuntime()); builder.Services.AddApiAuthorization(); var host = builder.Build(); @@ -35,7 +37,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication [Fact] public void ApiAuthorizationOptions_ConfigurationDefaultsGetApplied() { - var builder = WebAssemblyHostBuilder.CreateDefault(); + var builder = new WebAssemblyHostBuilder(GetJSRuntime()); builder.Services.AddApiAuthorization(); var host = builder.Build(); @@ -69,7 +71,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication [Fact] public void ApiAuthorizationOptions_DefaultsCanBeOverriden() { - var builder = WebAssemblyHostBuilder.CreateDefault(); + var builder = new WebAssemblyHostBuilder(GetJSRuntime()); builder.Services.AddApiAuthorization(options => { options.AuthenticationPaths = new RemoteAuthenticationApplicationPathsOptions @@ -129,7 +131,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication [Fact] public void OidcOptions_ConfigurationDefaultsGetApplied() { - var builder = WebAssemblyHostBuilder.CreateDefault(); + var builder = new WebAssemblyHostBuilder(GetJSRuntime()); builder.Services.Replace(ServiceDescriptor.Singleton()); builder.Services.AddOidcAuthentication(options => { }); var host = builder.Build(); @@ -167,7 +169,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication [Fact] public void OidcOptions_DefaultsCanBeOverriden() { - var builder = WebAssemblyHostBuilder.CreateDefault(); + var builder = new WebAssemblyHostBuilder(GetJSRuntime()); builder.Services.AddOidcAuthentication(options => { options.AuthenticationPaths = new RemoteAuthenticationApplicationPathsOptions @@ -242,5 +244,19 @@ 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(); + jsRuntime.Setup(j => j.InvokeUnmarshalled("Blazor._internal.getApplicationEnvironment", null, null, null)) + .Returns(environment) + .Verifiable(); + + jsRuntime.Setup(j => j.InvokeUnmarshalled("Blazor._internal.getConfig", It.IsAny(), null, null)) + .Returns((byte[])null) + .Verifiable(); + + return jsRuntime.Object; + } } } diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs index f7113d4041..02017004bf 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using Microsoft.AspNetCore.Components.Routing; using Microsoft.AspNetCore.Components.WebAssembly.Services; using Microsoft.Extensions.Configuration; @@ -14,6 +13,7 @@ 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 +35,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(); - var builder = new WebAssemblyHostBuilder(); + var builder = new WebAssemblyHostBuilder(DefaultWebAssemblyJSRuntime.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 +47,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting /// /// Creates an instance of with the minimal configuration. /// - private WebAssemblyHostBuilder() + internal WebAssemblyHostBuilder(WebAssemblyJSRuntime jsRuntime) { // 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,25 +58,20 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting InitializeDefaultServices(); + var hostEnvironment = InitializeEnvironment(jsRuntime); + _createServiceProvider = () => { - return Services.BuildServiceProvider(); + return Services.BuildServiceProvider(validateScopes: hostEnvironment.Environment == "Development"); }; - - InitializeEnvironment(); } - private void InitializeEnvironment() + private WebAssemblyHostEnvironment InitializeEnvironment(WebAssemblyJSRuntime jsRuntime) { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Create("WEBASSEMBLY"))) - { - // The remainder of this method relies on the ability to make .NET WebAssembly-specific JSInterop calls. - // Note that this short-circuit exists as a way for unit tests running in .NET Core without JSInterop to run. - return; - } + var applicationEnvironment = jsRuntime.InvokeUnmarshalled("Blazor._internal.getApplicationEnvironment"); + var hostEnvironment = new WebAssemblyHostEnvironment(applicationEnvironment); - var applicationEnvironment = DefaultWebAssemblyJSRuntime.Instance.InvokeUnmarshalled("Blazor._internal.getApplicationEnvironment"); - Services.AddSingleton(new WebAssemblyHostEnvironment(applicationEnvironment)); + Services.AddSingleton(hostEnvironment); var configFiles = new[] { @@ -86,7 +81,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting foreach (var configFile in configFiles) { - var appSettingsJson = DefaultWebAssemblyJSRuntime.Instance.InvokeUnmarshalled( + var appSettingsJson = jsRuntime.InvokeUnmarshalled( "Blazor._internal.getConfig", configFile); @@ -97,6 +92,8 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting Configuration.Add(s => s.Stream = new MemoryStream(appSettingsJson)); } } + + return hostEnvironment; } /// diff --git a/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj b/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj index b2e8302cc4..c543a7a11b 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj +++ b/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj @@ -23,6 +23,7 @@ + diff --git a/src/Components/WebAssembly/WebAssembly/test/Hosting/TestWebAssemblyJSRuntime.cs b/src/Components/WebAssembly/WebAssembly/test/Hosting/TestWebAssemblyJSRuntime.cs new file mode 100644 index 0000000000..74a251a28e --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/test/Hosting/TestWebAssemblyJSRuntime.cs @@ -0,0 +1,25 @@ +// 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(); + jsRuntime.Setup(j => j.InvokeUnmarshalled("Blazor._internal.getApplicationEnvironment", null, null, null)) + .Returns(environment) + .Verifiable(); + + jsRuntime.Setup(j => j.InvokeUnmarshalled("Blazor._internal.getConfig", It.IsAny(), null, null)) + .Returns((byte[])null) + .Verifiable(); + + return jsRuntime.Object; + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostBuilderTest.cs b/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostBuilderTest.cs index 5acc214437..b6378dca9a 100644 --- a/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostBuilderTest.cs +++ b/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostBuilderTest.cs @@ -3,14 +3,14 @@ using System; using System.Collections.Generic; -using System.Net.Http; using System.Text; -using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.JSInterop; +using Microsoft.JSInterop.WebAssembly; +using Moq; using Xunit; namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting public void Build_AllowsConfiguringConfiguration() { // Arrange - var builder = WebAssemblyHostBuilder.CreateDefault(); + var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); builder.Configuration.AddInMemoryCollection(new[] { @@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting public void Build_AllowsConfiguringServices() { // Arrange - var builder = WebAssemblyHostBuilder.CreateDefault(); + var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); // This test also verifies that we create a scope. builder.Services.AddScoped(); @@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting public void Build_AllowsConfiguringContainer() { // Arrange - var builder = WebAssemblyHostBuilder.CreateDefault(); + var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); builder.Services.AddScoped(); var factory = new MyFakeServiceProviderFactory(); @@ -73,7 +73,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting public void Build_AllowsConfiguringContainer_WithDelegate() { // Arrange - var builder = WebAssemblyHostBuilder.CreateDefault(); + var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); builder.Services.AddScoped(); @@ -92,6 +92,46 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting Assert.NotNull(host.Services.GetRequiredService>()); } + [Fact] + public void Build_InDevelopment_ConfiguresWithServiceProviderWithScopeValidation() + { + // Arrange + var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create(environment: "Development")); + + builder.Services.AddScoped(); + builder.Services.AddSingleton(); + + // Act + var host = builder.Build(); + + // Assert + Assert.NotNull(host.Services.GetRequiredService()); + Assert.Throws(() => host.Services.GetRequiredService()); + } + + [Fact] + public void Build_InProduction_ConfiguresWithServiceProviderWithScopeValidation() + { + // Arrange + var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); + + builder.Services.AddScoped(); + builder.Services.AddSingleton(); + + // Act + var host = builder.Build(); + + // Assert + Assert.NotNull(host.Services.GetRequiredService()); + Assert.NotNull(host.Services.GetRequiredService()); + } + + private class TestServiceThatTakesStringBuilder + { + public TestServiceThatTakesStringBuilder(StringBuilder builder) { } + } + + private class MyFakeDIBuilderThing { public MyFakeDIBuilderThing(IServiceCollection serviceCollection) @@ -125,7 +165,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting public void Build_AddsConfigurationToServices() { // Arrange - var builder = WebAssemblyHostBuilder.CreateDefault(); + var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); builder.Configuration.AddInMemoryCollection(new[] { @@ -151,6 +191,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting typeof(INavigationInterception), typeof(ILoggerFactory), typeof(ILogger<>), + typeof(IWebAssemblyHostEnvironment), }; } } @@ -159,7 +200,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting public void Constructor_AddsDefaultServices() { // Arrange & Act - var builder = WebAssemblyHostBuilder.CreateDefault(); + var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); // Assert Assert.Equal(DefaultServiceTypes.Count, builder.Services.Count); diff --git a/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostTest.cs b/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostTest.cs index 9fcdb7b8ec..a67363ba5c 100644 --- a/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostTest.cs +++ b/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostTest.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting public async Task RunAsync_CanExitBasedOnCancellationToken() { // Arrange - var builder = WebAssemblyHostBuilder.CreateDefault(); + var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); 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 = WebAssemblyHostBuilder.CreateDefault(); + var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); 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 = WebAssemblyHostBuilder.CreateDefault(); + var builder = new WebAssemblyHostBuilder(TestWebAssemblyJSRuntime.Create()); builder.Services.AddSingleton(); var host = builder.Build(); From cb293661e118f66c798e9a5d9315408e871fce3d Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 17 Mar 2020 09:48:00 -0700 Subject: [PATCH 2/2] Make extensions Make WebAssemblyJSRuntime interop-specific overloads extension methods --- .../JSInterop/src/WebAssemblyJSRuntime.cs | 37 ---------- .../src/WebAssemblyJSRuntimeExtensions.cs | 70 +++++++++++++++++++ .../src/Rendering/WebAssemblyRenderer.cs | 1 + 3 files changed, 71 insertions(+), 37 deletions(-) create mode 100644 src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntimeExtensions.cs diff --git a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs index 435c49afb2..783dbb1082 100644 --- a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs +++ b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs @@ -42,41 +42,6 @@ namespace Microsoft.JSInterop.WebAssembly BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", args); } - #region Custom WebAssemblyJSRuntime methods - - /// - /// Invokes the JavaScript function registered with the specified identifier. - /// - /// The .NET type corresponding to the function's return value type. - /// The identifier used when registering the target function. - /// The result of the function invocation. - public TResult InvokeUnmarshalled(string identifier) - => InvokeUnmarshalled(identifier, null, null, null); - - /// - /// Invokes the JavaScript function registered with the specified identifier. - /// - /// The type of the first argument. - /// The .NET type corresponding to the function's return value type. - /// The identifier used when registering the target function. - /// The first argument. - /// The result of the function invocation. - public TResult InvokeUnmarshalled(string identifier, T0 arg0) - => InvokeUnmarshalled(identifier, arg0, null, null); - - /// - /// Invokes the JavaScript function registered with the specified identifier. - /// - /// The type of the first argument. - /// The type of the second argument. - /// The .NET type corresponding to the function's return value type. - /// The identifier used when registering the target function. - /// The first argument. - /// The second argument. - /// The result of the function invocation. - public TResult InvokeUnmarshalled(string identifier, T0 arg0, T1 arg1) - => InvokeUnmarshalled(identifier, arg0, arg1, null); - /// /// Invokes the JavaScript function registered with the specified identifier. /// @@ -96,7 +61,5 @@ namespace Microsoft.JSInterop.WebAssembly ? throw new JSException(exception) : result; } - - #endregion } } diff --git a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntimeExtensions.cs b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntimeExtensions.cs new file mode 100644 index 0000000000..4ba4de1266 --- /dev/null +++ b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntimeExtensions.cs @@ -0,0 +1,70 @@ +// 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 +{ + /// + /// Extension methods for . + /// + public static class WebAssemblyJSRuntimeExtensions + { + /// + /// Invokes the JavaScript function registered with the specified identifier. + /// + /// The .NET type corresponding to the function's return value type. + /// The . + /// The identifier used when registering the target function. + /// The result of the function invocation. + public static TResult InvokeUnmarshalled(this WebAssemblyJSRuntime jsRuntime, string identifier) + { + if (jsRuntime is null) + { + throw new ArgumentNullException(nameof(jsRuntime)); + } + + return jsRuntime.InvokeUnmarshalled(identifier, null, null, null); + } + + /// + /// Invokes the JavaScript function registered with the specified identifier. + /// + /// The type of the first argument. + /// The .NET type corresponding to the function's return value type. + /// The . + /// The identifier used when registering the target function. + /// The first argument. + /// The result of the function invocation. + public static TResult InvokeUnmarshalled(this WebAssemblyJSRuntime jsRuntime, string identifier, T0 arg0) + { + if (jsRuntime is null) + { + throw new ArgumentNullException(nameof(jsRuntime)); + } + + return jsRuntime.InvokeUnmarshalled(identifier, arg0, null, null); + } + + /// + /// Invokes the JavaScript function registered with the specified identifier. + /// + /// The type of the first argument. + /// The type of the second argument. + /// The .NET type corresponding to the function's return value type. + /// The . + /// The identifier used when registering the target function. + /// The first argument. + /// The second argument. + /// The result of the function invocation. + public static TResult InvokeUnmarshalled(this WebAssemblyJSRuntime jsRuntime, string identifier, T0 arg0, T1 arg1) + { + if (jsRuntime is null) + { + throw new ArgumentNullException(nameof(jsRuntime)); + } + + return jsRuntime.InvokeUnmarshalled(identifier, arg0, arg1, null); + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly/src/Rendering/WebAssemblyRenderer.cs b/src/Components/WebAssembly/WebAssembly/src/Rendering/WebAssemblyRenderer.cs index 8615b05847..d9e81acf15 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Rendering/WebAssemblyRenderer.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Rendering/WebAssemblyRenderer.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Components.WebAssembly.Services; using Microsoft.Extensions.Logging; +using Microsoft.JSInterop.WebAssembly; namespace Microsoft.AspNetCore.Components.WebAssembly.Rendering {