Skip to content

Commit 8bf4d2d

Browse files
authored
[Blazor] Try to load the assembly when finding out a root component or a parameter in webassembly (#52331) (#52351)
# Load assembly when finding root component or parameter in WebAssembly This change allows the system to load the assembly when a root component is defined in an RCL and rendered in WebAssembly, even if the assembly is not yet loaded into memory. ## Description When a root component is defined in an RCL and rendered in WebAssembly, it might happen that the assembly for the component is not yet loaded into memory if the app has not used any type from the dll. This causes the framework to fail finding the assembly when it tries to look for it in the list of loaded assemblies. The fix is to detect this in webassembly and try to load the root component type at that point. Same with the component parameters. Fixes #52129 ## Customer Impact A Blazor Web app may experience a failure trying to render a root component in WebAssembly, when the component is defined in a Razor Class Library. This will happen if the assembly containing the component hasn't yet been loaded on the client, and the app is trying to render that component. ## Regression? - [ ] Yes - [x] No ## Risk - [ ] High - [ ] Medium - [x] Low The assembly will already be downloaded by JS and is ready to be loaded. ## Verification - [x] Manual (required) - [x] Automated There was manual verification that the fix addressed the issue and it was automated into an E2E test. ## Packaging changes reviewed? - [ ] Yes - [ ] No - [x] N/A There are no packaging changes associated with this fix. ## When servicing release/2.1 - [ ] Make necessary changes in eng/PatchConfig.props
1 parent 858160c commit 8bf4d2d

File tree

10 files changed

+101
-7
lines changed

10 files changed

+101
-7
lines changed

AspNetCore.sln

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,6 +1780,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Output
17801780
EndProject
17811781
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.OutputCaching.StackExchangeRedis", "src\Middleware\Microsoft.AspNetCore.OutputCaching.StackExchangeRedis\src\Microsoft.AspNetCore.OutputCaching.StackExchangeRedis.csproj", "{F232B503-D412-45EE-8B31-EFD46B9FA302}"
17821782
EndProject
1783+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotReferencedInWasmCodePackage", "src\Components\test\testassets\NotReferencedInWasmCodePackage\NotReferencedInWasmCodePackage.csproj", "{433F91E4-E39D-4EB0-B798-2998B3969A2C}"
1784+
EndProject
17831785
Global
17841786
GlobalSection(SolutionConfigurationPlatforms) = preSolution
17851787
Debug|Any CPU = Debug|Any CPU
@@ -10717,6 +10719,22 @@ Global
1071710719
{F232B503-D412-45EE-8B31-EFD46B9FA302}.Release|x64.Build.0 = Release|Any CPU
1071810720
{F232B503-D412-45EE-8B31-EFD46B9FA302}.Release|x86.ActiveCfg = Release|Any CPU
1071910721
{F232B503-D412-45EE-8B31-EFD46B9FA302}.Release|x86.Build.0 = Release|Any CPU
10722+
{433F91E4-E39D-4EB0-B798-2998B3969A2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
10723+
{433F91E4-E39D-4EB0-B798-2998B3969A2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
10724+
{433F91E4-E39D-4EB0-B798-2998B3969A2C}.Debug|arm64.ActiveCfg = Debug|Any CPU
10725+
{433F91E4-E39D-4EB0-B798-2998B3969A2C}.Debug|arm64.Build.0 = Debug|Any CPU
10726+
{433F91E4-E39D-4EB0-B798-2998B3969A2C}.Debug|x64.ActiveCfg = Debug|Any CPU
10727+
{433F91E4-E39D-4EB0-B798-2998B3969A2C}.Debug|x64.Build.0 = Debug|Any CPU
10728+
{433F91E4-E39D-4EB0-B798-2998B3969A2C}.Debug|x86.ActiveCfg = Debug|Any CPU
10729+
{433F91E4-E39D-4EB0-B798-2998B3969A2C}.Debug|x86.Build.0 = Debug|Any CPU
10730+
{433F91E4-E39D-4EB0-B798-2998B3969A2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
10731+
{433F91E4-E39D-4EB0-B798-2998B3969A2C}.Release|Any CPU.Build.0 = Release|Any CPU
10732+
{433F91E4-E39D-4EB0-B798-2998B3969A2C}.Release|arm64.ActiveCfg = Release|Any CPU
10733+
{433F91E4-E39D-4EB0-B798-2998B3969A2C}.Release|arm64.Build.0 = Release|Any CPU
10734+
{433F91E4-E39D-4EB0-B798-2998B3969A2C}.Release|x64.ActiveCfg = Release|Any CPU
10735+
{433F91E4-E39D-4EB0-B798-2998B3969A2C}.Release|x64.Build.0 = Release|Any CPU
10736+
{433F91E4-E39D-4EB0-B798-2998B3969A2C}.Release|x86.ActiveCfg = Release|Any CPU
10737+
{433F91E4-E39D-4EB0-B798-2998B3969A2C}.Release|x86.Build.0 = Release|Any CPU
1072010738
EndGlobalSection
1072110739
GlobalSection(SolutionProperties) = preSolution
1072210740
HideSolutionNode = FALSE
@@ -11596,6 +11614,7 @@ Global
1159611614
{CAEB7F57-28A8-451C-95D0-45FCAA3C726C} = {C445B129-0A4D-41F5-8347-6534B6B12303}
1159711615
{A939893A-B3CD-48F6-80D3-340C8A6E275B} = {AA5ABFBC-177C-421E-B743-005E0FD1248B}
1159811616
{F232B503-D412-45EE-8B31-EFD46B9FA302} = {AA5ABFBC-177C-421E-B743-005E0FD1248B}
11617+
{433F91E4-E39D-4EB0-B798-2998B3969A2C} = {6126DCE4-9692-4EE2-B240-C65743572995}
1159911618
EndGlobalSection
1160011619
GlobalSection(ExtensibilityGlobals) = postSolution
1160111620
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}

src/Components/ComponentsNoDeps.slnf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"src\\Components\\test\\testassets\\ComponentsApp.Server\\ComponentsApp.Server.csproj",
5555
"src\\Components\\test\\testassets\\GlobalizationWasmApp\\GlobalizationWasmApp.csproj",
5656
"src\\Components\\test\\testassets\\LazyTestContentPackage\\LazyTestContentPackage.csproj",
57+
"src\\Components\\test\\testassets\\NotReferencedInWasmCodePackage\\NotReferencedInWasmCodePackage.csproj",
5758
"src\\Components\\test\\testassets\\TestContentPackage\\TestContentPackage.csproj"
5859
]
5960
}

src/Components/Shared/src/ComponentParametersTypeCache.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,26 @@ internal sealed class ComponentParametersTypeCache
4040

4141
if (assembly == null)
4242
{
43-
return null;
43+
// It might be that the assembly is not loaded yet, this can happen if the root component is defined in a
44+
// different assembly than the app and there is no reference from the app assembly to any type in the class
45+
// library that has been used yet.
46+
// In this case, try and load the assembly and look up the type again.
47+
// We only need to do this in the browser because its a different process, in the server the assembly will already
48+
// be loaded.
49+
if (OperatingSystem.IsBrowser())
50+
{
51+
try
52+
{
53+
assembly = Assembly.Load(key.Assembly);
54+
}
55+
catch
56+
{
57+
// It's fine to ignore the exception, since we'll return null below.
58+
}
59+
}
4460
}
4561

46-
return assembly.GetType(key.Type, throwOnError: false, ignoreCase: false);
62+
return assembly?.GetType(key.Type, throwOnError: false, ignoreCase: false);
4763
}
4864

