From ed81b3dd6e937e132e32e492c35aa7e18cc6ef8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Mon, 28 Apr 2025 13:37:36 +0200 Subject: [PATCH 1/5] Try register LazyAssemblyLoader to DI in AddInteractiveWebAssemblyComponents --- ...ssemblyRazorComponentsBuilderExtensions.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/Components/WebAssembly/Server/src/WebAssemblyRazorComponentsBuilderExtensions.cs b/src/Components/WebAssembly/Server/src/WebAssemblyRazorComponentsBuilderExtensions.cs index 0dfb50ef840a..2fe9cd89fe34 100644 --- a/src/Components/WebAssembly/Server/src/WebAssemblyRazorComponentsBuilderExtensions.cs +++ b/src/Components/WebAssembly/Server/src/WebAssemblyRazorComponentsBuilderExtensions.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Reflection; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Endpoints.Infrastructure; @@ -25,6 +26,15 @@ public static IRazorComponentsBuilder AddInteractiveWebAssemblyComponents(this I builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); + // Try register LazyAssemblyLoader to prevent crashes during prerendering. + // TODO: Remove this once LazyAssemblyLoader is deprecated. + var lazyAssemblyLoaderType = GetLazyAssemblyLoaderType(); + + if (lazyAssemblyLoaderType != null) + { + builder.Services.AddScoped(lazyAssemblyLoaderType); + } + return builder; } @@ -46,4 +56,17 @@ public static IRazorComponentsBuilder AddAuthenticationStateSerialization(this I return builder; } + + private static Type? GetLazyAssemblyLoaderType() + { + try + { + var assembly = Assembly.Load("Microsoft.AspNetCore.Components.WebAssembly"); + return assembly.GetType("Microsoft.AspNetCore.Components.WebAssembly.Services.LazyAssemblyLoader", throwOnError: false); + } + catch + { + return null; + } + } } From 307e49d9a20f6db79382f0cf1007a8b7e94444e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Mon, 28 Apr 2025 13:51:32 +0200 Subject: [PATCH 2/5] Use TryAddScoped instead of AddScoped --- .../Server/src/WebAssemblyRazorComponentsBuilderExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/WebAssembly/Server/src/WebAssemblyRazorComponentsBuilderExtensions.cs b/src/Components/WebAssembly/Server/src/WebAssemblyRazorComponentsBuilderExtensions.cs index 2fe9cd89fe34..84686d6e637a 100644 --- a/src/Components/WebAssembly/Server/src/WebAssemblyRazorComponentsBuilderExtensions.cs +++ b/src/Components/WebAssembly/Server/src/WebAssemblyRazorComponentsBuilderExtensions.cs @@ -32,7 +32,7 @@ public static IRazorComponentsBuilder AddInteractiveWebAssemblyComponents(this I if (lazyAssemblyLoaderType != null) { - builder.Services.AddScoped(lazyAssemblyLoaderType); + builder.Services.TryAddScoped(lazyAssemblyLoaderType); } return builder; From 791a82f5d25fd531ee499fb54274f9610509d19e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Tue, 6 May 2025 15:06:02 +0200 Subject: [PATCH 3/5] Add E2E check that Blazor template adds necessary assembly for LazyAssemblyLoader registration --- .../BlazorWebTemplateTest.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorWebTemplateTest.cs b/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorWebTemplateTest.cs index dc2cf5b63982..416f248f4fef 100644 --- a/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorWebTemplateTest.cs +++ b/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorWebTemplateTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -26,6 +27,12 @@ public async Task BlazorWebTemplate_Works(BrowserKind browserKind, string intera args: ["-int", interactivityOption], getTargetProject: GetTargetProject); + if (HasClientProject()) + { + // TODO: Remove this when LazyAssemblyLoader is no longer being used. + AssertServerProjectCanUseLazyAssemblyLoader(GetTargetProject(project)); + } + // There won't be a counter page when the 'None' interactivity option is used var pagesToExclude = interactivityOption is "None" ? BlazorTemplatePages.Counter @@ -92,4 +99,12 @@ private static async Task AssertWebAssemblyCompressionFormatAsync(AspNetProcess }); Assert.Equal(expectedEncoding, response.Content.Headers.ContentEncoding.Single()); } + + private static void AssertServerProjectCanUseLazyAssemblyLoader(Project project) + { + var assemblyName = "Microsoft.AspNetCore.Components.WebAssembly.dll"; + var fullPath = Path.Combine(project.TemplateBuildDir, assemblyName); + var doesExist = File.Exists(fullPath); + Assert.True(doesExist, "Required assembly does not exist"); + } } From 80cf245c4de6bff7a3494fff4c91526b66a92101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Wed, 7 May 2025 10:09:45 +0200 Subject: [PATCH 4/5] Add comment explaining the new template test --- .../test/Templates.Blazor.Tests/BlazorWebTemplateTest.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorWebTemplateTest.cs b/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorWebTemplateTest.cs index 416f248f4fef..7a63733db354 100644 --- a/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorWebTemplateTest.cs +++ b/src/ProjectTemplates/test/Templates.Blazor.Tests/BlazorWebTemplateTest.cs @@ -29,7 +29,13 @@ public async Task BlazorWebTemplate_Works(BrowserKind browserKind, string intera if (HasClientProject()) { - // TODO: Remove this when LazyAssemblyLoader is no longer being used. + // In order to prevent an exception casued by missing LazyAssemblyLoader during pre-rendering, + // we are registering it into DI in AddInteractiveWebAssemblyComponents. + // To avoid adding new references between assemblies we try to resolve the type during run-time using reflection. + // This assert is here to check that the assembly containing LazyAssemblyLoader is actually present in a standard app + // created from the Blazor Web template. + // See https://github.com/dotnet/aspnetcore/issues/51966. + // TODO: Remove this when LazyAssemblyLoader is no longer being used, or the dependency graph changes so reflection is no longer needed. AssertServerProjectCanUseLazyAssemblyLoader(GetTargetProject(project)); } From 23399918d67b1b6632a9b6181832a20bbc3b3173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Rozto=C4=8Dil?= Date: Wed, 7 May 2025 10:50:13 +0200 Subject: [PATCH 5/5] Add DynamicDependencyAttribute to prevent LazyAssemblyBuilder from being trimmed --- .../WebAssemblyRazorComponentsBuilderExtensions.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Components/WebAssembly/Server/src/WebAssemblyRazorComponentsBuilderExtensions.cs b/src/Components/WebAssembly/Server/src/WebAssemblyRazorComponentsBuilderExtensions.cs index 84686d6e637a..a55065ca9b45 100644 --- a/src/Components/WebAssembly/Server/src/WebAssemblyRazorComponentsBuilderExtensions.cs +++ b/src/Components/WebAssembly/Server/src/WebAssemblyRazorComponentsBuilderExtensions.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using System.Reflection; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; @@ -15,6 +16,9 @@ namespace Microsoft.Extensions.DependencyInjection; /// public static class WebAssemblyRazorComponentsBuilderExtensions { + private const string LazyAssemblyLoaderAssemblyName = "Microsoft.AspNetCore.Components.WebAssembly"; + private const string LazyAssemblyLoaderTypeName = "Microsoft.AspNetCore.Components.WebAssembly.Services.LazyAssemblyLoader"; + /// /// Adds services to support rendering interactive WebAssembly components. /// @@ -27,7 +31,7 @@ public static IRazorComponentsBuilder AddInteractiveWebAssemblyComponents(this I builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); // Try register LazyAssemblyLoader to prevent crashes during prerendering. - // TODO: Remove this once LazyAssemblyLoader is deprecated. + // TODO: Remove this once LazyAssemblyLoader is no longer used. var lazyAssemblyLoaderType = GetLazyAssemblyLoaderType(); if (lazyAssemblyLoaderType != null) @@ -57,12 +61,13 @@ public static IRazorComponentsBuilder AddAuthenticationStateSerialization(this I return builder; } + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors, LazyAssemblyLoaderTypeName, LazyAssemblyLoaderAssemblyName)] private static Type? GetLazyAssemblyLoaderType() { try { - var assembly = Assembly.Load("Microsoft.AspNetCore.Components.WebAssembly"); - return assembly.GetType("Microsoft.AspNetCore.Components.WebAssembly.Services.LazyAssemblyLoader", throwOnError: false); + var assembly = Assembly.Load(LazyAssemblyLoaderAssemblyName); + return assembly.GetType(LazyAssemblyLoaderTypeName, throwOnError: false); } catch {