From e83a0f7db5769b4a2163b67f1cbfcb964202336f Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Thu, 26 Mar 2026 09:10:31 +0100 Subject: [PATCH 1/6] rebase --- .../src/AttributeAuthorizeDataCache.cs | 2 +- .../Components/src/BindConverter.cs | 4 +- .../Components/src/ChangeDetection.cs | 2 +- .../Components/src/ComponentFactory.cs | 2 +- .../src/DefaultComponentActivator.cs | 2 +- .../src/DefaultComponentPropertyActivator.cs | 2 +- .../PersistentServicesRegistry.cs | 2 +- ...PersistentStateValueProviderKeyResolver.cs | 2 +- ...stentValueProviderComponentSubscription.cs | 2 +- .../src/Properties/ILLink.Substitutions.xml | 2 +- .../src/Reflection/ComponentProperties.cs | 2 +- src/Components/Components/src/RenderHandle.cs | 2 +- .../src/RenderTree/EventArgsTypeCache.cs | 2 +- .../src/RenderTree/RenderTreeDiffBuilder.cs | 14 +++--- .../Components/src/RenderTree/Renderer.cs | 10 ++-- src/Components/Components/src/RouteView.cs | 2 +- .../Components/src/Routing/RouteTable.cs | 2 +- .../Components/src/Routing/Router.cs | 4 +- .../Components/test/RendererTest.cs | 20 ++++---- .../src/Rendering/EndpointComponentState.cs | 2 +- src/Components/Forms/src/FieldIdentifier.cs | 2 +- .../ExpressionFormatter.cs | 4 +- src/Components/Shared/src/HotReloadManager.cs | 7 ++- src/Components/Shared/src/RootTypeCache.cs | 4 +- .../Web/src/Forms/ExpressionMemberAccessor.cs | 2 +- .../src/JSComponents/JSComponentInterop.cs | 2 +- .../E2ETest/Tests/WebAssemblyTrimmingTest.cs | 47 +++++++++++++++++++ .../BasicTestApp/HotReloadTrimmingCheck.razor | 37 +++++++++++++++ .../test/testassets/BasicTestApp/Index.razor | 1 + .../Components.TestServer.csproj | 6 +++ .../Components.TestServer/HotReloadStartup.cs | 2 +- 31 files changed, 147 insertions(+), 49 deletions(-) create mode 100644 src/Components/test/E2ETest/Tests/WebAssemblyTrimmingTest.cs create mode 100644 src/Components/test/testassets/BasicTestApp/HotReloadTrimmingCheck.razor diff --git a/src/Components/Authorization/src/AttributeAuthorizeDataCache.cs b/src/Components/Authorization/src/AttributeAuthorizeDataCache.cs index 6f7f4bd06539..e5b66e0e6ad2 100644 --- a/src/Components/Authorization/src/AttributeAuthorizeDataCache.cs +++ b/src/Components/Authorization/src/AttributeAuthorizeDataCache.cs @@ -11,7 +11,7 @@ internal static class AttributeAuthorizeDataCache { static AttributeAuthorizeDataCache() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } diff --git a/src/Components/Components/src/BindConverter.cs b/src/Components/Components/src/BindConverter.cs index a3e68de026a5..b14137fc66b7 100644 --- a/src/Components/Components/src/BindConverter.cs +++ b/src/Components/Components/src/BindConverter.cs @@ -1670,7 +1670,7 @@ private static class FormatterDelegateCache static FormatterDelegateCache() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += _cache.Clear; } @@ -1867,7 +1867,7 @@ internal static class ParserDelegateCache static ParserDelegateCache() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += _cache.Clear; } diff --git a/src/Components/Components/src/ChangeDetection.cs b/src/Components/Components/src/ChangeDetection.cs index 952debeb8f90..c36baf1306c2 100644 --- a/src/Components/Components/src/ChangeDetection.cs +++ b/src/Components/Components/src/ChangeDetection.cs @@ -12,7 +12,7 @@ internal sealed class ChangeDetection static ChangeDetection() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += _immutableObjectTypesCache.Clear; } diff --git a/src/Components/Components/src/ComponentFactory.cs b/src/Components/Components/src/ComponentFactory.cs index a4b5aaa3224e..ada28abe59ca 100644 --- a/src/Components/Components/src/ComponentFactory.cs +++ b/src/Components/Components/src/ComponentFactory.cs @@ -21,7 +21,7 @@ internal sealed class ComponentFactory static ComponentFactory() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } diff --git a/src/Components/Components/src/DefaultComponentActivator.cs b/src/Components/Components/src/DefaultComponentActivator.cs index 24b92dcb1056..d22e6d5a3e2e 100644 --- a/src/Components/Components/src/DefaultComponentActivator.cs +++ b/src/Components/Components/src/DefaultComponentActivator.cs @@ -14,7 +14,7 @@ internal sealed class DefaultComponentActivator(IServiceProvider serviceProvider static DefaultComponentActivator() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } diff --git a/src/Components/Components/src/DefaultComponentPropertyActivator.cs b/src/Components/Components/src/DefaultComponentPropertyActivator.cs index 3fa29d33903c..36e728d0ad8c 100644 --- a/src/Components/Components/src/DefaultComponentPropertyActivator.cs +++ b/src/Components/Components/src/DefaultComponentPropertyActivator.cs @@ -20,7 +20,7 @@ private const BindingFlags InjectablePropertyBindingFlags static DefaultComponentPropertyActivator() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } diff --git a/src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs b/src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs index e27e5c2560b0..ae1e52f23ae2 100644 --- a/src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs +++ b/src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs @@ -27,7 +27,7 @@ internal sealed class PersistentServicesRegistry static PersistentServicesRegistry() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += _cachedAccessorsByType.Clear; } diff --git a/src/Components/Components/src/PersistentState/PersistentStateValueProviderKeyResolver.cs b/src/Components/Components/src/PersistentState/PersistentStateValueProviderKeyResolver.cs index 8de18363342c..127d12e17aaa 100644 --- a/src/Components/Components/src/PersistentState/PersistentStateValueProviderKeyResolver.cs +++ b/src/Components/Components/src/PersistentState/PersistentStateValueProviderKeyResolver.cs @@ -18,7 +18,7 @@ internal static class PersistentStateValueProviderKeyResolver static PersistentStateValueProviderKeyResolver() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += ClearCaches; } diff --git a/src/Components/Components/src/PersistentState/PersistentValueProviderComponentSubscription.cs b/src/Components/Components/src/PersistentState/PersistentValueProviderComponentSubscription.cs index 35ad18735816..1965786adb50 100644 --- a/src/Components/Components/src/PersistentState/PersistentValueProviderComponentSubscription.cs +++ b/src/Components/Components/src/PersistentState/PersistentValueProviderComponentSubscription.cs @@ -21,7 +21,7 @@ internal partial class PersistentValueProviderComponentSubscription : IDisposabl static PersistentValueProviderComponentSubscription() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += ClearCaches; } diff --git a/src/Components/Components/src/Properties/ILLink.Substitutions.xml b/src/Components/Components/src/Properties/ILLink.Substitutions.xml index 2e45bc3eb6e3..30fbb3914e46 100644 --- a/src/Components/Components/src/Properties/ILLink.Substitutions.xml +++ b/src/Components/Components/src/Properties/ILLink.Substitutions.xml @@ -1,7 +1,7 @@ - + diff --git a/src/Components/Components/src/Reflection/ComponentProperties.cs b/src/Components/Components/src/Reflection/ComponentProperties.cs index 353576925963..3172a21f24b5 100644 --- a/src/Components/Components/src/Reflection/ComponentProperties.cs +++ b/src/Components/Components/src/Reflection/ComponentProperties.cs @@ -14,7 +14,7 @@ internal static class ComponentProperties { static ComponentProperties() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } diff --git a/src/Components/Components/src/RenderHandle.cs b/src/Components/Components/src/RenderHandle.cs index edcb644bddfb..6f226c2a741e 100644 --- a/src/Components/Components/src/RenderHandle.cs +++ b/src/Components/Components/src/RenderHandle.cs @@ -49,7 +49,7 @@ public Dispatcher Dispatcher /// /// Gets a value that determines if the is triggering a render in response to a metadata update (hot-reload) change. /// - public bool IsRenderingOnMetadataUpdate => HotReloadManager.Default.MetadataUpdateSupported && (_renderer?.IsRenderingOnMetadataUpdate ?? false); + public bool IsRenderingOnMetadataUpdate => HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled && (_renderer?.IsRenderingOnMetadataUpdate ?? false); internal bool IsRendererDisposed => _renderer?.Disposed ?? throw new InvalidOperationException("No renderer has been initialized."); diff --git a/src/Components/Components/src/RenderTree/EventArgsTypeCache.cs b/src/Components/Components/src/RenderTree/EventArgsTypeCache.cs index 5b9abafb0bed..1c9cbd96c10c 100644 --- a/src/Components/Components/src/RenderTree/EventArgsTypeCache.cs +++ b/src/Components/Components/src/RenderTree/EventArgsTypeCache.cs @@ -13,7 +13,7 @@ internal static class EventArgsTypeCache static EventArgsTypeCache() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += Cache.Clear; } diff --git a/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs b/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs index e61fb6d2d932..bcbb6362789d 100644 --- a/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs +++ b/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs @@ -684,7 +684,7 @@ private static void AppendDiffEntriesForFramesWithSameSequence( var oldParameters = new ParameterView(ParameterViewLifetime.Unbound, oldTree, oldFrameIndex); var newParametersLifetime = new ParameterViewLifetime(diffContext.BatchBuilder); var newParameters = new ParameterView(newParametersLifetime, newTree, newFrameIndex); - var isHotReload = HotReloadManager.Default.MetadataUpdateSupported && diffContext.Renderer.IsRenderingOnMetadataUpdate; + var isHotReload = HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled && diffContext.Renderer.IsRenderingOnMetadataUpdate; if (isHotReload && newParameters.HasRemovedDirectParameters(oldParameters)) { @@ -754,13 +754,13 @@ private static string CreateDiffErrorMessage(ref DiffContext diffContext, int ne { var newTree = diffContext.NewTree; var unsupportedFrameType = newTree[newFrameIndex].FrameTypeField; - + // Build component hierarchy path var componentPath = BuildComponentPath(diffContext.Renderer, diffContext.ComponentId); - + // Build frame types descriptor var frameTypesDescriptor = BuildFrameTypeDescriptor(newTree, newFrameIndex); - + return $"Encountered an unsupported frame type during diffing {unsupportedFrameType} for Component Path: '{componentPath}' on tree with length '{newTree.Length}' and contents '{frameTypesDescriptor}'."; } @@ -771,7 +771,7 @@ private static string BuildFrameTypeDescriptor(RenderTreeFrame[] renderTree, int { frameTypes.Add(renderTree[i].FrameTypeField.ToString()); } - + return string.Join(", ", frameTypes); } @@ -779,14 +779,14 @@ private static string BuildComponentPath(Renderer renderer, int componentId) { var componentPath = new List(); var currentComponentState = renderer.GetRequiredComponentState(componentId); - + while (currentComponentState is not null) { var componentType = currentComponentState.Component.GetType(); componentPath.Insert(0, componentType.Name); currentComponentState = currentComponentState.ParentComponentState; } - + return string.Join(" -> ", componentPath); } diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index 6b6b9df93f8b..bde5ad2d54b1 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -114,7 +114,7 @@ public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, internal ICascadingValueSupplier[] ServiceProviderCascadingValueSuppliers { get; } - internal HotReloadManager HotReloadManager { get; set; } = HotReloadManager.Default; + internal HotReloadManager? HotReloadManager { get; set; } = HotReloadManager.IsSupported ? HotReloadManager.Default : null; private static IComponentActivator GetComponentActivatorOrDefault(IServiceProvider serviceProvider) { @@ -237,7 +237,7 @@ protected internal int AssignRootComponentId(IComponent component) if (!_hotReloadInitialized) { _hotReloadInitialized = true; - if (HotReloadManager.MetadataUpdateSupported) + if (HotReload.HotReloadManager.IsSupported && HotReloadManager != null && HotReloadManager.IsEnabled) { // Capture the current ExecutionContext so AsyncLocal values present during initial root component // registration flow through to hot reload re-renders. Without this, hot reload callbacks execute @@ -302,7 +302,7 @@ protected internal async Task RenderRootComponentAsync(int componentId, Paramete _pendingTasks ??= new(); var componentState = GetRequiredRootComponentState(componentId); - if (HotReloadManager.MetadataUpdateSupported) + if (HotReload.HotReloadManager.IsSupported && HotReloadManager != null && HotReloadManager.IsEnabled) { // When we're doing hot-reload, stash away the parameters used while rendering root components. // We'll use this to trigger re-renders on hot reload updates. @@ -332,7 +332,7 @@ protected internal void RemoveRootComponent(int componentId) // Currently there's no known scenario where we need to support calling RemoveRootComponentAsync // during a batch, but if a scenario emerges we can add support. _batchBuilder.ComponentDisposalQueue.Enqueue(componentId); - if (HotReloadManager.MetadataUpdateSupported) + if (HotReload.HotReloadManager.IsSupported && HotReloadManager != null && HotReloadManager.IsEnabled) { _rootComponentsLatestParameters?.Remove(componentId); } @@ -1247,7 +1247,7 @@ protected virtual void Dispose(bool disposing) _rendererIsDisposed = true; } - if (_hotReloadInitialized && HotReloadManager.MetadataUpdateSupported && _hotReloadRenderHandler is not null) + if (HotReload.HotReloadManager.IsSupported && HotReloadManager != null && HotReloadManager.IsEnabled && _hotReloadInitialized && _hotReloadRenderHandler is not null) { HotReloadManager.OnDeltaApplied -= _hotReloadRenderHandler.RerenderOnHotReload; } diff --git a/src/Components/Components/src/RouteView.cs b/src/Components/Components/src/RouteView.cs index 36560a2aa342..1fa034d60d6b 100644 --- a/src/Components/Components/src/RouteView.cs +++ b/src/Components/Components/src/RouteView.cs @@ -22,7 +22,7 @@ public class RouteView : IComponent static RouteView() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += _layoutAttributeCache.Clear; } diff --git a/src/Components/Components/src/Routing/RouteTable.cs b/src/Components/Components/src/Routing/RouteTable.cs index f56beec826b9..74c204626081 100644 --- a/src/Components/Components/src/Routing/RouteTable.cs +++ b/src/Components/Components/src/Routing/RouteTable.cs @@ -16,7 +16,7 @@ internal sealed class RouteTable(TreeRouter treeRouter) static RouteTable() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += _routeEntryCache.Clear; } diff --git a/src/Components/Components/src/Routing/Router.cs b/src/Components/Components/src/Routing/Router.cs index 54fc0c87aa46..064b9cfaa475 100644 --- a/src/Components/Components/src/Routing/Router.cs +++ b/src/Components/Components/src/Routing/Router.cs @@ -111,7 +111,7 @@ public void Attach(RenderHandle renderHandle) NavigationManager.OnNotFound += OnNotFound; RoutingStateProvider = ServiceProvider.GetService(); - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += ClearRouteCaches; } @@ -179,7 +179,7 @@ public void Dispose() { NavigationManager.LocationChanged -= OnLocationChanged; NavigationManager.OnNotFound -= OnNotFound; - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied -= ClearRouteCaches; } diff --git a/src/Components/Components/test/RendererTest.cs b/src/Components/Components/test/RendererTest.cs index 56fc26840edd..335552d11697 100644 --- a/src/Components/Components/test/RendererTest.cs +++ b/src/Components/Components/test/RendererTest.cs @@ -4982,11 +4982,12 @@ public async Task DisposeAsyncCallsComponentDisposeAsyncOnSyncContext() } [Fact] - public async Task NoHotReloadListenersAreRegistered_WhenMetadataUpdatesAreNotSupported() + public async Task NoHotReloadListenersAreRegistered_WhenHotReloadIsDisabled() { // Arrange await using var renderer = new TestRenderer(); - var hotReloadManager = new HotReloadManager { MetadataUpdateSupported = false }; + var hotReloadManager = new HotReloadManager { IsEnabled = false }; + HotReloadManager.Default.IsEnabled = false; renderer.HotReloadManager = hotReloadManager; var component = new TestComponent(builder => { @@ -5008,7 +5009,8 @@ public async Task DisposingRenderer_UnsubsribesFromHotReloadManager() { // Arrange var renderer = new TestRenderer(); - var hotReloadManager = new HotReloadManager { MetadataUpdateSupported = true }; + var hotReloadManager = new HotReloadManager { IsEnabled = true }; + HotReloadManager.Default.IsEnabled = true; renderer.HotReloadManager = hotReloadManager; var component = new TestComponent(builder => { @@ -5033,9 +5035,9 @@ public async Task HotReload_ReRenderPreservesAsyncLocalValues() { await using var renderer = new TestRenderer(); - var hotReloadManager = new HotReloadManager { MetadataUpdateSupported = true }; + var hotReloadManager = new HotReloadManager { IsEnabled = true }; renderer.HotReloadManager = hotReloadManager; - HotReloadManager.Default.MetadataUpdateSupported = true; + HotReloadManager.Default.IsEnabled = true; var component = new AsyncLocalCaptureComponent(); @@ -5223,7 +5225,7 @@ public void RenderFragmentContravariance_WorksWithInterfaceHierarchy() { // C# variance only works with reference types. This test uses interface hierarchy. // IComparable is contravariant, so we can demonstrate the concept - + // Arrange - Create a fragment that accepts any IComparable RenderFragment baseFragment = (IComparable value) => builder => { @@ -5244,7 +5246,7 @@ public void RenderFragmentContravariance_WorksWithInterfaceHierarchy() public void RenderFragmentContravariance_WorksWithObjectToPrimitiveWrapper() { // For value types, contravariance only works when going through object (boxing) - + // Arrange - Create a fragment that accepts object RenderFragment baseFragment = (object value) => builder => { @@ -5265,7 +5267,7 @@ public void RenderFragmentContravariance_WorksWithObjectToPrimitiveWrapper() public void RenderFragmentContravariance_WorksWithComparableTypes() { // Demonstrating contravariance with reference types implementing IComparable - + // Arrange - Create a fragment that accepts IComparable RenderFragment baseFragment = (IComparable value) => builder => { @@ -6332,7 +6334,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) private struct TestStructWithInterface : IComparable { public int Value { get; set; } - + public int CompareTo(object obj) { if (obj is TestStructWithInterface other) diff --git a/src/Components/Endpoints/src/Rendering/EndpointComponentState.cs b/src/Components/Endpoints/src/Rendering/EndpointComponentState.cs index 6c03d0585f2f..f63b2bd6d4f1 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointComponentState.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointComponentState.cs @@ -19,7 +19,7 @@ internal sealed class EndpointComponentState : ComponentState static EndpointComponentState() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += _streamRenderingAttributeByComponentType.Clear; } diff --git a/src/Components/Forms/src/FieldIdentifier.cs b/src/Components/Forms/src/FieldIdentifier.cs index 5764b018e4ce..f7079f402269 100644 --- a/src/Components/Forms/src/FieldIdentifier.cs +++ b/src/Components/Forms/src/FieldIdentifier.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Components.Forms; static FieldIdentifier() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } diff --git a/src/Components/Shared/src/ExpressionFormatting/ExpressionFormatter.cs b/src/Components/Shared/src/ExpressionFormatting/ExpressionFormatter.cs index fa2febd3754e..6308f7944572 100644 --- a/src/Components/Shared/src/ExpressionFormatting/ExpressionFormatter.cs +++ b/src/Components/Shared/src/ExpressionFormatting/ExpressionFormatter.cs @@ -14,7 +14,7 @@ internal static class ExpressionFormatter { static ExpressionFormatter() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } @@ -77,7 +77,7 @@ public static string FormatLambda(LambdaExpression expression, string? prefix = builder.InsertFront("]"); FormatIndexArgument(methodCallExpression.Arguments[0], ref builder); builder.InsertFront("["); - + break; case ExpressionType.ArrayIndex: diff --git a/src/Components/Shared/src/HotReloadManager.cs b/src/Components/Shared/src/HotReloadManager.cs index f3ca59cf2651..7a59013621c5 100644 --- a/src/Components/Shared/src/HotReloadManager.cs +++ b/src/Components/Shared/src/HotReloadManager.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.Metadata; using Microsoft.AspNetCore.Components.HotReload; @@ -12,7 +13,11 @@ internal sealed class HotReloadManager { public static readonly HotReloadManager Default = new(); - public bool MetadataUpdateSupported { get; set; } = MetadataUpdater.IsSupported; + public bool IsEnabled { get; set; } = IsSupported; + + [FeatureSwitchDefinition("System.Reflection.Metadata.MetadataUpdater.IsSupported")] + internal static bool IsSupported { get; } = + AppContext.TryGetSwitch("System.Reflection.Metadata.MetadataUpdater.IsSupported", out bool isSupported) ? isSupported : true; /// /// Gets a value that determines if OnDeltaApplied is subscribed to. diff --git a/src/Components/Shared/src/RootTypeCache.cs b/src/Components/Shared/src/RootTypeCache.cs index 016ab5acd814..3ec213d8b6ae 100644 --- a/src/Components/Shared/src/RootTypeCache.cs +++ b/src/Components/Shared/src/RootTypeCache.cs @@ -19,7 +19,7 @@ internal sealed class RootTypeCache : IDisposable public RootTypeCache() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } @@ -29,7 +29,7 @@ public RootTypeCache() public void Dispose() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied -= ClearCache; } diff --git a/src/Components/Web/src/Forms/ExpressionMemberAccessor.cs b/src/Components/Web/src/Forms/ExpressionMemberAccessor.cs index e1a81d4f0062..acee30f11aee 100644 --- a/src/Components/Web/src/Forms/ExpressionMemberAccessor.cs +++ b/src/Components/Web/src/Forms/ExpressionMemberAccessor.cs @@ -17,7 +17,7 @@ internal static class ExpressionMemberAccessor static ExpressionMemberAccessor() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } diff --git a/src/Components/Web/src/JSComponents/JSComponentInterop.cs b/src/Components/Web/src/JSComponents/JSComponentInterop.cs index 9df1c3288f71..7a7fe85a7c74 100644 --- a/src/Components/Web/src/JSComponents/JSComponentInterop.cs +++ b/src/Components/Web/src/JSComponents/JSComponentInterop.cs @@ -27,7 +27,7 @@ public class JSComponentInterop static JSComponentInterop() { - if (HotReloadManager.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) { HotReloadManager.Default.OnDeltaApplied += ParameterTypeCaches.Clear; } diff --git a/src/Components/test/E2ETest/Tests/WebAssemblyTrimmingTest.cs b/src/Components/test/E2ETest/Tests/WebAssemblyTrimmingTest.cs new file mode 100644 index 000000000000..e92ea34f7e67 --- /dev/null +++ b/src/Components/test/E2ETest/Tests/WebAssemblyTrimmingTest.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using BasicTestApp; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.E2ETesting; +using OpenQA.Selenium; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Components.E2ETest.Tests; + +public class WebAssemblyTrimmingTest : ServerTestBase> +{ + public WebAssemblyTrimmingTest( + BrowserFixture browserFixture, + BlazorWasmTestAppFixture serverFixture, + ITestOutputHelper output) + : base(browserFixture, serverFixture, output) + { + _serverFixture.PathBase = "/subdir"; + } + + protected override void InitializeAsyncCore() + { + base.InitializeAsyncCore(); + Navigate(ServerPathBase); + } + + [Fact] + public void HotReloadTypesAreTrimmed_WhenPublishedWithTrimming() + { + if (!_serverFixture.TestTrimmedOrMultithreadingApps) + { + // In dev mode, hot reload types are expected to be present + return; + } + + var appElement = Browser.MountTestComponent(); + + // Hot reload manager type is present, but shallow type + Browser.Equal("true", () => appElement.FindElement(By.Id("hot-reload-manager-found")).Text); + + // Verify that UpdateApplication method has been trimmed away + Browser.Equal("false", () => appElement.FindElement(By.Id("update-application-found")).Text); + } +} diff --git a/src/Components/test/testassets/BasicTestApp/HotReloadTrimmingCheck.razor b/src/Components/test/testassets/BasicTestApp/HotReloadTrimmingCheck.razor new file mode 100644 index 000000000000..6f6ed50573cb --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/HotReloadTrimmingCheck.razor @@ -0,0 +1,37 @@ +@using System.Diagnostics.CodeAnalysis + +