4965
private struct Key : IEquatable<Key>

src/Components/Shared/src/RootComponentTypeCache.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,26 @@ internal sealed class RootComponentTypeCache
4141

4242
if (assembly == null)
4343
{
44-
return null;
44+
// It might be that the assembly is not loaded yet, this can happen if the root component is defined in a
45+
// different assembly than the app and there is no reference from the app assembly to any type in the class
46+
// library that has been used yet.
47+
// In this case, try and load the assembly and look up the type again.
48+
// We only need to do this in the browser because its a different process, in the server the assembly will already
49+
// be loaded.
50+
if (OperatingSystem.IsBrowser())
51+
{
52+
try
53+
{
54+
assembly = Assembly.Load(key.Assembly);
55+
}
56+
catch
57+
{
58+
// It's fine to ignore the exception, since we'll return null below.
59+
}
60+
}
4561
}
4662

47-
return assembly.GetType(key.Type, throwOnError: false, ignoreCase: false);
63+
return assembly?.GetType(key.Type, throwOnError: false, ignoreCase: false);
4864
}
4965

5066
private readonly struct Key : IEquatable<Key>

src/Components/test/E2ETest/ServerRenderingTests/InteractivityTest.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,15 @@ public void CanRenderInteractiveWebAssemblyComponentFromRazorClassLibrary()
6868
Browser.Equal("4", () => Browser.FindElement(By.Id("count-wasm-shared")).Text);
6969
}
7070

