diff --git a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs
index 020052115a..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.
///
@@ -89,14 +54,12 @@ 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
? 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.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/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
{
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();