Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ static PersistentServicesRegistry()
if (HotReloadManager.Default.MetadataUpdateSupported)
{
HotReloadManager.Default.OnDeltaApplied += _cachedAccessorsByType.Clear;
HotReloadManager.Default.OnDeltaApplied += _persistentServiceTypeCache.Clear;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -57,11 +58,21 @@
// * 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<ServerComponentDeserializer> _logger;
private readonly RootTypeCache _RootTypeCache;
private readonly ComponentParameterDeserializer _parametersDeserializer;

static ServerComponentDeserializer()
{
if (HotReloadManager.Default.MetadataUpdateSupported)
{
HotReloadManager.Default.OnDeltaApplied += _rootTypeCache.Clear;
}
}

Check failure on line 73 in src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Source-Build (Managed))

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs#L73

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs(73,1): error IDE2000: (NETCORE_ENGINEERING_TELEMETRY=Build) Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

Check failure on line 73 in src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Build: Linux x64)

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs#L73

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs(73,1): error IDE2000: (NETCORE_ENGINEERING_TELEMETRY=Build) Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

Check failure on line 73 in src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Build: Linux Musl ARM)

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs#L73

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs(73,1): error IDE2000: (NETCORE_ENGINEERING_TELEMETRY=Build) Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

Check failure on line 73 in src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Build: Linux ARM)

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs#L73

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs(73,1): error IDE2000: (NETCORE_ENGINEERING_TELEMETRY=Build) Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

Check failure on line 73 in src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Build: Linux Musl ARM64)

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs#L73

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs(73,1): error IDE2000: (NETCORE_ENGINEERING_TELEMETRY=Build) Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

Check failure on line 73 in src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Build: Linux ARM64)

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs#L73

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs(73,1): error IDE2000: (NETCORE_ENGINEERING_TELEMETRY=Build) Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

Check failure on line 73 in src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Build: Linux Musl x64)

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs#L73

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs(73,1): error IDE2000: (NETCORE_ENGINEERING_TELEMETRY=Build) Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

Check failure on line 73 in src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Build: macOS x64)

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs#L73

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs(73,1): error IDE2000: (NETCORE_ENGINEERING_TELEMETRY=Build) Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

Check failure on line 73 in src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Build: macOS arm64)

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs#L73

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs(73,1): error IDE2000: (NETCORE_ENGINEERING_TELEMETRY=Build) Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

Check failure on line 73 in src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

View check run for this annotation

Azure Pipelines / aspnetcore-quarantined-pr (Tests: Ubuntu x64)

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs#L73

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs(73,1): error IDE2000: (NETCORE_ENGINEERING_TELEMETRY=Build) Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

Check failure on line 73 in src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

View check run for this annotation

Azure Pipelines / aspnetcore-quarantined-pr (Tests: macOS)

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs#L73

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs(73,1): error IDE2000: (NETCORE_ENGINEERING_TELEMETRY=Build) Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

Check failure on line 73 in src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Test: macOS)

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs#L73

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs(73,1): error IDE2000: (NETCORE_ENGINEERING_TELEMETRY=Build) Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

Check failure on line 73 in src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci (Build Test: Ubuntu x64)

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs#L73

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs(73,1): error IDE2000: (NETCORE_ENGINEERING_TELEMETRY=Build) Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

Check failure on line 73 in src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

View check run for this annotation

Azure Pipelines / aspnetcore-quarantined-pr

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs#L73

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs(73,1): error IDE2000: (NETCORE_ENGINEERING_TELEMETRY=Build) Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

Check failure on line 73 in src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

View check run for this annotation

Azure Pipelines / aspnetcore-quarantined-pr

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs#L73

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs(73,1): error IDE2000: (NETCORE_ENGINEERING_TELEMETRY=Build) Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

Check failure on line 73 in src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs#L73

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs(73,1): error IDE2000: (NETCORE_ENGINEERING_TELEMETRY=Build) Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

Check failure on line 73 in src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs#L73

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs(73,1): error IDE2000: (NETCORE_ENGINEERING_TELEMETRY=Build) Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

Check failure on line 73 in src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs#L73

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs(73,1): error IDE2000: (NETCORE_ENGINEERING_TELEMETRY=Build) Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)

