Skip to content

Commit 9e412f4

Browse files
authored
Do not attempt to load satellite assemblies until after MainAsync has executed (#21476)
WASM runtime does not like it when you attempt to load satellite assemblies for two sets of non-neutral cultures - the first is always loaded. This change defers loading satellite assemblies until after Program.Main has executed and the developer has configured the culture for the application. Fixes #21433
1 parent cc2b64e commit 9e412f4

File tree

6 files changed

+9
-93
lines changed

6 files changed

+9
-93
lines changed

src/Components/WebAssembly/WebAssembly/src/Hosting/EntrypointInvoker.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,10 @@ internal static class EntrypointInvoker
1515
// In the future we may want Blazor.start to return something that exposes the possibly-async
1616
// entrypoint result to the JS caller. There's no requirement to do that today, and if we
1717
// do change this it will be non-breaking.
18-
public static void InvokeEntrypoint(string assemblyName, string[] args)
19-
=> InvokeEntrypoint(assemblyName, args, SatelliteResourcesLoader.Init());
20-
21-
internal static async void InvokeEntrypoint(string assemblyName, string[] args, SatelliteResourcesLoader satelliteResourcesLoader)
18+
public static async void InvokeEntrypoint(string assemblyName, string[] args)
2219
{
2320
try
2421
{
25-
// Emscripten sets up the culture for the application based on the user's language preferences.
26-
// Before we execute the app's entry point, load satellite assemblies for this culture.
27-
// We'll allow users to configure their app culture as part of MainAsync. Loading satellite assemblies
28-
// for the configured culture will happen as part of WebAssemblyHost.RunAsync.
29-
await satelliteResourcesLoader.LoadCurrentCultureResourcesAsync();
30-
3122
var assembly = Assembly.Load(assemblyName);
3223
var entrypoint = FindUnderlyingEntrypoint(assembly);
3324
var @params = entrypoint.GetParameters().Length == 1 ? new object[] { args ?? Array.Empty<string>() } : new object[] { };

src/Components/WebAssembly/WebAssembly/src/Hosting/SatelliteResourcesLoader.cs

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System.Collections.Generic;
5-
using System.Diagnostics;
65
using System.Globalization;
76
using System.Reflection;
87
using System.Threading.Tasks;
@@ -16,36 +15,14 @@ internal class SatelliteResourcesLoader
1615
internal const string ReadSatelliteAssemblies = "window.Blazor._internal.readSatelliteAssemblies";
1716

1817
private readonly WebAssemblyJSRuntimeInvoker _invoker;
19-
private CultureInfo _previousCulture;
2018

2119
// For unit testing.
2220
internal SatelliteResourcesLoader(WebAssemblyJSRuntimeInvoker invoker)
2321
{
2422
_invoker = invoker;
2523
}
2624

27-
public static SatelliteResourcesLoader Instance { get; private set; }
28-
29-
public static SatelliteResourcesLoader Init()
30-
{
31-
Debug.Assert(Instance is null, "Init should not be called multiple times.");
32-
33-
Instance = new SatelliteResourcesLoader(WebAssemblyJSRuntimeInvoker.Instance);
34-
return Instance;
35-
}
36-
37-
public ValueTask LoadCurrentCultureResourcesAsync()
38-
{
39-
if (_previousCulture != CultureInfo.CurrentCulture)
40-
{
41-
_previousCulture = CultureInfo.CurrentCulture;
42-
return LoadSatelliteAssembliesForCurrentCultureAsync();
43-
}
44-
45-
return default;
46-
}
47-
48-
protected virtual async ValueTask LoadSatelliteAssembliesForCurrentCultureAsync()
25+
public virtual async ValueTask LoadCurrentCultureResourcesAsync()
4926
{
5027
var culturesToLoad = GetCultures(CultureInfo.CurrentCulture);
5128

src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHost.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Threading.Tasks;
77
using Microsoft.AspNetCore.Components.WebAssembly.Infrastructure;
88
using Microsoft.AspNetCore.Components.WebAssembly.Rendering;
9+
using Microsoft.AspNetCore.Components.WebAssembly.Services;
910
using Microsoft.Extensions.Configuration;
1011
using Microsoft.Extensions.DependencyInjection;
1112
using Microsoft.Extensions.Logging;
@@ -58,7 +59,7 @@ internal WebAssemblyHost(IServiceProvider services, IServiceScope scope, IConfig
5859
/// </summary>
5960
public IServiceProvider Services => _scope.ServiceProvider;
6061

61-
internal SatelliteResourcesLoader SatelliteResourcesLoader { get; set; } = SatelliteResourcesLoader.Instance;
62+
internal SatelliteResourcesLoader SatelliteResourcesLoader { get; set; } = new SatelliteResourcesLoader(WebAssemblyJSRuntimeInvoker.Instance);
6263

6364
/// <summary>
6465
/// Disposes the host asynchronously.

src/Components/WebAssembly/WebAssembly/test/Hosting/EntrypointInvokerTest.cs

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,12 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5-
using System.Globalization;
65
using System.IO;
76
using System.Reflection;
87
using System.Runtime.Loader;
98
using System.Threading.Tasks;
10-
using Microsoft.AspNetCore.Components.WebAssembly.Services;
119
using Microsoft.CodeAnalysis;
1210
using Microsoft.CodeAnalysis.CSharp;
13-
using Moq;
1411
using Xunit;
1512

1613
namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
@@ -36,7 +33,7 @@ public void InvokesEntrypoint_Sync_Success(bool hasReturnValue, bool hasParams)
3633
}", out var didMainExecute);
3734

3835
// Act
39-
EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { }, new TestSatelliteResourcesLoader());
36+
EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { });
4037

4138
// Assert
4239
Assert.True(didMainExecute());
@@ -66,7 +63,7 @@ public void InvokesEntrypoint_Async_Success(bool hasReturnValue, bool hasParams)
6663
// Act/Assert 1: Waits for task
6764
// The fact that we're not blocking here proves that we're not executing the
6865
// metadata-declared entrypoint, as that would block
69-
EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { }, new TestSatelliteResourcesLoader());
66+
EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { });
7067
Assert.False(didMainExecute());
7168

