Skip to content

Commit f808e24

Browse files
committed
add support for specifying the render mode for the persisted service
1 parent 5bb3215 commit f808e24

File tree

9 files changed

+99
-35
lines changed

9 files changed

+99
-35
lines changed

src/Components/Components/src/Infrastructure/ComponentStatePersistenceManager.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,13 @@ private void InferRenderModes(Renderer renderer)
134134
continue;
135135
}
136136

137+
if (registration.Callback.Target is PersistentServicesRegistry)
138+
{
139+
// The registration callback is associated with the services registry, which is a special case.
140+
// We don't need to infer the render mode for this case.
141+
continue;
142+
}
143+
137144
throw new InvalidOperationException(
138145
$"The registered callback {registration.Callback.Method.Name} must be associated with a component or define" +
139146
$" an explicit render mode type during registration.");

src/Components/Components/src/PersistentState/IPersistentComponentRegistration.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,26 @@
33

44
namespace Microsoft.AspNetCore.Components;
55

6-
internal interface IPersistentComponentRegistration
6+
internal interface IPersistentComponentRegistration : IComparable<IPersistentComponentRegistration>, IEquatable<IPersistentComponentRegistration>
77
{
88
public string Assembly { get; }
99
public string FullTypeName { get; }
10+
11+
public IComponentRenderMode? GetRenderModeOrDefault();
12+
13+
int IComparable<IPersistentComponentRegistration>.CompareTo(IPersistentComponentRegistration? other)
14+
{
15+
var assemblyComparison = string.Compare(Assembly, other?.Assembly, StringComparison.Ordinal);
16+
if (assemblyComparison != 0)
17+
{
18+
return assemblyComparison;
19+
}
20+
return string.Compare(FullTypeName, other?.FullTypeName, StringComparison.Ordinal);
21+
}
22+
23+
bool IEquatable<IPersistentComponentRegistration>.Equals(IPersistentComponentRegistration? other)
24+
{
25+
return string.Equals(Assembly, other?.Assembly, StringComparison.Ordinal) &&
26+
string.Equals(FullTypeName, other?.FullTypeName, StringComparison.Ordinal);
27+
}
1028
}

src/Components/Components/src/PersistentState/PersistentComponentRegistration.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
namespace Microsoft.AspNetCore.Components;
77