Check failure on line 73 in src/Components/Server/src/Circuits/ServerComponentDeserializer.cs

View check run for this annotation

Azure Pipelines / aspnetcore-ci

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs#L73

src/Components/Server/src/Circuits/ServerComponentDeserializer.cs(73,1): error IDE2000: (NETCORE_ENGINEERING_TELEMETRY=Build) Avoid multiple blank lines (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2000)


// The following fields are only used in TryDeserializeSingleComponentDescriptor.
// The TryDeserializeComponentDescriptorCollection method uses a stateless
// approach to efficiently detect invalid component records.
Expand All @@ -72,7 +83,6 @@
public ServerComponentDeserializer(
IDataProtectionProvider dataProtectionProvider,
ILogger<ServerComponentDeserializer> logger,
RootTypeCache RootTypeCache,
ComponentParameterDeserializer parametersDeserializer)
{
// When we protect the data we use a time-limited data protector with the
Expand All @@ -87,7 +97,6 @@
.ToTimeLimitedDataProtector();

_logger = logger;
_RootTypeCache = RootTypeCache;
_parametersDeserializer = parametersDeserializer;
}

Expand Down Expand Up @@ -206,7 +215,7 @@
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -67,7 +68,12 @@ public static IServerSideBlazorBuilder AddServerSideBlazor(this IServiceCollecti
services.TryAddSingleton<CircuitMetrics>();
services.TryAddSingleton<ICircuitFactory, CircuitFactory>();
services.TryAddSingleton<ICircuitHandleRegistry, CircuitHandleRegistry>();
services.TryAddSingleton<RootTypeCache>();
services.TryAddSingleton<RootTypeCache>(serviceProvider =>
{
var cache = new RootTypeCache();
RegisterForHotReload(cache);
return cache;
});
services.TryAddSingleton<ComponentParameterDeserializer>();
services.TryAddSingleton<ComponentParametersTypeCache>();
services.TryAddSingleton<CircuitIdFactory>();
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
<Compile Include="$(ComponentsSharedSourceRoot)src\RootComponentOperationBatch.cs" />
<Compile Include="$(ComponentsSharedSourceRoot)src\RootComponentOperationType.cs" />
<Compile Include="$(ComponentsSharedSourceRoot)src\RootTypeCache.cs" />
<Compile Include="$(ComponentsSharedSourceRoot)src\HotReloadManager.cs" LinkBase="HotReload" />
<Compile Include="$(ComponentsSharedSourceRoot)src\DefaultAntiforgeryStateProvider.cs" LinkBase="Forms" />
<Compile Include="$(ComponentsSharedSourceRoot)src\WebRendererId.cs" />
<Compile Include="$(SharedSourceRoot)Components\ServerComponentSerializer.cs" LinkBase="DependencyInjection" />
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Server/test/Circuits/CircuitHostTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,7 @@ private ProtectedPrerenderComponentApplicationStore CreateStore()

private ServerComponentDeserializer CreateDeserializer()
{
return new ServerComponentDeserializer(_ephemeralDataProtectionProvider, NullLogger<ServerComponentDeserializer>.Instance, new RootTypeCache(), new ComponentParameterDeserializer(NullLogger<ComponentParameterDeserializer>.Instance, new ComponentParametersTypeCache()));
return new ServerComponentDeserializer(_ephemeralDataProtectionProvider, NullLogger<ServerComponentDeserializer>.Instance, new ComponentParameterDeserializer(NullLogger<ComponentParameterDeserializer>.Instance, new ComponentParametersTypeCache()));
}

private static TestRemoteRenderer GetRemoteRenderer()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,6 @@ await circuitHost.UpdateRootComponents(
private static ServerComponentDeserializer CreateDeserializer(EphemeralDataProtectionProvider dataProtectionProvider) => new ServerComponentDeserializer(
dataProtectionProvider,
NullLoggerFactory.Instance.CreateLogger<ServerComponentDeserializer>(),
new RootTypeCache(),
new ComponentParameterDeserializer(
NullLoggerFactory.Instance.CreateLogger<ComponentParameterDeserializer>(),
new ComponentParametersTypeCache()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -646,7 +646,6 @@ private TestRemoteRenderer GetRemoteRenderer(IServiceProvider serviceProvider, C
var serverComponentDeserializer = new ServerComponentDeserializer(
_ephemeralDataProtectionProvider,
NullLogger<ServerComponentDeserializer>.Instance,
new RootTypeCache(),
new ComponentParameterDeserializer(
NullLogger<ComponentParameterDeserializer>.Instance,
new ComponentParametersTypeCache()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,6 @@ private ServerComponentDeserializer CreateServerComponentDeserializer()
return new ServerComponentDeserializer(
_ephemeralDataProtectionProvider,
NullLogger<ServerComponentDeserializer>.Instance,
new RootTypeCache(),
new ComponentParameterDeserializer(NullLogger<ComponentParameterDeserializer>.Instance, new ComponentParametersTypeCache()));
}

Expand Down
13 changes: 9 additions & 4 deletions src/Components/Shared/src/RootTypeCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,22 @@
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

#if COMPONENTS
namespace Microsoft.AspNetCore.Components.Infrastructure;
#else
namespace Microsoft.AspNetCore.Components;
#endif
Comment on lines -8 to -12
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot undo this change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted the namespace declaration back to block-scoped format as requested (commit 993ba49).


// A cache for root component types
internal sealed class RootTypeCache
{
private readonly ConcurrentDictionary<Key, Type?> _typeToKeyLookUp = new();

public RootTypeCache()
{
}

internal void Clear()
{
_typeToKeyLookUp.Clear();
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot

Don't do #if COMPONENTS. Add an internal instance method Clear to clear the concurrent dictionary cache. Look for all the callsites where the RootTypeCache is used, and add the MetadataUpdateHandler in those situations.

You can set things up in the constructor since those types are Singletons.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private readonly RootTypeCache _RootTypeCache;

Look for places where you can make private RootTypeCache fields static if they aren't. Like

private readonly RootTypeCache _RootTypeCache;

@copilot

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've implemented the requested changes. Removed #if COMPONENTS directives, added an internal Clear() method to RootTypeCache, and registered MetadataUpdateHandler at the singleton registration points in ComponentServiceCollectionExtensions and PersistentServicesRegistry. WebAssembly project builds without hot reload support as expected since it doesn't include HotReloadManager.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've implemented the requested changes:

  1. Made ServerComponentDeserializer._RootTypeCache field static and added hot reload support in static constructor (b2d6288)
  2. Made DefaultWebAssemblyJSRuntime._rootComponentCache field static (WebAssembly doesn't include HotReloadManager so no hot reload registration)
  3. Updated constructor signatures and test files to remove RootTypeCache parameters where they're now static
  4. Updated usages to reference the static fields instead of instance fields

The implementation follows the same pattern used by PersistentServicesRegistry which already has a static _persistentServiceTypeCache with hot reload support in its static constructor.

public Type? GetRootType(string assembly, string type)
{
var key = new Key(assembly, type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ public sealed class WebAssemblyHostBuilder
{
private readonly IInternalJSImportMethods _jsMethods;
private Func<IServiceProvider> _createServiceProvider;
private RootTypeCache? _rootComponentCache;
private string? _persistedState;
private ServiceProviderOptions? _serviceProviderOptions;

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -323,7 +321,10 @@ internal void InitializeDefaultServices()
Services.AddSingleton<IScrollToLocationHash>(WebAssemblyScrollToLocationHash.Instance);
Services.AddSingleton(_jsMethods);
Services.AddSingleton(new LazyAssemblyLoader(DefaultWebAssemblyJSRuntime.Instance));
Services.AddSingleton(_ => _rootComponentCache ?? new());
Services.AddSingleton(serviceProvider =>
{
return new RootTypeCache();
});
Services.AddSingleton<ComponentStatePersistenceManager>();
Services.AddSingleton(sp => sp.GetRequiredService<ComponentStatePersistenceManager>().State);
Services.AddSupplyValueFromPersistentComponentStateProvider();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down Expand Up @@ -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);
Expand Down
Loading