7269
// Act/Assert 2: Continues
@@ -91,7 +88,7 @@ public static void Main()
9188
// to handle the exception. We can't assert about what it does here, because that
9289
// would involve capturing console output, which isn't safe in unit tests. Instead
9390
// we'll check this in E2E tests.
94-
EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { }, new TestSatelliteResourcesLoader());
91+
EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { });
9592
Assert.True(didMainExecute());
9693
}
9794

@@ -110,7 +107,7 @@ public static async Task Main()
110107
}", out var didMainExecute);
111108

112109
// Act/Assert 1: Waits for task
113-
EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { }, new TestSatelliteResourcesLoader());
110+
EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { });
114111
Assert.False(didMainExecute());
115112

116113
// Act/Assert 2: Continues
@@ -152,15 +149,5 @@ public static class Program
152149

153150
return assembly;
154151
}
155-
156-
private class TestSatelliteResourcesLoader : SatelliteResourcesLoader
157-
{
158-
internal TestSatelliteResourcesLoader()
159-
: base(WebAssemblyJSRuntimeInvoker.Instance)
160-
{
161-
}
162-
163-
protected override ValueTask LoadSatelliteAssembliesForCurrentCultureAsync() => default;
164-
}
165152
}
166153
}

src/Components/WebAssembly/WebAssembly/test/Hosting/SatelliteResourcesLoaderTest.cs

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -70,45 +70,5 @@ public async Task LoadCurrentCultureResourcesAsync_DoesNotReadAssembliesWhenTher
7070
// Assert
7171
invoker.Verify(i => i.InvokeUnmarshalled<object, object, object, object[]>(ReadSatelliteAssemblies, null, null, null), Times.Never());
7272
}
73-
74-
[Fact]
75-
public async Task LoadCurrentCultureResourcesAsync_AttemptsToReadAssemblies_IfCultureIsChangedBetweenInvocation()
76-
{
77-
// Arrange
78-
using var cultureReplacer = new CultureReplacer("en-GB");
79-
var invoker = new Mock<WebAssemblyJSRuntimeInvoker>();
80-
invoker.Setup(i => i.InvokeUnmarshalled<string[], object, object, Task<object>>(GetSatelliteAssemblies, It.IsAny<string[]>(), null, null))
81-
.Returns(Task.FromResult<object>(0))
82-
.Verifiable();
83-
84-
var loader = new SatelliteResourcesLoader(invoker.Object);
85-
86-
// Act
87-
await loader.LoadCurrentCultureResourcesAsync();
88-
CultureInfo.CurrentCulture = new CultureInfo("fr-fr");
89-
await loader.LoadCurrentCultureResourcesAsync();
90-
91-
invoker.Verify(i => i.InvokeUnmarshalled<object, object, object, Task<object>>(GetSatelliteAssemblies, It.IsAny<string[]>(), null, null),
92-
Times.Exactly(2));
93-
}
94-
95-
[Fact]
96-
public async Task LoadCurrentCultureResourcesAsync_NoOps_WhenInvokedSecondTime_WithSameCulture()
97-
{
98-
// Arrange
99-
using var cultureReplacer = new CultureReplacer("en-GB");
100-
var invoker = new Mock<WebAssemblyJSRuntimeInvoker>();
101-
invoker.Setup(i => i.InvokeUnmarshalled<string[], object, object, Task<object>>(GetSatelliteAssemblies, It.IsAny<string[]>(), null, null))
102-
.Returns(Task.FromResult<object>(0));;
103-
104-
var loader = new SatelliteResourcesLoader(invoker.Object);
105-
106-
// Act
107-
await loader.LoadCurrentCultureResourcesAsync();
108-
await loader.LoadCurrentCultureResourcesAsync();
109-
110-
invoker.Verify(i => i.InvokeUnmarshalled<object, object, object, Task<object>>(GetSatelliteAssemblies, It.IsAny<string[]>(), null, null),
111-
Times.Once());
112-
}
11373
}
11474
}

src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ internal TestSatelliteResourcesLoader()
9999
{
100100
}
101101

102-
protected override ValueTask LoadSatelliteAssembliesForCurrentCultureAsync() => default;
102+
public override ValueTask LoadCurrentCultureResourcesAsync() => default;
103103
}
104104
}
105105
}

0 commit comments

Comments
 (0)