Hot Reload Trimming Check

+

+ HotReloadManager type found: + @HotReloadManagerFound +

+

+ UpdateApplication method found: + @UpdateApplicationFound +

+ +@code { +#nullable enable + string HotReloadManagerFound { get; set; } = "unknown"; + string UpdateApplicationFound { get; set; } = "unknown"; + + protected override void OnInitialized() + { + var hotReloadManagerType = GetHotReloadManagerType(); + HotReloadManagerFound = hotReloadManagerType is not null ? "true" : "false"; + + if (hotReloadManagerType is not null) + { + var method = hotReloadManagerType.GetMethod("UpdateApplication", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + UpdateApplicationFound = method is not null ? "true" : "false"; + } + } + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2057:UnrecognizedReflectionPattern", + Justification = "Returning null when the type is trimmed is the expected and desired behavior.")] + private static Type? GetHotReloadManagerType() + { + return Type.GetType("Microsoft.AspNetCore.Components.HotReload.HotReloadManager, Microsoft.AspNetCore.Components"); + } +#nullable restore +} \ No newline at end of file diff --git a/src/Components/test/testassets/BasicTestApp/Index.razor b/src/Components/test/testassets/BasicTestApp/Index.razor index 6749e15a575d..eedb2c34e459 100644 --- a/src/Components/test/testassets/BasicTestApp/Index.razor +++ b/src/Components/test/testassets/BasicTestApp/Index.razor @@ -141,6 +141,7 @@ + diff --git a/src/Components/test/testassets/Components.TestServer/Components.TestServer.csproj b/src/Components/test/testassets/Components.TestServer/Components.TestServer.csproj index bbd94ca13df1..53424dc3837c 100644 --- a/src/Components/test/testassets/Components.TestServer/Components.TestServer.csproj +++ b/src/Components/test/testassets/Components.TestServer/Components.TestServer.csproj @@ -104,4 +104,10 @@ <_Parameter2 Condition="'$(IsHelixJob)' == 'true'">..\BasicTestApp + + + + true + + diff --git a/src/Components/test/testassets/Components.TestServer/HotReloadStartup.cs b/src/Components/test/testassets/Components.TestServer/HotReloadStartup.cs index 51269338ae33..ea2d007d5bec 100644 --- a/src/Components/test/testassets/Components.TestServer/HotReloadStartup.cs +++ b/src/Components/test/testassets/Components.TestServer/HotReloadStartup.cs @@ -12,7 +12,7 @@ public class HotReloadStartup { public HotReloadStartup() { - HotReloadManager.Default.MetadataUpdateSupported = true; + HotReloadManager.Default.IsEnabled = true; } public void ConfigureServices(IServiceCollection services) From 59288afd14192a6a97f242e45f85c3e7880e6e82 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Thu, 26 Mar 2026 19:26:25 +0100 Subject: [PATCH 2/6] fix --- .../Components/test/RendererTest.cs | 36 +++++++++++-------- .../ServerExecutionTests/HotReloadTest.cs | 6 ++++ .../BasicTestApp/BasicTestApp.csproj | 5 +++ 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/Components/Components/test/RendererTest.cs b/src/Components/Components/test/RendererTest.cs index 335552d11697..cff73e1e3c1d 100644 --- a/src/Components/Components/test/RendererTest.cs +++ b/src/Components/Components/test/RendererTest.cs @@ -4985,23 +4985,31 @@ public async Task DisposeAsyncCallsComponentDisposeAsyncOnSyncContext() public async Task NoHotReloadListenersAreRegistered_WhenHotReloadIsDisabled() { // Arrange - await using var renderer = new TestRenderer(); - var hotReloadManager = new HotReloadManager { IsEnabled = false }; - HotReloadManager.Default.IsEnabled = false; - renderer.HotReloadManager = hotReloadManager; - var component = new TestComponent(builder => + try { - builder.OpenElement(0, "h2"); - builder.AddContent(1, "some text"); - builder.CloseElement(); - }); + await using var renderer = new TestRenderer(); + var hotReloadManager = new HotReloadManager { IsEnabled = false }; + HotReloadManager.Default.IsEnabled = false; + renderer.HotReloadManager = hotReloadManager; + var component = new TestComponent(builder => + { + builder.OpenElement(0, "h2"); + builder.AddContent(1, "some text"); + builder.CloseElement(); + }); - // Act - var componentId = renderer.AssignRootComponentId(component); - component.TriggerRender(); - Assert.False(hotReloadManager.IsSubscribedTo); + // Act + var componentId = renderer.AssignRootComponentId(component); + component.TriggerRender(); + Assert.False(hotReloadManager.IsSubscribedTo); - await renderer.DisposeAsync(); + await renderer.DisposeAsync(); + } + finally + { + // Ensure we don't affect other tests by leaving hot reload disabled + HotReloadManager.Default.IsEnabled = HotReloadManager.IsSupported; + } } [Fact] diff --git a/src/Components/test/E2ETest/ServerExecutionTests/HotReloadTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/HotReloadTest.cs index e17c426583b9..5993d0e823de 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/HotReloadTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/HotReloadTest.cs @@ -36,6 +36,12 @@ protected override void InitializeAsyncCore() [Fact] public async Task InvokingRender_CausesComponentToRender() { + if (_serverFixture.TestTrimmedOrMultithreadingApps) + { + // In published app, metrics are trimmed + return; + } + Browser.Equal("This component was rendered 1 time(s).", () => Browser.Exists(By.TagName("h2")).Text); Browser.Equal("Initial title", () => Browser.Exists(By.TagName("h3")).Text); Browser.Equal("Component with ShouldRender=false was rendered 1 time(s).", () => Browser.Exists(By.TagName("h4")).Text); diff --git a/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj b/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj index cc5d77b5a3d9..35dea697a6a8 100644 --- a/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj +++ b/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj @@ -20,6 +20,11 @@ <_BlazorBrotliCompressionLevel>NoCompression + + + true + + + true From 2cce1bcea18b6ab0772d63c905f48ddfd7b6b157 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 1 Apr 2026 10:13:48 +0200 Subject: [PATCH 5/6] feedback --- .../src/AttributeAuthorizeDataCache.cs | 2 +- ...AspNetCore.Components.Authorization.csproj | 4 +++ .../src/Properties/ILLink.Substitutions.xml | 7 +++++ .../Components/src/BindConverter.cs | 4 +-- .../Components/src/ChangeDetection.cs | 2 +- .../Components/src/ComponentFactory.cs | 2 +- .../src/DefaultComponentActivator.cs | 2 +- .../src/DefaultComponentPropertyActivator.cs | 2 +- .../PersistentServicesRegistry.cs | 2 +- ...PersistentStateValueProviderKeyResolver.cs | 2 +- ...stentValueProviderComponentSubscription.cs | 2 +- .../src/Reflection/ComponentProperties.cs | 2 +- src/Components/Components/src/RenderHandle.cs | 2 +- .../src/RenderTree/EventArgsTypeCache.cs | 2 +- .../src/RenderTree/RenderTreeDiffBuilder.cs | 2 +- src/Components/Components/src/RouteView.cs | 2 +- .../Components/src/Routing/RouteTable.cs | 2 +- .../Components/src/Routing/Router.cs | 4 +-- .../Components/test/RendererTest.cs | 10 +++---- ...oft.AspNetCore.Components.Endpoints.csproj | 4 +++ .../src/Properties/ILLink.Substitutions.xml | 7 +++++ .../src/Rendering/EndpointComponentState.cs | 2 +- src/Components/Forms/src/FieldIdentifier.cs | 2 +- ...crosoft.AspNetCore.Components.Forms.csproj | 4 +++ .../src/Properties/ILLink.Substitutions.xml | 7 +++++ ...rosoft.AspNetCore.Components.Server.csproj | 4 +++ .../src/Properties/ILLink.Substitutions.xml | 7 +++++ .../ExpressionFormatter.cs | 2 +- src/Components/Shared/src/HotReloadManager.cs | 4 +-- src/Components/Shared/src/RootTypeCache.cs | 4 +-- .../Web/src/Forms/ExpressionMemberAccessor.cs | 2 +- .../src/JSComponents/JSComponentInterop.cs | 2 +- .../src/Properties/ILLink.Substitutions.xml | 3 ++ .../BasicTestApp/HotReloadTrimmingCheck.razor | 28 +++++++++++-------- .../Components.TestServer/HotReloadStartup.cs | 2 +- 35 files changed, 95 insertions(+), 46 deletions(-) create mode 100644 src/Components/Authorization/src/Properties/ILLink.Substitutions.xml create mode 100644 src/Components/Endpoints/src/Properties/ILLink.Substitutions.xml create mode 100644 src/Components/Forms/src/Properties/ILLink.Substitutions.xml create mode 100644 src/Components/Server/src/Properties/ILLink.Substitutions.xml diff --git a/src/Components/Authorization/src/AttributeAuthorizeDataCache.cs b/src/Components/Authorization/src/AttributeAuthorizeDataCache.cs index e5b66e0e6ad2..e37d2aed57d7 100644 --- a/src/Components/Authorization/src/AttributeAuthorizeDataCache.cs +++ b/src/Components/Authorization/src/AttributeAuthorizeDataCache.cs @@ -11,7 +11,7 @@ internal static class AttributeAuthorizeDataCache { static AttributeAuthorizeDataCache() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } diff --git a/src/Components/Authorization/src/Microsoft.AspNetCore.Components.Authorization.csproj b/src/Components/Authorization/src/Microsoft.AspNetCore.Components.Authorization.csproj index cef47c8d844d..5d715cb928ba 100644 --- a/src/Components/Authorization/src/Microsoft.AspNetCore.Components.Authorization.csproj +++ b/src/Components/Authorization/src/Microsoft.AspNetCore.Components.Authorization.csproj @@ -18,4 +18,8 @@ + + + + diff --git a/src/Components/Authorization/src/Properties/ILLink.Substitutions.xml b/src/Components/Authorization/src/Properties/ILLink.Substitutions.xml new file mode 100644 index 000000000000..cf09080ae79b --- /dev/null +++ b/src/Components/Authorization/src/Properties/ILLink.Substitutions.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Components/Components/src/BindConverter.cs b/src/Components/Components/src/BindConverter.cs index b14137fc66b7..d4971de515d3 100644 --- a/src/Components/Components/src/BindConverter.cs +++ b/src/Components/Components/src/BindConverter.cs @@ -1670,7 +1670,7 @@ private static class FormatterDelegateCache static FormatterDelegateCache() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += _cache.Clear; } @@ -1867,7 +1867,7 @@ internal static class ParserDelegateCache static ParserDelegateCache() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += _cache.Clear; } diff --git a/src/Components/Components/src/ChangeDetection.cs b/src/Components/Components/src/ChangeDetection.cs index c36baf1306c2..9fef8303d826 100644 --- a/src/Components/Components/src/ChangeDetection.cs +++ b/src/Components/Components/src/ChangeDetection.cs @@ -12,7 +12,7 @@ internal sealed class ChangeDetection static ChangeDetection() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += _immutableObjectTypesCache.Clear; } diff --git a/src/Components/Components/src/ComponentFactory.cs b/src/Components/Components/src/ComponentFactory.cs index ada28abe59ca..0b3d84796196 100644 --- a/src/Components/Components/src/ComponentFactory.cs +++ b/src/Components/Components/src/ComponentFactory.cs @@ -21,7 +21,7 @@ internal sealed class ComponentFactory static ComponentFactory() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } diff --git a/src/Components/Components/src/DefaultComponentActivator.cs b/src/Components/Components/src/DefaultComponentActivator.cs index d22e6d5a3e2e..4e868cfcc4e6 100644 --- a/src/Components/Components/src/DefaultComponentActivator.cs +++ b/src/Components/Components/src/DefaultComponentActivator.cs @@ -14,7 +14,7 @@ internal sealed class DefaultComponentActivator(IServiceProvider serviceProvider static DefaultComponentActivator() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } diff --git a/src/Components/Components/src/DefaultComponentPropertyActivator.cs b/src/Components/Components/src/DefaultComponentPropertyActivator.cs index 36e728d0ad8c..396bba438a10 100644 --- a/src/Components/Components/src/DefaultComponentPropertyActivator.cs +++ b/src/Components/Components/src/DefaultComponentPropertyActivator.cs @@ -20,7 +20,7 @@ private const BindingFlags InjectablePropertyBindingFlags static DefaultComponentPropertyActivator() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } diff --git a/src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs b/src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs index ae1e52f23ae2..79d7be17618d 100644 --- a/src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs +++ b/src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs @@ -27,7 +27,7 @@ internal sealed class PersistentServicesRegistry static PersistentServicesRegistry() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += _cachedAccessorsByType.Clear; } diff --git a/src/Components/Components/src/PersistentState/PersistentStateValueProviderKeyResolver.cs b/src/Components/Components/src/PersistentState/PersistentStateValueProviderKeyResolver.cs index 127d12e17aaa..c12dbe12533d 100644 --- a/src/Components/Components/src/PersistentState/PersistentStateValueProviderKeyResolver.cs +++ b/src/Components/Components/src/PersistentState/PersistentStateValueProviderKeyResolver.cs @@ -18,7 +18,7 @@ internal static class PersistentStateValueProviderKeyResolver static PersistentStateValueProviderKeyResolver() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += ClearCaches; } diff --git a/src/Components/Components/src/PersistentState/PersistentValueProviderComponentSubscription.cs b/src/Components/Components/src/PersistentState/PersistentValueProviderComponentSubscription.cs index 1965786adb50..624439c8a592 100644 --- a/src/Components/Components/src/PersistentState/PersistentValueProviderComponentSubscription.cs +++ b/src/Components/Components/src/PersistentState/PersistentValueProviderComponentSubscription.cs @@ -21,7 +21,7 @@ internal partial class PersistentValueProviderComponentSubscription : IDisposabl static PersistentValueProviderComponentSubscription() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += ClearCaches; } diff --git a/src/Components/Components/src/Reflection/ComponentProperties.cs b/src/Components/Components/src/Reflection/ComponentProperties.cs index 3172a21f24b5..c5a49dfa3f80 100644 --- a/src/Components/Components/src/Reflection/ComponentProperties.cs +++ b/src/Components/Components/src/Reflection/ComponentProperties.cs @@ -14,7 +14,7 @@ internal static class ComponentProperties { static ComponentProperties() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } diff --git a/src/Components/Components/src/RenderHandle.cs b/src/Components/Components/src/RenderHandle.cs index 6f226c2a741e..f6840d8c554a 100644 --- a/src/Components/Components/src/RenderHandle.cs +++ b/src/Components/Components/src/RenderHandle.cs @@ -49,7 +49,7 @@ public Dispatcher Dispatcher /// /// Gets a value that determines if the is triggering a render in response to a metadata update (hot-reload) change. /// - public bool IsRenderingOnMetadataUpdate => HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled && (_renderer?.IsRenderingOnMetadataUpdate ?? false); + public bool IsRenderingOnMetadataUpdate => HotReloadManager.IsSupported && (_renderer?.IsRenderingOnMetadataUpdate ?? false); internal bool IsRendererDisposed => _renderer?.Disposed ?? throw new InvalidOperationException("No renderer has been initialized."); diff --git a/src/Components/Components/src/RenderTree/EventArgsTypeCache.cs b/src/Components/Components/src/RenderTree/EventArgsTypeCache.cs index 1c9cbd96c10c..82b6661b2f93 100644 --- a/src/Components/Components/src/RenderTree/EventArgsTypeCache.cs +++ b/src/Components/Components/src/RenderTree/EventArgsTypeCache.cs @@ -13,7 +13,7 @@ internal static class EventArgsTypeCache static EventArgsTypeCache() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += Cache.Clear; } diff --git a/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs b/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs index bcbb6362789d..35ae4707692b 100644 --- a/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs +++ b/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs @@ -684,7 +684,7 @@ private static void AppendDiffEntriesForFramesWithSameSequence( var oldParameters = new ParameterView(ParameterViewLifetime.Unbound, oldTree, oldFrameIndex); var newParametersLifetime = new ParameterViewLifetime(diffContext.BatchBuilder); var newParameters = new ParameterView(newParametersLifetime, newTree, newFrameIndex); - var isHotReload = HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled && diffContext.Renderer.IsRenderingOnMetadataUpdate; + var isHotReload = HotReloadManager.IsSupported && diffContext.Renderer.IsRenderingOnMetadataUpdate; if (isHotReload && newParameters.HasRemovedDirectParameters(oldParameters)) { diff --git a/src/Components/Components/src/RouteView.cs b/src/Components/Components/src/RouteView.cs index 1fa034d60d6b..ab40bf32f14b 100644 --- a/src/Components/Components/src/RouteView.cs +++ b/src/Components/Components/src/RouteView.cs @@ -22,7 +22,7 @@ public class RouteView : IComponent static RouteView() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += _layoutAttributeCache.Clear; } diff --git a/src/Components/Components/src/Routing/RouteTable.cs b/src/Components/Components/src/Routing/RouteTable.cs index 74c204626081..b868d145d9bf 100644 --- a/src/Components/Components/src/Routing/RouteTable.cs +++ b/src/Components/Components/src/Routing/RouteTable.cs @@ -16,7 +16,7 @@ internal sealed class RouteTable(TreeRouter treeRouter) static RouteTable() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += _routeEntryCache.Clear; } diff --git a/src/Components/Components/src/Routing/Router.cs b/src/Components/Components/src/Routing/Router.cs index 6641a49ee3ce..002a48fa69bf 100644 --- a/src/Components/Components/src/Routing/Router.cs +++ b/src/Components/Components/src/Routing/Router.cs @@ -111,7 +111,7 @@ public void Attach(RenderHandle renderHandle) NavigationManager.OnNotFound += OnNotFound; RoutingStateProvider = ServiceProvider.GetService(); - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += ClearRouteCaches; } @@ -179,7 +179,7 @@ public void Dispose() { NavigationManager.LocationChanged -= OnLocationChanged; NavigationManager.OnNotFound -= OnNotFound; - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied -= ClearRouteCaches; } diff --git a/src/Components/Components/test/RendererTest.cs b/src/Components/Components/test/RendererTest.cs index cff73e1e3c1d..ac8936bfda04 100644 --- a/src/Components/Components/test/RendererTest.cs +++ b/src/Components/Components/test/RendererTest.cs @@ -4987,9 +4987,9 @@ public async Task NoHotReloadListenersAreRegistered_WhenHotReloadIsDisabled() // Arrange try { + AppContext.SetSwitch("System.Reflection.Metadata.MetadataUpdater.IsSupported", false); await using var renderer = new TestRenderer(); var hotReloadManager = new HotReloadManager { IsEnabled = false }; - HotReloadManager.Default.IsEnabled = false; renderer.HotReloadManager = hotReloadManager; var component = new TestComponent(builder => { @@ -5007,8 +5007,7 @@ public async Task NoHotReloadListenersAreRegistered_WhenHotReloadIsDisabled() } finally { - // Ensure we don't affect other tests by leaving hot reload disabled - HotReloadManager.Default.IsEnabled = HotReloadManager.IsSupported; + AppContext.SetSwitch("System.Reflection.Metadata.MetadataUpdater.IsSupported", true); } } @@ -5016,9 +5015,9 @@ public async Task NoHotReloadListenersAreRegistered_WhenHotReloadIsDisabled() public async Task DisposingRenderer_UnsubsribesFromHotReloadManager() { // Arrange + AppContext.SetSwitch("System.Reflection.Metadata.MetadataUpdater.IsSupported", true); var renderer = new TestRenderer(); var hotReloadManager = new HotReloadManager { IsEnabled = true }; - HotReloadManager.Default.IsEnabled = true; renderer.HotReloadManager = hotReloadManager; var component = new TestComponent(builder => { @@ -5041,11 +5040,12 @@ public async Task DisposingRenderer_UnsubsribesFromHotReloadManager() [Fact] public async Task HotReload_ReRenderPreservesAsyncLocalValues() { + AppContext.SetSwitch("System.Reflection.Metadata.MetadataUpdater.IsSupported", true); + await using var renderer = new TestRenderer(); var hotReloadManager = new HotReloadManager { IsEnabled = true }; renderer.HotReloadManager = hotReloadManager; - HotReloadManager.Default.IsEnabled = true; var component = new AsyncLocalCaptureComponent(); diff --git a/src/Components/Endpoints/src/Microsoft.AspNetCore.Components.Endpoints.csproj b/src/Components/Endpoints/src/Microsoft.AspNetCore.Components.Endpoints.csproj index eb03be9786ae..5d60d14d148a 100644 --- a/src/Components/Endpoints/src/Microsoft.AspNetCore.Components.Endpoints.csproj +++ b/src/Components/Endpoints/src/Microsoft.AspNetCore.Components.Endpoints.csproj @@ -68,4 +68,8 @@ + + + + diff --git a/src/Components/Endpoints/src/Properties/ILLink.Substitutions.xml b/src/Components/Endpoints/src/Properties/ILLink.Substitutions.xml new file mode 100644 index 000000000000..367ba717e59d --- /dev/null +++ b/src/Components/Endpoints/src/Properties/ILLink.Substitutions.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Components/Endpoints/src/Rendering/EndpointComponentState.cs b/src/Components/Endpoints/src/Rendering/EndpointComponentState.cs index f63b2bd6d4f1..f76bdc40a236 100644 --- a/src/Components/Endpoints/src/Rendering/EndpointComponentState.cs +++ b/src/Components/Endpoints/src/Rendering/EndpointComponentState.cs @@ -19,7 +19,7 @@ internal sealed class EndpointComponentState : ComponentState static EndpointComponentState() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += _streamRenderingAttributeByComponentType.Clear; } diff --git a/src/Components/Forms/src/FieldIdentifier.cs b/src/Components/Forms/src/FieldIdentifier.cs index f7079f402269..bd66ae25ae43 100644 --- a/src/Components/Forms/src/FieldIdentifier.cs +++ b/src/Components/Forms/src/FieldIdentifier.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Components.Forms; static FieldIdentifier() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } diff --git a/src/Components/Forms/src/Microsoft.AspNetCore.Components.Forms.csproj b/src/Components/Forms/src/Microsoft.AspNetCore.Components.Forms.csproj index c92fe86008f3..5f068882fb38 100644 --- a/src/Components/Forms/src/Microsoft.AspNetCore.Components.Forms.csproj +++ b/src/Components/Forms/src/Microsoft.AspNetCore.Components.Forms.csproj @@ -23,4 +23,8 @@ + + + + diff --git a/src/Components/Forms/src/Properties/ILLink.Substitutions.xml b/src/Components/Forms/src/Properties/ILLink.Substitutions.xml new file mode 100644 index 000000000000..367ba717e59d --- /dev/null +++ b/src/Components/Forms/src/Properties/ILLink.Substitutions.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj b/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj index 3efe5346b208..72809b5d3b3c 100644 --- a/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj +++ b/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj @@ -114,4 +114,8 @@ + + + + diff --git a/src/Components/Server/src/Properties/ILLink.Substitutions.xml b/src/Components/Server/src/Properties/ILLink.Substitutions.xml new file mode 100644 index 000000000000..ec8951251310 --- /dev/null +++ b/src/Components/Server/src/Properties/ILLink.Substitutions.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Components/Shared/src/ExpressionFormatting/ExpressionFormatter.cs b/src/Components/Shared/src/ExpressionFormatting/ExpressionFormatter.cs index 6308f7944572..8682810448fe 100644 --- a/src/Components/Shared/src/ExpressionFormatting/ExpressionFormatter.cs +++ b/src/Components/Shared/src/ExpressionFormatting/ExpressionFormatter.cs @@ -14,7 +14,7 @@ internal static class ExpressionFormatter { static ExpressionFormatter() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } diff --git a/src/Components/Shared/src/HotReloadManager.cs b/src/Components/Shared/src/HotReloadManager.cs index 7a59013621c5..ac41cb6ebf80 100644 --- a/src/Components/Shared/src/HotReloadManager.cs +++ b/src/Components/Shared/src/HotReloadManager.cs @@ -13,10 +13,8 @@ internal sealed class HotReloadManager { public static readonly HotReloadManager Default = new(); - public bool IsEnabled { get; set; } = IsSupported; - [FeatureSwitchDefinition("System.Reflection.Metadata.MetadataUpdater.IsSupported")] - internal static bool IsSupported { get; } = + internal static bool IsSupported { get; } => AppContext.TryGetSwitch("System.Reflection.Metadata.MetadataUpdater.IsSupported", out bool isSupported) ? isSupported : true; /// diff --git a/src/Components/Shared/src/RootTypeCache.cs b/src/Components/Shared/src/RootTypeCache.cs index 3ec213d8b6ae..e9ef1bad55ca 100644 --- a/src/Components/Shared/src/RootTypeCache.cs +++ b/src/Components/Shared/src/RootTypeCache.cs @@ -19,7 +19,7 @@ internal sealed class RootTypeCache : IDisposable public RootTypeCache() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } @@ -29,7 +29,7 @@ public RootTypeCache() public void Dispose() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied -= ClearCache; } diff --git a/src/Components/Web/src/Forms/ExpressionMemberAccessor.cs b/src/Components/Web/src/Forms/ExpressionMemberAccessor.cs index acee30f11aee..f6b971081225 100644 --- a/src/Components/Web/src/Forms/ExpressionMemberAccessor.cs +++ b/src/Components/Web/src/Forms/ExpressionMemberAccessor.cs @@ -17,7 +17,7 @@ internal static class ExpressionMemberAccessor static ExpressionMemberAccessor() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } diff --git a/src/Components/Web/src/JSComponents/JSComponentInterop.cs b/src/Components/Web/src/JSComponents/JSComponentInterop.cs index 7a7fe85a7c74..8fdf4785152a 100644 --- a/src/Components/Web/src/JSComponents/JSComponentInterop.cs +++ b/src/Components/Web/src/JSComponents/JSComponentInterop.cs @@ -27,7 +27,7 @@ public class JSComponentInterop static JSComponentInterop() { - if (HotReloadManager.IsSupported && HotReloadManager.Default.IsEnabled) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += ParameterTypeCaches.Clear; } diff --git a/src/Components/WebAssembly/WebAssembly/src/Properties/ILLink.Substitutions.xml b/src/Components/WebAssembly/WebAssembly/src/Properties/ILLink.Substitutions.xml index 867efb0dfbd9..ec4d41761279 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Properties/ILLink.Substitutions.xml +++ b/src/Components/WebAssembly/WebAssembly/src/Properties/ILLink.Substitutions.xml @@ -1,5 +1,8 @@ + + + diff --git a/src/Components/test/testassets/BasicTestApp/HotReloadTrimmingCheck.razor b/src/Components/test/testassets/BasicTestApp/HotReloadTrimmingCheck.razor index 6f6ed50573cb..f46fcc902f5f 100644 --- a/src/Components/test/testassets/BasicTestApp/HotReloadTrimmingCheck.razor +++ b/src/Components/test/testassets/BasicTestApp/HotReloadTrimmingCheck.razor @@ -15,23 +15,27 @@ string HotReloadManagerFound { get; set; } = "unknown"; string UpdateApplicationFound { get; set; } = "unknown"; + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", + Justification = "This is a test component that intentionally uses reflection to check trimming behavior.")] protected override void OnInitialized() { - var hotReloadManagerType = GetHotReloadManagerType(); - HotReloadManagerFound = hotReloadManagerType is not null ? "true" : "false"; + HotReloadManagerFound = "false"; + UpdateApplicationFound = "false"; - if (hotReloadManagerType is not null) + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { - var method = hotReloadManagerType.GetMethod("UpdateApplication", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); - UpdateApplicationFound = method is not null ? "true" : "false"; + var hotReloadManagerType = assembly.GetType("Microsoft.AspNetCore.Components.HotReload.HotReloadManager"); + if (hotReloadManagerType is not null) + { + HotReloadManagerFound = "true"; + var method = hotReloadManagerType.GetMethod("UpdateApplication", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + if (method is not null) + { + UpdateApplicationFound = "true"; + } + break; + } } } - - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2057:UnrecognizedReflectionPattern", - Justification = "Returning null when the type is trimmed is the expected and desired behavior.")] - private static Type? GetHotReloadManagerType() - { - return Type.GetType("Microsoft.AspNetCore.Components.HotReload.HotReloadManager, Microsoft.AspNetCore.Components"); - } #nullable restore } \ No newline at end of file diff --git a/src/Components/test/testassets/Components.TestServer/HotReloadStartup.cs b/src/Components/test/testassets/Components.TestServer/HotReloadStartup.cs index ea2d007d5bec..219c242ef3f8 100644 --- a/src/Components/test/testassets/Components.TestServer/HotReloadStartup.cs +++ b/src/Components/test/testassets/Components.TestServer/HotReloadStartup.cs @@ -12,7 +12,7 @@ public class HotReloadStartup { public HotReloadStartup() { - HotReloadManager.Default.IsEnabled = true; + AppContext.SetSwitch("System.Reflection.Metadata.MetadataUpdater.IsSupported", true); } public void ConfigureServices(IServiceCollection services) From 0c2a37e355d10285980750c097fa316d27a33287 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 1 Apr 2026 15:31:42 +0200 Subject: [PATCH 6/6] fix --- src/Components/Components/src/RenderTree/Renderer.cs | 8 ++++---- src/Components/Components/test/RendererTest.cs | 6 +++--- src/Components/Shared/src/HotReloadManager.cs | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index 213d1f6fed2e..ba6f72512ae2 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -243,7 +243,7 @@ protected internal int AssignRootComponentId(IComponent component) if (!_hotReloadInitialized) { _hotReloadInitialized = true; - if (HotReload.HotReloadManager.IsSupported && HotReloadManager != null && HotReloadManager.IsEnabled) + if (HotReload.HotReloadManager.IsSupported && HotReloadManager != null) { // Capture the current ExecutionContext so AsyncLocal values present during initial root component // registration flow through to hot reload re-renders. Without this, hot reload callbacks execute @@ -308,7 +308,7 @@ protected internal async Task RenderRootComponentAsync(int componentId, Paramete _pendingTasks ??= new(); var componentState = GetRequiredRootComponentState(componentId); - if (HotReload.HotReloadManager.IsSupported && HotReloadManager != null && HotReloadManager.IsEnabled) + if (HotReload.HotReloadManager.IsSupported && HotReloadManager != null) { // When we're doing hot-reload, stash away the parameters used while rendering root components. // We'll use this to trigger re-renders on hot reload updates. @@ -338,7 +338,7 @@ protected internal void RemoveRootComponent(int componentId) // Currently there's no known scenario where we need to support calling RemoveRootComponentAsync // during a batch, but if a scenario emerges we can add support. _batchBuilder.ComponentDisposalQueue.Enqueue(componentId); - if (HotReload.HotReloadManager.IsSupported && HotReloadManager != null && HotReloadManager.IsEnabled) + if (HotReload.HotReloadManager.IsSupported && HotReloadManager != null) { _rootComponentsLatestParameters?.Remove(componentId); } @@ -1253,7 +1253,7 @@ protected virtual void Dispose(bool disposing) _rendererIsDisposed = true; } - if (HotReload.HotReloadManager.IsSupported && HotReloadManager != null && HotReloadManager.IsEnabled && _hotReloadInitialized && _hotReloadRenderHandler is not null) + if (HotReload.HotReloadManager.IsSupported && HotReloadManager != null && _hotReloadInitialized && _hotReloadRenderHandler is not null) { HotReloadManager.OnDeltaApplied -= _hotReloadRenderHandler.RerenderOnHotReload; } diff --git a/src/Components/Components/test/RendererTest.cs b/src/Components/Components/test/RendererTest.cs index ac8936bfda04..b672dcaf867a 100644 --- a/src/Components/Components/test/RendererTest.cs +++ b/src/Components/Components/test/RendererTest.cs @@ -4989,7 +4989,7 @@ public async Task NoHotReloadListenersAreRegistered_WhenHotReloadIsDisabled() { AppContext.SetSwitch("System.Reflection.Metadata.MetadataUpdater.IsSupported", false); await using var renderer = new TestRenderer(); - var hotReloadManager = new HotReloadManager { IsEnabled = false }; + var hotReloadManager = new HotReloadManager(); renderer.HotReloadManager = hotReloadManager; var component = new TestComponent(builder => { @@ -5017,7 +5017,7 @@ public async Task DisposingRenderer_UnsubsribesFromHotReloadManager() // Arrange AppContext.SetSwitch("System.Reflection.Metadata.MetadataUpdater.IsSupported", true); var renderer = new TestRenderer(); - var hotReloadManager = new HotReloadManager { IsEnabled = true }; + var hotReloadManager = new HotReloadManager(); renderer.HotReloadManager = hotReloadManager; var component = new TestComponent(builder => { @@ -5044,7 +5044,7 @@ public async Task HotReload_ReRenderPreservesAsyncLocalValues() await using var renderer = new TestRenderer(); - var hotReloadManager = new HotReloadManager { IsEnabled = true }; + var hotReloadManager = new HotReloadManager(); renderer.HotReloadManager = hotReloadManager; var component = new AsyncLocalCaptureComponent(); diff --git a/src/Components/Shared/src/HotReloadManager.cs b/src/Components/Shared/src/HotReloadManager.cs index ac41cb6ebf80..ab01a8f04f39 100644 --- a/src/Components/Shared/src/HotReloadManager.cs +++ b/src/Components/Shared/src/HotReloadManager.cs @@ -14,7 +14,7 @@ internal sealed class HotReloadManager public static readonly HotReloadManager Default = new(); [FeatureSwitchDefinition("System.Reflection.Metadata.MetadataUpdater.IsSupported")] - internal static bool IsSupported { get; } => + internal static bool IsSupported => AppContext.TryGetSwitch("System.Reflection.Metadata.MetadataUpdater.IsSupported", out bool isSupported) ? isSupported : true; ///