diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj index 490ee213ed6b..9a75de9cca0d 100644 --- a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj +++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Components/Components/src/Properties/ILLink.Substitutions.xml b/src/Components/Components/src/Properties/ILLink.Substitutions.xml index 724381711cb7..90abb328b06f 100644 --- a/src/Components/Components/src/Properties/ILLink.Substitutions.xml +++ b/src/Components/Components/src/Properties/ILLink.Substitutions.xml @@ -1,17 +1,11 @@ - - - - - - - - - - + + + + diff --git a/src/Components/Components/src/RenderHandle.cs b/src/Components/Components/src/RenderHandle.cs index 3b6cf7de5290..d7789e18cec8 100644 --- a/src/Components/Components/src/RenderHandle.cs +++ b/src/Components/Components/src/RenderHandle.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Components.HotReload; using Microsoft.AspNetCore.Components.RenderTree; namespace Microsoft.AspNetCore.Components @@ -46,7 +47,7 @@ public Dispatcher Dispatcher /// /// Gets a value that determines if the is triggering a render in response to a hot-reload change. /// - public bool IsHotReloading => _renderer?.IsHotReloading ?? false; + public bool IsHotReloading => HotReloadFeature.IsSupported && (_renderer?.IsHotReloading ?? false); /// /// Notifies the renderer that the component should be rendered. diff --git a/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs b/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs index 5669fdad80fb..6ba718884410 100644 --- a/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs +++ b/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using Microsoft.AspNetCore.Components.HotReload; using Microsoft.AspNetCore.Components.Rendering; namespace Microsoft.AspNetCore.Components.RenderTree @@ -541,7 +542,7 @@ private static void UpdateRetainedChildComponent( var oldParameters = new ParameterView(ParameterViewLifetime.Unbound, oldTree, oldComponentIndex); var newParametersLifetime = new ParameterViewLifetime(diffContext.BatchBuilder); var newParameters = new ParameterView(newParametersLifetime, newTree, newComponentIndex); - if (!newParameters.DefinitelyEquals(oldParameters) || diffContext.Renderer.IsHotReloading) + if (!newParameters.DefinitelyEquals(oldParameters) || (HotReloadFeature.IsSupported && diffContext.Renderer.IsHotReloading)) { componentState.SetDirectParameters(newParameters); } diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index 06e1724b7b5c..b059b1998f20 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.HotReload; @@ -35,10 +34,9 @@ public abstract partial class Renderer : IDisposable, IAsyncDisposable private readonly Dictionary _eventHandlerIdReplacements = new Dictionary(); private readonly ILogger _logger; private readonly ComponentFactory _componentFactory; - private HotReloadEnvironment? _hotReloadEnvironment; private List<(ComponentState, ParameterView)>? _rootComponents; - private int _nextComponentId = 0; // TODO: change to 'long' when Mono .NET->JS interop supports it + private int _nextComponentId = 0; private bool _isBatchInProgress; private ulong _lastEventHandlerId; private List? _pendingTasks; @@ -98,16 +96,7 @@ public Renderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, _logger = loggerFactory.CreateLogger(); _componentFactory = new ComponentFactory(componentActivator); - InitializeHotReload(serviceProvider); - } - - private void InitializeHotReload(IServiceProvider serviceProvider) - { - // HotReloadEnvironment is a test-specific feature and may not be available in a running app. We'll fallback to the default instance - // if the test fixture does not provide one. - _hotReloadEnvironment = serviceProvider.GetService() ?? HotReloadEnvironment.Instance; - - if (_hotReloadEnvironment.IsHotReloadEnabled) + if (HotReloadFeature.IsSupported) { HotReloadManager.OnDeltaApplied += RenderRootComponentsOnHotReload; } @@ -232,7 +221,13 @@ protected async Task RenderRootComponentAsync(int componentId, ParameterView ini // During the asynchronous rendering process we want to wait up until all components have // finished rendering so that we can produce the complete output. var componentState = GetRequiredComponentState(componentId); - CaptureRootComponentForHotReload(initialParameters, componentState); + if (HotReloadFeature.IsSupported) + { + // 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. + _rootComponents ??= new(); + _rootComponents.Add((componentState, initialParameters.Clone())); + } componentState.SetDirectParameters(initialParameters); @@ -247,20 +242,6 @@ protected async Task RenderRootComponentAsync(int componentId, ParameterView ini } } - /// - /// Intentionally authored as a separate method call so we can trim this code. - /// - private void CaptureRootComponentForHotReload(ParameterView initialParameters, ComponentState componentState) - { - if (_hotReloadEnvironment?.IsHotReloadEnabled ?? false) - { - // 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. - _rootComponents ??= new(); - _rootComponents.Add((componentState, initialParameters.Clone())); - } - } - /// /// Allows derived types to handle exceptions during rendering. Defaults to rethrowing the original exception. /// @@ -1011,7 +992,10 @@ public void Dispose() /// public async ValueTask DisposeAsync() { - DisposeForHotReload(); + if (HotReloadFeature.IsSupported) + { + HotReloadManager.OnDeltaApplied -= RenderRootComponentsOnHotReload; + } if (_disposed) { @@ -1035,16 +1019,5 @@ public async ValueTask DisposeAsync() } } } - - /// - /// Intentionally authored as a separate method call so we can trim this code. - /// - private void DisposeForHotReload() - { - if (_hotReloadEnvironment?.IsHotReloadEnabled ?? false) - { - HotReloadManager.OnDeltaApplied -= RenderRootComponentsOnHotReload; - } - } } } diff --git a/src/Components/Shared/src/HotReloadEnvironment.cs b/src/Components/Shared/src/HotReloadEnvironment.cs deleted file mode 100644 index fef2cd9255cd..000000000000 --- a/src/Components/Shared/src/HotReloadEnvironment.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.AspNetCore.Components.HotReload -{ - internal class HotReloadEnvironment - { - public static readonly HotReloadEnvironment Instance = new(Environment.GetEnvironmentVariable("DOTNET_MODIFIABLE_ASSEMBLIES") == "debug"); - - public HotReloadEnvironment(bool isHotReloadEnabled) - { - IsHotReloadEnabled = isHotReloadEnabled; - } - - /// - /// Gets a value that determines if HotReload is configured for this application. - /// - public bool IsHotReloadEnabled { get; } - } -} diff --git a/src/Components/Shared/src/HotReloadFeature.cs b/src/Components/Shared/src/HotReloadFeature.cs new file mode 100644 index 000000000000..7a5ef6baa6b2 --- /dev/null +++ b/src/Components/Shared/src/HotReloadFeature.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Components.HotReload +{ + internal static class HotReloadFeature + { + /// + /// Gets a value that determines if hot reload is supported. Currently, the Debugger.IsSupported feature switch is used as a proxy for this. + /// + public static bool IsSupported { get; } = AppContext.TryGetSwitch("System.Diagnostics.Debugger.IsSupported", out var isSupported) ? isSupported : true; + } +} diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHost.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHost.cs index a2c7f8c0777c..70bfa2456cfd 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHost.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHost.cs @@ -4,6 +4,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.HotReload; using Microsoft.AspNetCore.Components.Lifetime; using Microsoft.AspNetCore.Components.WebAssembly.HotReload; using Microsoft.AspNetCore.Components.WebAssembly.Infrastructure; @@ -144,11 +145,9 @@ internal async Task RunAsyncCore(CancellationToken cancellationToken, WebAssembl await manager.RestoreStateAsync(store); - var initializeTask = InitializeHotReloadAsync(); - if (initializeTask is not null) + if (HotReloadFeature.IsSupported) { - // The returned value will be "null" in a trimmed app - await initializeTask; + await WebAssemblyHotReload.InitializeAsync(); } var tcs = new TaskCompletionSource(); @@ -184,11 +183,5 @@ internal async Task RunAsyncCore(CancellationToken cancellationToken, WebAssembl await tcs.Task; } } - - private Task? InitializeHotReloadAsync() - { - // In Development scenarios, wait for hot reload to apply deltas before initiating rendering. - return WebAssemblyHotReload.InitializeAsync(); - } } } diff --git a/src/Components/WebAssembly/WebAssembly/src/HotReload/WebAssemblyHotReload.cs b/src/Components/WebAssembly/WebAssembly/src/HotReload/WebAssemblyHotReload.cs index ba672d4c8d3d..c79182917fbc 100644 --- a/src/Components/WebAssembly/WebAssembly/src/HotReload/WebAssemblyHotReload.cs +++ b/src/Components/WebAssembly/WebAssembly/src/HotReload/WebAssemblyHotReload.cs @@ -5,7 +5,6 @@ using System.ComponentModel; using System.Diagnostics; using System.Threading.Tasks; -using Microsoft.AspNetCore.Components.HotReload; using Microsoft.AspNetCore.Components.WebAssembly.Services; using Microsoft.Extensions.HotReload; using Microsoft.JSInterop; @@ -27,7 +26,10 @@ public static class WebAssemblyHotReload internal static async Task InitializeAsync() { - if (!HotReloadEnvironment.Instance.IsHotReloadEnabled) + // Determine if we're running under a hot reload environment (e.g. dotnet-watch). + // It's insufficient to know it the app can be hot reloaded (HotReloadEnvironment.IsEnabled), + // since the hot-reload agent might be unavilable. + if (Environment.GetEnvironmentVariable("DOTNET_MODIFIABLE_ASSEMBLIES") != "debug") { return; } @@ -45,7 +47,6 @@ internal static async Task InitializeAsync() public static void ApplyHotReloadDelta(string moduleIdString, byte[] metadataDelta, byte[] ilDeta) { var moduleId = Guid.Parse(moduleIdString); - Debug.Assert(HotReloadEnvironment.Instance.IsHotReloadEnabled); _updateDeltas[0].ModuleId = moduleId; _updateDeltas[0].MetadataDelta = metadataDelta; diff --git a/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj b/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj index 46238a9a4c0d..f80c2e225fa5 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj +++ b/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) @@ -32,7 +32,7 @@ - + diff --git a/src/Components/WebAssembly/WebAssembly/src/Properties/ILLink.Substitutions.xml b/src/Components/WebAssembly/WebAssembly/src/Properties/ILLink.Substitutions.xml index 95ac01acc9ac..2b8260e080d5 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Properties/ILLink.Substitutions.xml +++ b/src/Components/WebAssembly/WebAssembly/src/Properties/ILLink.Substitutions.xml @@ -1,8 +1,7 @@ - - - + + diff --git a/src/Components/test/testassets/TestServer/HotReloadStartup.cs b/src/Components/test/testassets/TestServer/HotReloadStartup.cs index f9b0061c9d41..aeb99355c4f1 100644 --- a/src/Components/test/testassets/TestServer/HotReloadStartup.cs +++ b/src/Components/test/testassets/TestServer/HotReloadStartup.cs @@ -3,7 +3,6 @@ using System.Globalization; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Components.HotReload; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -14,7 +13,6 @@ public class HotReloadStartup { public void ConfigureServices(IServiceCollection services) { - services.AddSingleton(new HotReloadEnvironment(isHotReloadEnabled: true)); services.AddControllers(); services.AddRazorPages(); services.AddServerSideBlazor();