71+
[Fact]
72+
public void CanRenderInteractiveWebAssemblyComponentFromRazorClassLibraryThatIsNotExplicitlyReferenced()
73+
{
74+
Navigate($"{ServerPathBase}/not-explicitly-referenced-in-wasm-code");
75+
76+
// The element with id success is only rendered when webassembly has successfully loaded the component.
77+
Browser.Exists(By.Id("success"));
78+
}
79+
7180
[Fact]
7281
public void CanRenderInteractiveServerAndWebAssemblyComponentsAtTheSameTime()
7382
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@page "/not-explicitly-referenced-in-wasm-code"
2+
@using NotReferencedInWasmCodePackage
3+
<h3>RenderComponentWasmFromRazorClassLibrary</h3>
4+
5+
<NotExplicitlyLoadedFromWasmCode @rendermode="RenderMode.InteractiveWebAssembly" />

src/Components/test/testassets/Components.WasmMinimal/Components.WasmMinimal.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
</ItemGroup>
1313

1414
<ItemGroup>
15+
<ProjectReference Include="..\NotReferencedInWasmCodePackage\NotReferencedInWasmCodePackage.csproj" />
1516
<ProjectReference Include="..\TestContentPackage\TestContentPackage.csproj" />
1617
</ItemGroup>
1718

src/Components/test/testassets/Components.WasmMinimal/Program.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Reflection;
54
using Components.TestServer.Services;
65
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
76

8-
Assembly.Load(nameof(TestContentPackage));
9-
107
var builder = WebAssemblyHostBuilder.CreateDefault(args);
118
builder.Services.AddSingleton<AsyncOperationService>();
129

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<h3>NotExplicitlyLoadedFromWasmCode</h3>
2+
3+
<p>Represents a component that wasn't explicitly loaded from webassembly code by a direct type reference.
4+
</p>
5+
6+
<p>Instead, this component is only rendered in webassembly mode from the server and not loaded into the application domain until we explicitly load it via a call to Assembly.Load when we try to render the root component.
7+
</p>
8+
9+
@if(OperatingSystem.IsBrowser())
10+
{
11+
<p id="success">Running in webassembly mode.</p>
12+
}
13+
else
14+
{
15+
<p>Running in server mode.</p>
16+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Razor">
2+
3+
<PropertyGroup>
4+
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
5+
<OutputType>library</OutputType>
6+
<StaticWebAssetBasePath>_content/NotReferencedInWasmCodePackage</StaticWebAssetBasePath>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<Reference Include="Microsoft.AspNetCore.Components" />
11+
<Reference Include="Microsoft.AspNetCore.Components.Web" />
12+
</ItemGroup>
13+
14+
</Project>

0 commit comments

Comments
 (0)