();
services.AddCascadingAuthenticationState();
diff --git a/src/Components/test/testassets/TestContentPackage/DeclarativePersistStateComponent.razor b/src/Components/test/testassets/TestContentPackage/DeclarativePersistStateComponent.razor
index 07cec64a2706..a0a8400c17cb 100644
--- a/src/Components/test/testassets/TestContentPackage/DeclarativePersistStateComponent.razor
+++ b/src/Components/test/testassets/TestContentPackage/DeclarativePersistStateComponent.razor
@@ -1,4 +1,5 @@
Application state is @Value
+Custom value is @CustomValue
Render mode: @_renderMode
@code {
@@ -11,11 +12,18 @@
[PersistentState]
public string Value { get; set; }
+ [PersistentState]
+ public int CustomValue { get; set; }
+
private string _renderMode = "SSR";
protected override void OnInitialized()
{
Value ??= !RendererInfo.IsInteractive ? InitialValue : "not restored";
+ if (CustomValue == 0)
+ {
+ CustomValue = !RendererInfo.IsInteractive ? 42 : 0;
+ }
_renderMode = OperatingSystem.IsBrowser() ? "WebAssembly" : "Server";
}
}
From 208556834792d8591de986ac5c8e89f48071175b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 3 Jul 2025 19:09:10 +0000
Subject: [PATCH 12/22] Fix CA1310 analyzer error by specifying
StringComparison.Ordinal in CustomIntSerializer
Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
---
.../testassets/Components.TestServer/CustomIntSerializer.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Components/test/testassets/Components.TestServer/CustomIntSerializer.cs b/src/Components/test/testassets/Components.TestServer/CustomIntSerializer.cs
index 4be462f43527..949dd1417376 100644
--- a/src/Components/test/testassets/Components.TestServer/CustomIntSerializer.cs
+++ b/src/Components/test/testassets/Components.TestServer/CustomIntSerializer.cs
@@ -26,7 +26,7 @@ public int Restore(ReadOnlySequence data)
var bytes = data.ToArray();
var text = Encoding.UTF8.GetString(bytes);
- if (text.StartsWith("CUSTOM:") && int.TryParse(text.Substring(7), out var value))
+ if (text.StartsWith("CUSTOM:", StringComparison.Ordinal) && int.TryParse(text.Substring(7), out var value))
{
return value;
}
From b6723a606ea5d5ea91f76c20c06ce13002fa825e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 3 Jul 2025 20:46:54 +0000
Subject: [PATCH 13/22] Move CustomIntSerializer to shared TestContentPackage
and register in WebAssembly for E2E tests
Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
---
src/Components/test/testassets/BasicTestApp/Program.cs | 4 ++++
.../Components.TestServer/RazorComponentEndpointsStartup.cs | 1 +
.../CustomIntSerializer.cs | 2 +-
3 files changed, 6 insertions(+), 1 deletion(-)
rename src/Components/test/testassets/{Components.TestServer => TestContentPackage}/CustomIntSerializer.cs (97%)
diff --git a/src/Components/test/testassets/BasicTestApp/Program.cs b/src/Components/test/testassets/BasicTestApp/Program.cs
index 554e65ba1ffe..4b7b70eae7d4 100644
--- a/src/Components/test/testassets/BasicTestApp/Program.cs
+++ b/src/Components/test/testassets/BasicTestApp/Program.cs
@@ -13,6 +13,7 @@
using Microsoft.AspNetCore.Components.WebAssembly.Services;
using Microsoft.Extensions.Logging.Configuration;
using Microsoft.JSInterop;
+using TestContentPackage;
namespace BasicTestApp;
@@ -45,6 +46,9 @@ public static async Task Main(string[] args)
builder.Services.AddScoped();
builder.Services.AddTransient();
+ // Register custom serializer for E2E testing of persistent component state serialization extensibility
+ builder.Services.AddSingleton, CustomIntSerializer>();
+
builder.Services.AddKeyedSingleton("keyed-service-1", TestKeyedService.Create("value-1"));
builder.Services.AddKeyedSingleton(TestServiceKey.ServiceB, TestKeyedService.Create("value-2"));
diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs b/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs
index c43b30f10654..ca9b936766f5 100644
--- a/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs
+++ b/src/Components/test/testassets/Components.TestServer/RazorComponentEndpointsStartup.cs
@@ -13,6 +13,7 @@
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Server;
using Microsoft.AspNetCore.Mvc;
+using TestContentPackage;
using TestContentPackage.Services;
namespace TestServer;
diff --git a/src/Components/test/testassets/Components.TestServer/CustomIntSerializer.cs b/src/Components/test/testassets/TestContentPackage/CustomIntSerializer.cs
similarity index 97%
rename from src/Components/test/testassets/Components.TestServer/CustomIntSerializer.cs
rename to src/Components/test/testassets/TestContentPackage/CustomIntSerializer.cs
index 949dd1417376..a9a65cc8dc7e 100644
--- a/src/Components/test/testassets/Components.TestServer/CustomIntSerializer.cs
+++ b/src/Components/test/testassets/TestContentPackage/CustomIntSerializer.cs
@@ -5,7 +5,7 @@
using System.Text;
using Microsoft.AspNetCore.Components;
-namespace TestServer;
+namespace TestContentPackage;
///
/// A custom serializer for int values that uses a custom format to test serialization extensibility.
From 67ca5ec5ed386c516440d379e383ad995d9b9ffe Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 4 Jul 2025 03:26:52 +0000
Subject: [PATCH 14/22] Register CustomIntSerializer in Components.WasmMinimal
for E2E test compatibility
Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
---
.../test/testassets/Components.WasmMinimal/Program.cs | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/Components/test/testassets/Components.WasmMinimal/Program.cs b/src/Components/test/testassets/Components.WasmMinimal/Program.cs
index 88a28726961b..ab9098babd5b 100644
--- a/src/Components/test/testassets/Components.WasmMinimal/Program.cs
+++ b/src/Components/test/testassets/Components.WasmMinimal/Program.cs
@@ -4,8 +4,10 @@
using System.Runtime.InteropServices.JavaScript;
using System.Security.Claims;
using Components.TestServer.Services;
+using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
+using TestContentPackage;
using TestContentPackage.Services;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
@@ -14,6 +16,9 @@
builder.Services.AddSingleton();
builder.Services.AddSingleton();
+// Register custom serializer for persistent component state
+builder.Services.AddSingleton, CustomIntSerializer>();
+
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddAuthenticationStateDeserialization(options =>
From c1fefc54c9544561cf12284877b15f316cc02e48 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 4 Jul 2025 09:55:51 +0000
Subject: [PATCH 15/22] Make IPersistentComponentStateSerializer internal and
move generic interface to separate file
Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
---
.../IPersistentComponentStateSerializer.cs | 37 +------------------
.../IPersistentComponentStateSerializerOfT.cs | 29 +++++++++++++++
.../src/PersistentStateValueProvider.cs | 23 +++++++++++-
.../Components/src/PublicAPI.Unshipped.txt | 3 --
4 files changed, 52 insertions(+), 40 deletions(-)
create mode 100644 src/Components/Components/src/IPersistentComponentStateSerializerOfT.cs
diff --git a/src/Components/Components/src/IPersistentComponentStateSerializer.cs b/src/Components/Components/src/IPersistentComponentStateSerializer.cs
index 8d7717c2f535..ce7ff0a7bbaf 100644
--- a/src/Components/Components/src/IPersistentComponentStateSerializer.cs
+++ b/src/Components/Components/src/IPersistentComponentStateSerializer.cs
@@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Components;
///
/// Provides custom serialization logic for persistent component state values.
///
-public interface IPersistentComponentStateSerializer
+internal interface IPersistentComponentStateSerializer
{
///
/// Serializes the provided and writes it to the .
@@ -27,39 +27,4 @@ public interface IPersistentComponentStateSerializer
/// The serialized data to deserialize.
/// The deserialized value.
object Restore(Type type, ReadOnlySequence data);
-}
-
-///
-/// Provides custom serialization logic for persistent component state values of type .
-///
-/// The type of the value to serialize.
-public interface IPersistentComponentStateSerializer : IPersistentComponentStateSerializer
-{
- ///
- /// Serializes the provided and writes it to the .
- ///
- /// The value to serialize.
- /// The buffer writer to write the serialized data to.
- /// A task that represents the asynchronous serialization operation.
- Task PersistAsync(T value, IBufferWriter writer);
-
- ///
- /// Deserializes a value of type from the provided .
- /// This method must be synchronous to avoid UI tearing during component state restoration.
- ///
- /// The serialized data to deserialize.
- /// The deserialized value.
- T Restore(ReadOnlySequence data);
-
- ///
- /// Default implementation of the non-generic PersistAsync method.
- ///
- Task IPersistentComponentStateSerializer.PersistAsync(Type type, object value, IBufferWriter writer)
- => PersistAsync((T)value, writer);
-
- ///
- /// Default implementation of the non-generic Restore method.
- ///
- object IPersistentComponentStateSerializer.Restore(Type type, ReadOnlySequence data)
- => Restore(data)!;
}
\ No newline at end of file
diff --git a/src/Components/Components/src/IPersistentComponentStateSerializerOfT.cs b/src/Components/Components/src/IPersistentComponentStateSerializerOfT.cs
new file mode 100644
index 000000000000..3af342d09cec
--- /dev/null
+++ b/src/Components/Components/src/IPersistentComponentStateSerializerOfT.cs
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers;
+
+namespace Microsoft.AspNetCore.Components;
+
+///
+/// Provides custom serialization logic for persistent component state values of type .
+///
+/// The type of the value to serialize.
+public interface IPersistentComponentStateSerializer
+{
+ ///
+ /// Serializes the provided and writes it to the .
+ ///
+ /// The value to serialize.
+ /// The buffer writer to write the serialized data to.
+ /// A task that represents the asynchronous serialization operation.
+ Task PersistAsync(T value, IBufferWriter writer);
+
+ ///
+ /// Deserializes a value of type from the provided .
+ /// This method must be synchronous to avoid UI tearing during component state restoration.
+ ///
+ /// The serialized data to deserialize.
+ /// The deserialized value.
+ T Restore(ReadOnlySequence data);
+}
\ No newline at end of file
diff --git a/src/Components/Components/src/PersistentStateValueProvider.cs b/src/Components/Components/src/PersistentStateValueProvider.cs
index 9b2f8706d7c1..e5031d62855d 100644
--- a/src/Components/Components/src/PersistentStateValueProvider.cs
+++ b/src/Components/Components/src/PersistentStateValueProvider.cs
@@ -108,7 +108,15 @@ private static PropertyGetter ResolvePropertyGetter(Type type, string propertyNa
{
var serializerType = typeof(IPersistentComponentStateSerializer<>).MakeGenericType(type);
var serializer = serviceProvider.GetService(serializerType);
- return serializer as IPersistentComponentStateSerializer;
+
+ if (serializer != null)
+ {
+ // Create an adapter that implements the internal interface
+ var adapterType = typeof(SerializerAdapter<>).MakeGenericType(type);
+ return (IPersistentComponentStateSerializer?)Activator.CreateInstance(adapterType, serializer);
+ }
+
+ return null;
}
[UnconditionalSuppressMessage(
@@ -366,4 +374,17 @@ internal bool TryTake(string key, IPersistentComponentStateSerializer
+ /// Adapter class to bridge between the public generic interface and the internal interface.
+ ///
+ /// The type of the value to serialize.
+ private sealed class SerializerAdapter(IPersistentComponentStateSerializer serializer) : IPersistentComponentStateSerializer
+ {
+ public Task PersistAsync(Type type, object value, IBufferWriter writer)
+ => serializer.PersistAsync((T)value, writer);
+
+ public object Restore(Type type, ReadOnlySequence data)
+ => serializer.Restore(data)!;
+ }
}
diff --git a/src/Components/Components/src/PublicAPI.Unshipped.txt b/src/Components/Components/src/PublicAPI.Unshipped.txt
index cb3a8aaa4e32..4f5a360c8a5d 100644
--- a/src/Components/Components/src/PublicAPI.Unshipped.txt
+++ b/src/Components/Components/src/PublicAPI.Unshipped.txt
@@ -16,9 +16,6 @@ Microsoft.AspNetCore.Components.Infrastructure.RegisterPersistentComponentStateS
Microsoft.AspNetCore.Components.PersistentStateAttribute
Microsoft.AspNetCore.Components.PersistentStateAttribute.PersistentStateAttribute() -> void
Microsoft.AspNetCore.Components.Infrastructure.PersistentStateProviderServiceCollectionExtensions
-Microsoft.AspNetCore.Components.IPersistentComponentStateSerializer
-Microsoft.AspNetCore.Components.IPersistentComponentStateSerializer.PersistAsync(System.Type! type, object! value, System.Buffers.IBufferWriter! writer) -> System.Threading.Tasks.Task!
-Microsoft.AspNetCore.Components.IPersistentComponentStateSerializer.Restore(System.Type! type, System.Buffers.ReadOnlySequence data) -> object!
Microsoft.AspNetCore.Components.IPersistentComponentStateSerializer
Microsoft.AspNetCore.Components.IPersistentComponentStateSerializer.PersistAsync(T value, System.Buffers.IBufferWriter! writer) -> System.Threading.Tasks.Task!
Microsoft.AspNetCore.Components.IPersistentComponentStateSerializer.Restore(System.Buffers.ReadOnlySequence data) -> T
From cbf9fa9e351383151b20464f7df5c00108a717de Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 4 Jul 2025 10:35:22 +0000
Subject: [PATCH 16/22] Implement explicit interface implementation for custom
serializers and remove XML comments from internal interface
Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
---
.../src/IPersistentComponentStateSerializer.cs | 18 ------------------
.../src/PersistentStateValueProvider.cs | 8 ++------
2 files changed, 2 insertions(+), 24 deletions(-)
diff --git a/src/Components/Components/src/IPersistentComponentStateSerializer.cs b/src/Components/Components/src/IPersistentComponentStateSerializer.cs
index ce7ff0a7bbaf..9fe18f53ce92 100644
--- a/src/Components/Components/src/IPersistentComponentStateSerializer.cs
+++ b/src/Components/Components/src/IPersistentComponentStateSerializer.cs
@@ -5,26 +5,8 @@
namespace Microsoft.AspNetCore.Components;
-///
-/// Provides custom serialization logic for persistent component state values.
-///
internal interface IPersistentComponentStateSerializer
{
- ///
- /// Serializes the provided and writes it to the .
- ///
- /// The type of the value to serialize.
- /// The value to serialize.
- /// The buffer writer to write the serialized data to.
- /// A task that represents the asynchronous serialization operation.
Task PersistAsync(Type type, object value, IBufferWriter writer);
-
- ///
- /// Deserializes a value from the provided .
- /// This method must be synchronous to avoid UI tearing during component state restoration.
- ///
- /// The type of the value to deserialize.
- /// The serialized data to deserialize.
- /// The deserialized value.
object Restore(Type type, ReadOnlySequence data);
}
\ No newline at end of file
diff --git a/src/Components/Components/src/PersistentStateValueProvider.cs b/src/Components/Components/src/PersistentStateValueProvider.cs
index e5031d62855d..fe38b2c77f68 100644
--- a/src/Components/Components/src/PersistentStateValueProvider.cs
+++ b/src/Components/Components/src/PersistentStateValueProvider.cs
@@ -375,16 +375,12 @@ internal bool TryTake(string key, IPersistentComponentStateSerializer
- /// Adapter class to bridge between the public generic interface and the internal interface.
- ///
- /// The type of the value to serialize.
private sealed class SerializerAdapter(IPersistentComponentStateSerializer serializer) : IPersistentComponentStateSerializer
{
- public Task PersistAsync(Type type, object value, IBufferWriter writer)
+ Task IPersistentComponentStateSerializer.PersistAsync(Type type, object value, IBufferWriter writer)
=> serializer.PersistAsync((T)value, writer);
- public object Restore(Type type, ReadOnlySequence data)
+ object IPersistentComponentStateSerializer.Restore(Type type, ReadOnlySequence data)
=> serializer.Restore(data)!;
}
}
From 365064dcfc064c4b4e94547534824815b6a0ec54 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 4 Jul 2025 10:52:16 +0000
Subject: [PATCH 17/22] Implement explicit interface implementation in
IPersistentComponentStateSerializer<T> and make base interface public
Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
---
.../IPersistentComponentStateSerializer.cs | 2 +-
.../IPersistentComponentStateSerializerOfT.cs | 14 +++++++++++++-
.../src/PersistentStateValueProvider.cs | 19 ++-----------------
.../Components/src/PublicAPI.Unshipped.txt | 3 +++
4 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/src/Components/Components/src/IPersistentComponentStateSerializer.cs b/src/Components/Components/src/IPersistentComponentStateSerializer.cs
index 9fe18f53ce92..f6146749f24d 100644
--- a/src/Components/Components/src/IPersistentComponentStateSerializer.cs
+++ b/src/Components/Components/src/IPersistentComponentStateSerializer.cs
@@ -5,7 +5,7 @@
namespace Microsoft.AspNetCore.Components;
-internal interface IPersistentComponentStateSerializer
+public interface IPersistentComponentStateSerializer
{
Task PersistAsync(Type type, object value, IBufferWriter writer);
object Restore(Type type, ReadOnlySequence data);
diff --git a/src/Components/Components/src/IPersistentComponentStateSerializerOfT.cs b/src/Components/Components/src/IPersistentComponentStateSerializerOfT.cs
index 3af342d09cec..5e3c311989e6 100644
--- a/src/Components/Components/src/IPersistentComponentStateSerializerOfT.cs
+++ b/src/Components/Components/src/IPersistentComponentStateSerializerOfT.cs
@@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Components;
/// Provides custom serialization logic for persistent component state values of type .
///