diff --git a/src/Components/Authorization/src/AttributeAuthorizeDataCache.cs b/src/Components/Authorization/src/AttributeAuthorizeDataCache.cs index 6f7f4bd06539..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.Default.MetadataUpdateSupported) + 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 a3e68de026a5..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.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += _cache.Clear; } @@ -1867,7 +1867,7 @@ internal static class ParserDelegateCache static ParserDelegateCache() { - if (HotReloadManager.Default.MetadataUpdateSupported) + 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 952debeb8f90..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.Default.MetadataUpdateSupported) + 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 a4b5aaa3224e..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.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } diff --git a/src/Components/Components/src/DefaultComponentActivator.cs b/src/Components/Components/src/DefaultComponentActivator.cs index 24b92dcb1056..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.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } diff --git a/src/Components/Components/src/DefaultComponentPropertyActivator.cs b/src/Components/Components/src/DefaultComponentPropertyActivator.cs index 3fa29d33903c..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.Default.MetadataUpdateSupported) + 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 e27e5c2560b0..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.Default.MetadataUpdateSupported) + 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 8de18363342c..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.Default.MetadataUpdateSupported) + 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 35ad18735816..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.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported) { 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 6f45322d42d0..d069a5a3aa7f 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..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.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } diff --git a/src/Components/Components/src/RenderHandle.cs b/src/Components/Components/src/RenderHandle.cs index edcb644bddfb..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.Default.MetadataUpdateSupported && (_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 5b9abafb0bed..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.Default.MetadataUpdateSupported) + 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 e61fb6d2d932..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.Default.MetadataUpdateSupported && diffContext.Renderer.IsRenderingOnMetadataUpdate; + var isHotReload = HotReloadManager.IsSupported && 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 0f5f7f23a24a..ba6f72512ae2 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -120,7 +120,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) { @@ -243,7 +243,7 @@ protected internal int AssignRootComponentId(IComponent component) if (!_hotReloadInitialized) { _hotReloadInitialized = true; - if (HotReloadManager.MetadataUpdateSupported) + 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 (HotReloadManager.MetadataUpdateSupported) + 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 (HotReloadManager.MetadataUpdateSupported) + if (HotReload.HotReloadManager.IsSupported && HotReloadManager != null) { _rootComponentsLatestParameters?.Remove(componentId); } @@ -1253,7 +1253,7 @@ protected virtual void Dispose(bool disposing) _rendererIsDisposed = true; } - if (_hotReloadInitialized && HotReloadManager.MetadataUpdateSupported && _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/src/RouteView.cs b/src/Components/Components/src/RouteView.cs index 36560a2aa342..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.Default.MetadataUpdateSupported) + 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 f56beec826b9..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.Default.MetadataUpdateSupported) + 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 c52a818767e3..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.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported) { 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.OnDeltaApplied -= ClearRouteCaches; } diff --git a/src/Components/Components/test/Microsoft.AspNetCore.Components.Tests.csproj b/src/Components/Components/test/Microsoft.AspNetCore.Components.Tests.csproj index 012c36a3c274..25a12fa2bdb9 100644 --- a/src/Components/Components/test/Microsoft.AspNetCore.Components.Tests.csproj +++ b/src/Components/Components/test/Microsoft.AspNetCore.Components.Tests.csproj @@ -3,6 +3,8 @@ $(DefaultNetCoreTargetFramework) Microsoft.AspNetCore.Components + + true diff --git a/src/Components/Components/test/RendererTest.cs b/src/Components/Components/test/RendererTest.cs index 56fc26840edd..b672dcaf867a 100644 --- a/src/Components/Components/test/RendererTest.cs +++ b/src/Components/Components/test/RendererTest.cs @@ -4982,33 +4982,42 @@ 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 }; - renderer.HotReloadManager = hotReloadManager; - var component = new TestComponent(builder => + try { - builder.OpenElement(0, "h2"); - builder.AddContent(1, "some text"); - builder.CloseElement(); - }); + AppContext.SetSwitch("System.Reflection.Metadata.MetadataUpdater.IsSupported", false); + await using var renderer = new TestRenderer(); + var hotReloadManager = new HotReloadManager(); + 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 + { + AppContext.SetSwitch("System.Reflection.Metadata.MetadataUpdater.IsSupported", true); + } } [Fact] public async Task DisposingRenderer_UnsubsribesFromHotReloadManager() { // Arrange + AppContext.SetSwitch("System.Reflection.Metadata.MetadataUpdater.IsSupported", true); var renderer = new TestRenderer(); - var hotReloadManager = new HotReloadManager { MetadataUpdateSupported = true }; + var hotReloadManager = new HotReloadManager(); renderer.HotReloadManager = hotReloadManager; var component = new TestComponent(builder => { @@ -5031,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 { MetadataUpdateSupported = true }; + var hotReloadManager = new HotReloadManager(); renderer.HotReloadManager = hotReloadManager; - HotReloadManager.Default.MetadataUpdateSupported = true; var component = new AsyncLocalCaptureComponent(); @@ -5223,7 +5233,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 +5254,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 +5275,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 +6342,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/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 6c03d0585f2f..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.Default.MetadataUpdateSupported) + 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 5764b018e4ce..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.Default.MetadataUpdateSupported) + 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 fa2febd3754e..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.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported) { 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..ab01a8f04f39 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,9 @@ internal sealed class HotReloadManager { public static readonly HotReloadManager Default = new(); - public bool MetadataUpdateSupported { get; set; } = MetadataUpdater.IsSupported; + [FeatureSwitchDefinition("System.Reflection.Metadata.MetadataUpdater.IsSupported")] + internal static bool IsSupported => + 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..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.Default.MetadataUpdateSupported) + if (HotReloadManager.IsSupported) { HotReloadManager.Default.OnDeltaApplied += ClearCache; } @@ -29,7 +29,7 @@ public RootTypeCache() public void Dispose() { - if (HotReloadManager.Default.MetadataUpdateSupported) + 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 e1a81d4f0062..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.Default.MetadataUpdateSupported) + 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 9df1c3288f71..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.Default.MetadataUpdateSupported) + 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/E2ETest/Infrastructure/ServerFixtures/BasicTestAppServerSiteFixture.cs b/src/Components/test/E2ETest/Infrastructure/ServerFixtures/BasicTestAppServerSiteFixture.cs index 20bb845423b8..61a5e6790d40 100644 --- a/src/Components/test/E2ETest/Infrastructure/ServerFixtures/BasicTestAppServerSiteFixture.cs +++ b/src/Components/test/E2ETest/Infrastructure/ServerFixtures/BasicTestAppServerSiteFixture.cs @@ -1,10 +1,17 @@ // 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; + namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; public class BasicTestAppServerSiteFixture : AspNetSiteServerFixture where TStartup : class { + public readonly bool TestTrimmedOrMultithreadingApps = typeof(BasicTestAppServerSiteFixture<>).Assembly + .GetCustomAttributes() + .First(m => m.Key == "Microsoft.AspNetCore.E2ETesting.TestTrimmedOrMultithreadingApps") + .Value == "true"; + public BasicTestAppServerSiteFixture() { ApplicationAssembly = typeof(TStartup).Assembly; 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/E2ETest/Tests/WebAssemblyTrimmingTest.cs b/src/Components/test/E2ETest/Tests/WebAssemblyTrimmingTest.cs index 49587e420985..cf11b68b1790 100644 --- a/src/Components/test/E2ETest/Tests/WebAssemblyTrimmingTest.cs +++ b/src/Components/test/E2ETest/Tests/WebAssemblyTrimmingTest.cs @@ -27,6 +27,24 @@ protected override void 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); + } + [Fact] public void MetricsTypesAreTrimmed_WhenPublishedWithTrimming() { 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 true diff --git a/src/Components/test/testassets/Components.TestServer/HotReloadStartup.cs b/src/Components/test/testassets/Components.TestServer/HotReloadStartup.cs index 51269338ae33..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.MetadataUpdateSupported = true; + AppContext.SetSwitch("System.Reflection.Metadata.MetadataUpdater.IsSupported", true); } public void ConfigureServices(IServiceCollection services)