From 9f7175f88ba5bb52f9561e990922d3b413c86140 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 Aug 2025 21:10:18 +0000 Subject: [PATCH 01/10] Initial plan From 941dcd7443b40dc7e2a646a55c85d019fb755630 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 Aug 2025 21:28:39 +0000 Subject: [PATCH 02/10] Implement hot reload cache clearing for RootTypeCache Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- .../Components/test/RootTypeCacheTest.cs | 66 +++++++++++++++++++ src/Components/Shared/src/RootTypeCache.cs | 39 +++++++++++ 2 files changed, 105 insertions(+) create mode 100644 src/Components/Components/test/RootTypeCacheTest.cs diff --git a/src/Components/Components/test/RootTypeCacheTest.cs b/src/Components/Components/test/RootTypeCacheTest.cs new file mode 100644 index 000000000000..c90b5023369d --- /dev/null +++ b/src/Components/Components/test/RootTypeCacheTest.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Components.HotReload; +using Microsoft.AspNetCore.Components.Infrastructure; +using Xunit; + +namespace Microsoft.AspNetCore.Components.Tests; + +public class RootTypeCacheTest +{ + [Fact] + public void HotReload_ClearsCache() + { + // Arrange + var cache1 = new RootTypeCache(); + var cache2 = new RootTypeCache(); + + // Populate caches by attempting to resolve types + var result1Before = cache1.GetRootType("NonExistentAssembly", "NonExistentType"); + var result2Before = cache2.GetRootType("AnotherAssembly", "AnotherType"); + + // Verify initial state (should be null for non-existent types) + Assert.Null(result1Before); + Assert.Null(result2Before); + + // Act - Trigger hot reload cache clearing + HotReloadManager.UpdateApplication(null); + + // Assert - Verify cache is cleared by checking that subsequent lookups still work + // (This verifies the cache clearing didn't break functionality) + var result1After = cache1.GetRootType("NonExistentAssembly", "NonExistentType"); + var result2After = cache2.GetRootType("AnotherAssembly", "AnotherType"); + + Assert.Null(result1After); + Assert.Null(result2After); + } + + [Fact] + public void GetRootType_ReturnsNullForNonExistentType() + { + // Arrange + var cache = new RootTypeCache(); + + // Act + var result = cache.GetRootType("NonExistentAssembly", "NonExistentType"); + + // Assert + Assert.Null(result); + } + + [Fact] + public void GetRootType_ReturnsTypeForExistingType() + { + // Arrange + var cache = new RootTypeCache(); + var currentAssembly = typeof(RootTypeCache).Assembly.GetName().Name; + var existingTypeName = typeof(RootTypeCache).FullName; + + // Act + var result = cache.GetRootType(currentAssembly!, existingTypeName!); + + // Assert + Assert.Equal(typeof(RootTypeCache), result); + } +} \ No newline at end of file diff --git a/src/Components/Shared/src/RootTypeCache.cs b/src/Components/Shared/src/RootTypeCache.cs index 9c41b8cd1533..3ec25c9cd4d5 100644 --- a/src/Components/Shared/src/RootTypeCache.cs +++ b/src/Components/Shared/src/RootTypeCache.cs @@ -4,6 +4,7 @@ using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Reflection; +using Microsoft.AspNetCore.Components.HotReload; #if COMPONENTS namespace Microsoft.AspNetCore.Components.Infrastructure; @@ -14,8 +15,46 @@ namespace Microsoft.AspNetCore.Components; // A cache for root component types internal sealed class RootTypeCache { + private static readonly List _instances = new(); + + static RootTypeCache() + { + if (HotReloadManager.Default.MetadataUpdateSupported) + { + HotReloadManager.Default.OnDeltaApplied += ClearCache; + } + } + private readonly ConcurrentDictionary _typeToKeyLookUp = new(); + public RootTypeCache() + { + lock (_instances) + { + _instances.Add(new WeakReference(this)); + } + } + + private static void ClearCache() + { + lock (_instances) + { + for (int i = _instances.Count - 1; i >= 0; i--) + { + var weakRef = _instances[i]; + if (weakRef.Target is RootTypeCache instance) + { + instance._typeToKeyLookUp.Clear(); + } + else + { + // Remove dead reference + _instances.RemoveAt(i); + } + } + } + } + public Type? GetRootType(string assembly, string type) { var key = new Key(assembly, type); From af5307b71484e83e5f792b0793034227041b484b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 Aug 2025 21:33:02 +0000 Subject: [PATCH 03/10] Fix conditional compilation for RootTypeCache hot reload support Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- src/Components/Shared/src/RootTypeCache.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Components/Shared/src/RootTypeCache.cs b/src/Components/Shared/src/RootTypeCache.cs index 3ec25c9cd4d5..4c1cf4a27323 100644 --- a/src/Components/Shared/src/RootTypeCache.cs +++ b/src/Components/Shared/src/RootTypeCache.cs @@ -4,7 +4,9 @@ using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Reflection; +#if COMPONENTS using Microsoft.AspNetCore.Components.HotReload; +#endif #if COMPONENTS namespace Microsoft.AspNetCore.Components.Infrastructure; @@ -15,6 +17,7 @@ namespace Microsoft.AspNetCore.Components; // A cache for root component types internal sealed class RootTypeCache { +#if COMPONENTS private static readonly List _instances = new(); static RootTypeCache() @@ -24,17 +27,21 @@ static RootTypeCache() HotReloadManager.Default.OnDeltaApplied += ClearCache; } } +#endif private readonly ConcurrentDictionary _typeToKeyLookUp = new(); public RootTypeCache() { +#if COMPONENTS lock (_instances) { _instances.Add(new WeakReference(this)); } +#endif } +#if COMPONENTS private static void ClearCache() { lock (_instances) @@ -54,6 +61,7 @@ private static void ClearCache() } } } +#endif public Type? GetRootType(string assembly, string type) { From 62e8bab9d356f734a99a62e8bdcd5dfd233d3386 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 Aug 2025 21:43:17 +0000 Subject: [PATCH 04/10] Remove RootTypeCacheTest.cs as requested Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- .../Components/test/RootTypeCacheTest.cs | 66 ------------------- 1 file changed, 66 deletions(-) delete mode 100644 src/Components/Components/test/RootTypeCacheTest.cs diff --git a/src/Components/Components/test/RootTypeCacheTest.cs b/src/Components/Components/test/RootTypeCacheTest.cs deleted file mode 100644 index c90b5023369d..000000000000 --- a/src/Components/Components/test/RootTypeCacheTest.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.AspNetCore.Components.HotReload; -using Microsoft.AspNetCore.Components.Infrastructure; -using Xunit; - -namespace Microsoft.AspNetCore.Components.Tests; - -public class RootTypeCacheTest -{ - [Fact] - public void HotReload_ClearsCache() - { - // Arrange - var cache1 = new RootTypeCache(); - var cache2 = new RootTypeCache(); - - // Populate caches by attempting to resolve types - var result1Before = cache1.GetRootType("NonExistentAssembly", "NonExistentType"); - var result2Before = cache2.GetRootType("AnotherAssembly", "AnotherType"); - - // Verify initial state (should be null for non-existent types) - Assert.Null(result1Before); - Assert.Null(result2Before); - - // Act - Trigger hot reload cache clearing - HotReloadManager.UpdateApplication(null); - - // Assert - Verify cache is cleared by checking that subsequent lookups still work - // (This verifies the cache clearing didn't break functionality) - var result1After = cache1.GetRootType("NonExistentAssembly", "NonExistentType"); - var result2After = cache2.GetRootType("AnotherAssembly", "AnotherType"); - - Assert.Null(result1After); - Assert.Null(result2After); - } - - [Fact] - public void GetRootType_ReturnsNullForNonExistentType() - { - // Arrange - var cache = new RootTypeCache(); - - // Act - var result = cache.GetRootType("NonExistentAssembly", "NonExistentType"); - - // Assert - Assert.Null(result); - } - - [Fact] - public void GetRootType_ReturnsTypeForExistingType() - { - // Arrange - var cache = new RootTypeCache(); - var currentAssembly = typeof(RootTypeCache).Assembly.GetName().Name; - var existingTypeName = typeof(RootTypeCache).FullName; - - // Act - var result = cache.GetRootType(currentAssembly!, existingTypeName!); - - // Assert - Assert.Equal(typeof(RootTypeCache), result); - } -} \ No newline at end of file From 562666cb4d16a9ff5762e5e7bff407d7865e04ed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 Aug 2025 22:04:43 +0000 Subject: [PATCH 05/10] Refactor RootTypeCache hot reload to use instance Clear method Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- .../PersistentServicesRegistry.cs | 1 + .../ComponentServiceCollectionExtensions.cs | 16 ++++++- src/Components/Shared/src/RootTypeCache.cs | 46 +------------------ .../src/Hosting/WebAssemblyHostBuilder.cs | 6 ++- 4 files changed, 23 insertions(+), 46 deletions(-) diff --git a/src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs b/src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs index e27e5c2560b0..fb656d4a2dd5 100644 --- a/src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs +++ b/src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs @@ -30,6 +30,7 @@ static PersistentServicesRegistry() if (HotReloadManager.Default.MetadataUpdateSupported) { HotReloadManager.Default.OnDeltaApplied += _cachedAccessorsByType.Clear; + HotReloadManager.Default.OnDeltaApplied += _persistentServiceTypeCache.Clear; } } diff --git a/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs b/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs index 47e00958895f..35ed43de1b7a 100644 --- a/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs +++ b/src/Components/Server/src/DependencyInjection/ComponentServiceCollectionExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Endpoints; using Microsoft.AspNetCore.Components.Forms; +using Microsoft.AspNetCore.Components.HotReload; using Microsoft.AspNetCore.Components.Routing; using Microsoft.AspNetCore.Components.Server; using Microsoft.AspNetCore.Components.Server.BlazorPack; @@ -67,7 +68,12 @@ public static IServerSideBlazorBuilder AddServerSideBlazor(this IServiceCollecti services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(serviceProvider => + { + var cache = new RootTypeCache(); + RegisterForHotReload(cache); + return cache; + }); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); @@ -124,6 +130,14 @@ public static IServerSideBlazorBuilder AddServerSideBlazor(this IServiceCollecti return builder; } + private static void RegisterForHotReload(RootTypeCache cache) + { + if (HotReloadManager.Default.MetadataUpdateSupported) + { + HotReloadManager.Default.OnDeltaApplied += cache.Clear; + } + } + private sealed class DefaultServerSideBlazorBuilder : IServerSideBlazorBuilder { public DefaultServerSideBlazorBuilder(IServiceCollection services) diff --git a/src/Components/Shared/src/RootTypeCache.cs b/src/Components/Shared/src/RootTypeCache.cs index 4c1cf4a27323..18d208ab61f2 100644 --- a/src/Components/Shared/src/RootTypeCache.cs +++ b/src/Components/Shared/src/RootTypeCache.cs @@ -4,64 +4,22 @@ using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Reflection; -#if COMPONENTS -using Microsoft.AspNetCore.Components.HotReload; -#endif -#if COMPONENTS -namespace Microsoft.AspNetCore.Components.Infrastructure; -#else namespace Microsoft.AspNetCore.Components; -#endif // A cache for root component types internal sealed class RootTypeCache { -#if COMPONENTS - private static readonly List _instances = new(); - - static RootTypeCache() - { - if (HotReloadManager.Default.MetadataUpdateSupported) - { - HotReloadManager.Default.OnDeltaApplied += ClearCache; - } - } -#endif - private readonly ConcurrentDictionary _typeToKeyLookUp = new(); public RootTypeCache() { -#if COMPONENTS - lock (_instances) - { - _instances.Add(new WeakReference(this)); - } -#endif } -#if COMPONENTS - private static void ClearCache() + internal void Clear() { - lock (_instances) - { - for (int i = _instances.Count - 1; i >= 0; i--) - { - var weakRef = _instances[i]; - if (weakRef.Target is RootTypeCache instance) - { - instance._typeToKeyLookUp.Clear(); - } - else - { - // Remove dead reference - _instances.RemoveAt(i); - } - } - } + _typeToKeyLookUp.Clear(); } -#endif public Type? GetRootType(string assembly, string type) { diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs index c9971669d6c0..4ef4e7a06f20 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs @@ -323,7 +323,11 @@ internal void InitializeDefaultServices() Services.AddSingleton(WebAssemblyScrollToLocationHash.Instance); Services.AddSingleton(_jsMethods); Services.AddSingleton(new LazyAssemblyLoader(DefaultWebAssemblyJSRuntime.Instance)); - Services.AddSingleton(_ => _rootComponentCache ?? new()); + Services.AddSingleton(serviceProvider => + { + var cache = _rootComponentCache ?? new RootTypeCache(); + return cache; + }); Services.AddSingleton(); Services.AddSingleton(sp => sp.GetRequiredService().State); Services.AddSupplyValueFromPersistentComponentStateProvider(); From b2d62884a2f87a68e9b9220ed80fe441b0a36b51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 Aug 2025 22:22:27 +0000 Subject: [PATCH 06/10] Make RootTypeCache fields static and add hot reload support in constructors Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- .../src/Circuits/ServerComponentDeserializer.cs | 15 +++++++++++---- .../Circuits/ServerComponentDeserializerTest.cs | 1 - .../src/Hosting/WebAssemblyHostBuilder.cs | 7 ++----- .../src/Services/DefaultWebAssemblyJSRuntime.cs | 4 ++-- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Components/Server/src/Circuits/ServerComponentDeserializer.cs b/src/Components/Server/src/Circuits/ServerComponentDeserializer.cs index eaebd8856968..157cafd612de 100644 --- a/src/Components/Server/src/Circuits/ServerComponentDeserializer.cs +++ b/src/Components/Server/src/Circuits/ServerComponentDeserializer.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text; using System.Text.Json; +using Microsoft.AspNetCore.Components.HotReload; using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.Logging; @@ -57,11 +58,19 @@ namespace Microsoft.AspNetCore.Components.Server; // * If a marker can't be unprotected we will fail early. We know that the marker was tampered with and can't be trusted. internal sealed partial class ServerComponentDeserializer : IServerComponentDeserializer { + private static readonly RootTypeCache _rootTypeCache = new(); private readonly IDataProtector _dataProtector; private readonly ILogger _logger; - private readonly RootTypeCache _RootTypeCache; private readonly ComponentParameterDeserializer _parametersDeserializer; + static ServerComponentDeserializer() + { + if (HotReloadManager.Default.MetadataUpdateSupported) + { + HotReloadManager.Default.OnDeltaApplied += _rootTypeCache.Clear; + } + } + // The following fields are only used in TryDeserializeSingleComponentDescriptor. // The TryDeserializeComponentDescriptorCollection method uses a stateless // approach to efficiently detect invalid component records. @@ -72,7 +81,6 @@ internal sealed partial class ServerComponentDeserializer : IServerComponentDese public ServerComponentDeserializer( IDataProtectionProvider dataProtectionProvider, ILogger logger, - RootTypeCache RootTypeCache, ComponentParameterDeserializer parametersDeserializer) { // When we protect the data we use a time-limited data protector with the @@ -87,7 +95,6 @@ public ServerComponentDeserializer( .ToTimeLimitedDataProtector(); _logger = logger; - _RootTypeCache = RootTypeCache; _parametersDeserializer = parametersDeserializer; } @@ -206,7 +213,7 @@ public bool TryDeserializeWebRootComponentDescriptor(ComponentMarker record, [No private bool TryDeserializeComponentTypeAndParameters(ServerComponent serverComponent, [NotNullWhen(true)] out Type? componentType, out ParameterView parameters) { parameters = default; - componentType = _RootTypeCache + componentType = _rootTypeCache .GetRootType(serverComponent.AssemblyName, serverComponent.TypeName); if (componentType == null) diff --git a/src/Components/Server/test/Circuits/ServerComponentDeserializerTest.cs b/src/Components/Server/test/Circuits/ServerComponentDeserializerTest.cs index 0d04a3135dfe..7ceb4a3d3e25 100644 --- a/src/Components/Server/test/Circuits/ServerComponentDeserializerTest.cs +++ b/src/Components/Server/test/Circuits/ServerComponentDeserializerTest.cs @@ -436,7 +436,6 @@ private ServerComponentDeserializer CreateServerComponentDeserializer() return new ServerComponentDeserializer( _ephemeralDataProtectionProvider, NullLogger.Instance, - new RootTypeCache(), new ComponentParameterDeserializer(NullLogger.Instance, new ComponentParametersTypeCache())); } diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs index 4ef4e7a06f20..9dad26a0e937 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs @@ -28,7 +28,6 @@ public sealed class WebAssemblyHostBuilder { private readonly IInternalJSImportMethods _jsMethods; private Func _createServiceProvider; - private RootTypeCache? _rootComponentCache; private string? _persistedState; private ServiceProviderOptions? _serviceProviderOptions; @@ -146,11 +145,10 @@ private void InitializeRegisteredRootComponents() registeredComponents[i].PrerenderId = i.ToString(CultureInfo.InvariantCulture); } - _rootComponentCache = new RootTypeCache(); var componentDeserializer = WebAssemblyComponentParameterDeserializer.Instance; foreach (var registeredComponent in registeredComponents) { - var componentType = _rootComponentCache.GetRootType(registeredComponent.Assembly!, registeredComponent.TypeName!); + var componentType = DefaultWebAssemblyJSRuntime._rootComponentCache.GetRootType(registeredComponent.Assembly!, registeredComponent.TypeName!); if (componentType is null) { throw new InvalidOperationException( @@ -325,8 +323,7 @@ internal void InitializeDefaultServices() Services.AddSingleton(new LazyAssemblyLoader(DefaultWebAssemblyJSRuntime.Instance)); Services.AddSingleton(serviceProvider => { - var cache = _rootComponentCache ?? new RootTypeCache(); - return cache; + return new RootTypeCache(); }); Services.AddSingleton(); Services.AddSingleton(sp => sp.GetRequiredService().State); diff --git a/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs b/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs index f55739f2bdda..f0b893152baf 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs @@ -20,7 +20,7 @@ internal sealed partial class DefaultWebAssemblyJSRuntime : WebAssemblyJSRuntime { public static readonly DefaultWebAssemblyJSRuntime Instance = new(); - private readonly RootTypeCache _rootComponentCache = new(); + internal static readonly RootTypeCache _rootComponentCache = new(); public ElementReferenceContext ElementReferenceContext { get; } @@ -131,7 +131,7 @@ internal static RootComponentOperationBatch DeserializeOperations(string operati throw new InvalidOperationException($"The component operation of type '{operation.Type}' requires a '{nameof(operation.Marker)}' to be specified."); } - var componentType = Instance._rootComponentCache.GetRootType(operation.Marker!.Value.Assembly!, operation.Marker.Value.TypeName!) + var componentType = _rootComponentCache.GetRootType(operation.Marker!.Value.Assembly!, operation.Marker.Value.TypeName!) ?? throw new InvalidOperationException($"Root component type '{operation.Marker.Value.TypeName}' could not be found in the assembly '{operation.Marker.Value.Assembly}'."); var parameters = DeserializeComponentParameters(operation.Marker.Value); operation.Descriptor = new(componentType, parameters); From 9916739d600b367a2e2b896bdfd85d8c33b60839 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 Aug 2025 22:25:43 +0000 Subject: [PATCH 07/10] Update remaining test files to remove RootTypeCache constructor parameters Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- src/Components/Server/test/Circuits/CircuitHostTest.cs | 2 +- .../Server/test/Circuits/CircuitPersistenceManagerTest.cs | 1 - src/Components/Server/test/Circuits/RemoteRendererTest.cs | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Components/Server/test/Circuits/CircuitHostTest.cs b/src/Components/Server/test/Circuits/CircuitHostTest.cs index 670ba4427247..fd85aa0ea920 100644 --- a/src/Components/Server/test/Circuits/CircuitHostTest.cs +++ b/src/Components/Server/test/Circuits/CircuitHostTest.cs @@ -834,7 +834,7 @@ private ProtectedPrerenderComponentApplicationStore CreateStore() private ServerComponentDeserializer CreateDeserializer() { - return new ServerComponentDeserializer(_ephemeralDataProtectionProvider, NullLogger.Instance, new RootTypeCache(), new ComponentParameterDeserializer(NullLogger.Instance, new ComponentParametersTypeCache())); + return new ServerComponentDeserializer(_ephemeralDataProtectionProvider, NullLogger.Instance, new ComponentParameterDeserializer(NullLogger.Instance, new ComponentParametersTypeCache())); } private static TestRemoteRenderer GetRemoteRenderer() diff --git a/src/Components/Server/test/Circuits/CircuitPersistenceManagerTest.cs b/src/Components/Server/test/Circuits/CircuitPersistenceManagerTest.cs index 6bd9be6e9a7d..5c242cbff12d 100644 --- a/src/Components/Server/test/Circuits/CircuitPersistenceManagerTest.cs +++ b/src/Components/Server/test/Circuits/CircuitPersistenceManagerTest.cs @@ -474,7 +474,6 @@ await circuitHost.UpdateRootComponents( private static ServerComponentDeserializer CreateDeserializer(EphemeralDataProtectionProvider dataProtectionProvider) => new ServerComponentDeserializer( dataProtectionProvider, NullLoggerFactory.Instance.CreateLogger(), - new RootTypeCache(), new ComponentParameterDeserializer( NullLoggerFactory.Instance.CreateLogger(), new ComponentParametersTypeCache())); diff --git a/src/Components/Server/test/Circuits/RemoteRendererTest.cs b/src/Components/Server/test/Circuits/RemoteRendererTest.cs index 73af5c13651c..355f04c04bc3 100644 --- a/src/Components/Server/test/Circuits/RemoteRendererTest.cs +++ b/src/Components/Server/test/Circuits/RemoteRendererTest.cs @@ -646,7 +646,6 @@ private TestRemoteRenderer GetRemoteRenderer(IServiceProvider serviceProvider, C var serverComponentDeserializer = new ServerComponentDeserializer( _ephemeralDataProtectionProvider, NullLogger.Instance, - new RootTypeCache(), new ComponentParameterDeserializer( NullLogger.Instance, new ComponentParametersTypeCache())); From 2859f253dcc117d349f105c59bfe1fb4bf9a0a11 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 Aug 2025 11:34:24 +0000 Subject: [PATCH 08/10] Fix HotReloadManager accessibility in Server project by including shared source Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- .../Server/src/Circuits/ServerComponentDeserializer.cs | 2 ++ .../Server/src/Microsoft.AspNetCore.Components.Server.csproj | 1 + 2 files changed, 3 insertions(+) diff --git a/src/Components/Server/src/Circuits/ServerComponentDeserializer.cs b/src/Components/Server/src/Circuits/ServerComponentDeserializer.cs index 157cafd612de..98ba5203a0a2 100644 --- a/src/Components/Server/src/Circuits/ServerComponentDeserializer.cs +++ b/src/Components/Server/src/Circuits/ServerComponentDeserializer.cs @@ -71,6 +71,8 @@ static ServerComponentDeserializer() } } + + // The following fields are only used in TryDeserializeSingleComponentDescriptor. // The TryDeserializeComponentDescriptorCollection method uses a stateless // approach to efficiently detect invalid component records. diff --git a/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj b/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj index a26f7d17f2d1..e8f4a0edfc8e 100644 --- a/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj +++ b/src/Components/Server/src/Microsoft.AspNetCore.Components.Server.csproj @@ -51,6 +51,7 @@ + From 993ba49ff50c7ab31a0fb85e98d444943e41afe4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 Aug 2025 11:44:09 +0000 Subject: [PATCH 09/10] Revert to block-scoped namespace as requested Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- src/Components/Shared/src/RootTypeCache.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Components/Shared/src/RootTypeCache.cs b/src/Components/Shared/src/RootTypeCache.cs index 18d208ab61f2..9759234e059d 100644 --- a/src/Components/Shared/src/RootTypeCache.cs +++ b/src/Components/Shared/src/RootTypeCache.cs @@ -5,7 +5,8 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; -namespace Microsoft.AspNetCore.Components; +namespace Microsoft.AspNetCore.Components +{ // A cache for root component types internal sealed class RootTypeCache @@ -89,3 +90,4 @@ public bool Equals(Key other) => string.Equals(Assembly, other.Assembly, StringC public override int GetHashCode() => HashCode.Combine(Assembly, Type); } } +} From c74ec0f7304ff2b6512b0fbe0f1035d8c62c9f47 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 Aug 2025 14:06:21 +0000 Subject: [PATCH 10/10] Convert RootTypeCache namespace to file-scoped format Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- src/Components/Shared/src/RootTypeCache.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Components/Shared/src/RootTypeCache.cs b/src/Components/Shared/src/RootTypeCache.cs index 9759234e059d..18d208ab61f2 100644 --- a/src/Components/Shared/src/RootTypeCache.cs +++ b/src/Components/Shared/src/RootTypeCache.cs @@ -5,8 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; -namespace Microsoft.AspNetCore.Components -{ +namespace Microsoft.AspNetCore.Components; // A cache for root component types internal sealed class RootTypeCache @@ -90,4 +89,3 @@ public bool Equals(Key other) => string.Equals(Assembly, other.Assembly, StringC public override int GetHashCode() => HashCode.Combine(Assembly, Type); } } -}