1515
1616namespace Microsoft . AspNetCore . Components . Infrastructure ;
1717
18- internal sealed class PersistentStateValueProvider ( PersistentComponentState state ) : ICascadingValueSupplier
18+ internal sealed class PersistentStateValueProvider ( PersistentComponentState state , IServiceProvider serviceProvider ) : ICascadingValueSupplier
1919{
2020 private static readonly ConcurrentDictionary < ( string , string , string ) , byte [ ] > _keyCache = new ( ) ;
2121 private static readonly ConcurrentDictionary < ( Type , string ) , PropertyGetter > _propertyGetterCache = new ( ) ;
22+ private readonly ConcurrentDictionary < Type , IPersistentComponentStateSerializer ? > _serializerCache = new ( ) ;
2223
2324 private readonly Dictionary < ComponentState , PersistingComponentStateSubscription > _subscriptions = [ ] ;
2425
@@ -42,6 +43,20 @@ public bool CanSupplyValue(in CascadingParameterInfo parameterInfo)
4243 var componentState = ( ComponentState ) key ! ;
4344 var storageKey = ComputeKey ( componentState , parameterInfo . PropertyName ) ;
4445
46+ // Try to get a custom serializer for this type first
47+ var customSerializer = _serializerCache . GetOrAdd ( parameterInfo . PropertyType , SerializerFactory ) ;
48+
49+ if ( customSerializer != null )
50+ {
51+ if ( state . TryTakeBytes ( storageKey , out var data ) )
52+ {
53+ var sequence = new ReadOnlySequence < byte > ( data ! ) ;
54+ return customSerializer . Restore ( parameterInfo . PropertyType , sequence ) ;
55+ }
56+ return null ;
57+ }
58+
59+ // Fallback to JSON serialization
4560 return state . TryTakeFromJson ( storageKey , parameterInfo . PropertyType , out var value ) ? value : null ;
4661 }
4762
@@ -52,6 +67,10 @@ public void Subscribe(ComponentState subscriber, in CascadingParameterInfo param
5267 {
5368 var propertyName = parameterInfo . PropertyName ;
5469 var propertyType = parameterInfo . PropertyType ;
70+
71+ // Resolve serializer outside the lambda
72+ var customSerializer = _serializerCache . GetOrAdd ( propertyType , SerializerFactory ) ;
73+
5574 _subscriptions [ subscriber ] = state . RegisterOnPersisting ( ( ) =>
5675 {
5776 var storageKey = ComputeKey ( subscriber , propertyName ) ;
@@ -61,6 +80,16 @@ public void Subscribe(ComponentState subscriber, in CascadingParameterInfo param
6180 {
6281 return Task . CompletedTask ;
6382 }
83+
84+ if ( customSerializer != null )
85+ {
86+ using var writer = new PooledArrayBufferWriter < byte > ( ) ;
87+ customSerializer . Persist ( propertyType , property , writer ) ;
88+ state . PersistAsBytes ( storageKey , writer . WrittenMemory . ToArray ( ) ) ;
89+ return Task . CompletedTask ;
90+ }
91+
92+ // Fallback to JSON serialization
6493 state . PersistAsJson ( storageKey , property , propertyType ) ;
6594 return Task . CompletedTask ;
6695 } , subscriber . Renderer . GetComponentRenderMode ( subscriber . Component ) ) ;
@@ -71,6 +100,15 @@ private static PropertyGetter ResolvePropertyGetter(Type type, string propertyNa
71100 return _propertyGetterCache . GetOrAdd ( ( type , propertyName ) , PropertyGetterFactory ) ;
72101 }
73102
103+ private IPersistentComponentStateSerializer ? SerializerFactory ( Type type )
104+ {
105+ var serializerType = typeof ( PersistentComponentStateSerializer < > ) . MakeGenericType ( type ) ;
106+ var serializer = serviceProvider . GetService ( serializerType ) ;
107+
108+ // The generic class now inherits from the internal interface, so we can cast directly
109+ return serializer as IPersistentComponentStateSerializer ;
110+ }
111+
74112 [ UnconditionalSuppressMessage (
75113 "Trimming" ,
76114 "IL2077:Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The source field does not have matching annotations." ,
@@ -281,4 +319,49 @@ private static bool IsSerializableKey(object key)
281319
282320 return result ;
283321 }
322+
323+ /// <summary>
324+ /// Serializes <paramref name="instance"/> using the provided <paramref name="serializer"/> and persists it under the given <paramref name="key"/>.
325+ /// </summary>
326+ /// <typeparam name="TValue">The <paramref name="instance"/> type.</typeparam>
327+ /// <param name="key">The key to use to persist the state.</param>
328+ /// <param name="instance">The instance to persist.</param>
329+ /// <param name="serializer">The custom serializer to use for serialization.</param>
330+ internal void PersistAsync < TValue > ( string key , TValue instance , PersistentComponentStateSerializer < TValue > serializer )
331+ {
332+ ArgumentNullException . ThrowIfNull ( key ) ;
333+ ArgumentNullException . ThrowIfNull ( serializer ) ;
334+
335+ using var writer = new PooledArrayBufferWriter < byte > ( ) ;
336+ serializer . Persist ( instance , writer ) ;
337+ state . PersistAsBytes ( key , writer . WrittenMemory . ToArray ( ) ) ;
338+ }
339+
340+ /// <summary>
341+ /// Tries to retrieve the persisted state with the given <paramref name="key"/> and deserializes it using the provided <paramref name="serializer"/> into an
342+ /// instance of type <typeparamref name="TValue"/>.
343+ /// When the key is present, the state is successfully returned via <paramref name="instance"/>
344+ /// and removed from the <see cref="PersistentComponentState"/>.
345+ /// </summary>
346+ /// <param name="key">The key used to persist the instance.</param>
347+ /// <param name="serializer">The custom serializer to use for deserialization.</param>
348+ /// <param name="instance">The persisted instance.</param>
349+ /// <returns><c>true</c> if the state was found; <c>false</c> otherwise.</returns>
350+ internal bool TryTake < TValue > ( string key , PersistentComponentStateSerializer < TValue > serializer , [ MaybeNullWhen ( false ) ] out TValue instance )
351+ {
352+ ArgumentNullException . ThrowIfNull ( key ) ;
353+ ArgumentNullException . ThrowIfNull ( serializer ) ;
354+
355+ if ( state . TryTakeBytes ( key , out var data ) )
356+ {
357+ var sequence = new ReadOnlySequence < byte > ( data ! ) ;
358+ instance = serializer . Restore ( sequence ) ;
359+ return true ;
360+ }
361+ else
362+ {
363+ instance = default ;
364+ return false ;
365+ }
366+ }
284367}
0 commit comments