88
[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
9-
internal class PersistentComponentRegistration<TService> : IPersistentComponentRegistration
9+
internal class PersistentComponentRegistration<TService>(IComponentRenderMode componentRenderMode) : IPersistentComponentRegistration
1010
{
1111
public string Assembly => typeof(TService).Assembly.GetName().Name!;
1212
public string FullTypeName => typeof(TService).FullName!;
1313

14+
public IComponentRenderMode? GetRenderModeOrDefault() => componentRenderMode;
15+
1416
private string GetDebuggerDisplay() => $"{Assembly}::{FullTypeName}";
1517
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Components;
5+
6+
internal class PersistentServiceRenderMode(IComponentRenderMode componentRenderMode)
7+
{
8+
public IComponentRenderMode ComponentRenderMode { get; } = componentRenderMode;
9+
}

src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ internal class PersistentServicesRegistry
2020

2121
private readonly IServiceProvider _serviceProvider;
2222
private readonly PersistentServiceTypeCache _persistentServiceTypeCache;
23-
private IEnumerable<IPersistentComponentRegistration> _registrations;
24-
private PersistingComponentStateSubscription _subscription;
23+
private IPersistentComponentRegistration[] _registrations;
24+
private List<PersistingComponentStateSubscription> _subscriptions = [];
2525
private static readonly ConcurrentDictionary<Type, PropertiesAccessor> _cachedAccessorsByType = new();
2626

2727
public PersistentServicesRegistry(
@@ -30,7 +30,7 @@ public PersistentServicesRegistry(
3030
{
3131
_serviceProvider = serviceProvider;
3232
_persistentServiceTypeCache = new PersistentServiceTypeCache();
33-
_registrations = registrations;
33+
_registrations = [.. registrations.Distinct().Order()];
3434
}
3535

3636
[DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")]
@@ -40,6 +40,8 @@ private class PersistentComponentRegistration : IPersistentComponentRegistration
4040

4141
public string FullTypeName { get; set; } = "";
4242

43+
public IComponentRenderMode? GetRenderModeOrDefault() => null;
44+
4345
private string GetDebuggerDisplay() => $"{Assembly}::{FullTypeName}";
4446
}
4547

@@ -76,24 +78,6 @@ private static void RestoreInstanceState(object instance, Type type, PersistentC
7678
}
7779
}
7880

79-
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
80-
private void PersistServicesState(PersistentComponentState state)
81-
{
82-
// Persist all the registrations
83-
state.PersistAsJson(_registryKey, _registrations);
84-
foreach (var registration in _registrations)
85-
{
86-
var type = ResolveType(registration.Assembly, registration.FullTypeName);
87-
if (type == null)
88-
{
89-
continue;
90-
}
91-
92-
var instance = _serviceProvider.GetRequiredService(type);
93-
PersistInstanceState(instance, type, state);
94-
}
95-
}
96-
9781
[RequiresUnreferencedCode("Calls Microsoft.AspNetCore.Components.PersistentComponentState.PersistAsJson(String, Object, Type)")]
9882
private static void PersistInstanceState(object instance, Type type, PersistentComponentState state)
9983
{
@@ -114,28 +98,67 @@ private static void PersistInstanceState(object instance, Type type, PersistentC
11498
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
11599
internal void Restore(PersistentComponentState state)
116100
{
117-
if (_registrations?.Any() != true &&
118-
state.TryTakeFromJson<IEnumerable<PersistentComponentRegistration>>(_registryKey, out var registry) &&
101+
if (_registrations.Length == 0 &&
102+
state.TryTakeFromJson<PersistentComponentRegistration[]>(_registryKey, out var registry) &&
119103
registry != null)
120104
{
121-
_registrations = registry;
105+
_registrations = registry ?? [];
122106
}
123107

124108
RestoreRegistrationsIfAvailable(state);
125109
}
126110

111+
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
127112
internal void RegisterForPersistence(PersistentComponentState state)
128113
{
129-
if (!_subscription.Equals(default(PersistingComponentStateSubscription)))
114+
if (_subscriptions.Count != 0)
130115
{
131116
return;
132117
}
133118

134-
_subscription = state.RegisterOnPersisting(() =>
119+
var comparer = EqualityComparer<IComponentRenderMode?>.Create((x, y) =>
135120
{
136-
PersistServicesState(state);
137-
return Task.CompletedTask;
121+
var xType = x?.GetType();
122+
var yType = y?.GetType();
123+
return xType == yType;
138124
});
125+
126+
var renderModes = new HashSet<IComponentRenderMode?>(comparer);
127+
128+
var subscriptions = new List<PersistingComponentStateSubscription>(_registrations.Length + 1);
129+
for (var i = 1; i < _registrations.Length; i++)
130+
{
131+
var registration = _registrations[i];
132+
var type = ResolveType(registration.Assembly, registration.FullTypeName);
133+
if (type == null)
134+
{
135+
continue;
136+
}
137+
138+
var renderMode = registration.GetRenderModeOrDefault();
139+
if (renderMode != null)
140+
{
141+
renderModes.Add(renderMode);
142+
}
143+
144+
var instance = _serviceProvider.GetRequiredService(type);
145+
subscriptions.Add(state.RegisterOnPersisting(() =>
146+
{
147+
PersistInstanceState(instance, type, state);
148+
return Task.CompletedTask;
149+
}, renderMode));
150+
}
151+
152+
foreach (var renderMode in renderModes)
153+
{
154+
subscriptions.Add(state.RegisterOnPersisting(() =>
155+
{
156+
state.PersistAsJson(_registryKey, _registrations);
157+
return Task.CompletedTask;
158+
}, renderMode));
159+
}
160+
161+
_subscriptions = subscriptions;
139162
}
140163

141164
private sealed class PropertiesAccessor

src/Components/Components/src/PublicAPI.Unshipped.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager.
33
Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateAttribute
44
Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateAttribute.SupplyParameterFromPersistentComponentStateAttribute() -> void
55
Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions
6-
static Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions.AddPersistentService<TService>(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
6+
static Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions.AddPersistentService<TService>(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, Microsoft.AspNetCore.Components.IComponentRenderMode! componentRenderMode) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
77
static Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions.AddSupplyValueFromPersistentComponentStateProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!

src/Components/Components/src/SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ public static IServiceCollection AddSupplyValueFromPersistentComponentStateProvi
3232
/// </remarks>
3333
/// <typeparam name="TService">The service type to register for persistence.</typeparam>
3434
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
35+
/// <param name="componentRenderMode">The <see cref="IComponentRenderMode"/> to register the service for.</param>
3536
/// <returns>The <see cref="IServiceCollection"/>.</returns>
36-
public static IServiceCollection AddPersistentService<[DynamicallyAccessedMembers(JsonSerialized)] TService>(this IServiceCollection services)
37+
public static IServiceCollection AddPersistentService<[DynamicallyAccessedMembers(JsonSerialized)] TService>(
38+
this IServiceCollection services,
39+
IComponentRenderMode componentRenderMode)
3740
{
3841
// This method does something very similar to what we do when we register root components, except in this case we are registering services.
3942
// We collect a list of all the registrations on during static rendering mode and push those registrations to interactive mode.
@@ -47,7 +50,8 @@ public static IServiceCollection AddSupplyValueFromPersistentComponentStateProvi
4750
// We can choose to fail when the service is not registered on DI.
4851
// We loop through the properties in the type and try to restore the properties that have SupplyParameterFromPersistentComponentState on them.
4952
services.TryAddScoped<PersistentServicesRegistry>();
50-
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPersistentComponentRegistration, PersistentComponentRegistration<TService>>());
53+
//services.TryAddEnumerable(ServiceDescriptor.Singleton(new PersistentServiceRenderMode(componentRenderMode)));
54+
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPersistentComponentRegistration>(new PersistentComponentRegistration<TService>(componentRenderMode)));
5155

5256
return services;
5357
}

src/Components/Endpoints/src/DependencyInjection/RazorComponentsServiceCollectionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public static IRazorComponentsBuilder AddRazorComponents(this IServiceCollection
7979
services.AddSupplyValueFromFormProvider();
8080
services.TryAddScoped<AntiforgeryStateProvider, EndpointAntiforgeryStateProvider>();
8181
services.TryAddScoped(sp => (EndpointAntiforgeryStateProvider)sp.GetRequiredService<AntiforgeryStateProvider>());
82-
services.AddPersistentService<AntiforgeryStateProvider>();
82+
services.AddPersistentService<AntiforgeryStateProvider>(RenderMode.InteractiveAuto);
8383
services.TryAddScoped<HttpContextFormDataProvider>();
8484
services.TryAddScoped<IFormValueMapper, HttpContextFormValueMapper>();
8585

src/Components/Samples/BlazorServerApp/Startup.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using BlazorServerApp.Data;
55
using Microsoft.AspNetCore.Components;
66
using Microsoft.AspNetCore.Components.Authorization;
7+
using Microsoft.AspNetCore.Components.Web;
78

89
namespace BlazorServerApp;
910

@@ -23,7 +24,7 @@ public void ConfigureServices(IServiceCollection services)
2324
services.AddRazorPages();
2425
services.AddServerSideBlazor();
2526
services.AddScoped<SamplePersistentService>();
26-
services.AddPersistentService<SamplePersistentService>();
27+
services.AddPersistentService<SamplePersistentService>(RenderMode.InteractiveServer);
2728
services.AddSingleton<WeatherForecastService>();
2829
}
2930

0 commit comments

Comments
 (0)