PrerenderPersistedStateAsync(HttpContext ht
store = new CompositeStore(server, auto, webAssembly);
IPersistenceReason persistenceReason = IsProgressivelyEnhancedNavigation(httpContext.Request)
- ? new PersistOnEnhancedNavigation()
- : new PersistOnPrerendering();
+ ? PersistOnEnhancedNavigation.Instance
+ : PersistOnPrerendering.Instance;
await manager.PersistStateAsync(store, this, persistenceReason);
foreach (var kvp in auto.Saved)
diff --git a/src/Components/Server/src/Circuits/CircuitPersistenceManager.cs b/src/Components/Server/src/Circuits/CircuitPersistenceManager.cs
index b91f4567269e..6344a4f5e963 100644
--- a/src/Components/Server/src/Circuits/CircuitPersistenceManager.cs
+++ b/src/Components/Server/src/Circuits/CircuitPersistenceManager.cs
@@ -28,7 +28,7 @@ public async Task PauseCircuitAsync(CircuitHost circuit, bool saveStateToClient
collector.PersistRootComponents,
RenderMode.InteractiveServer);
- await persistenceManager.PersistStateAsync(collector, renderer, new PersistOnCircuitPause());
+ await persistenceManager.PersistStateAsync(collector, renderer, PersistOnCircuitPause.Instance);
if (saveStateToClient)
{
diff --git a/src/Components/Web/src/PersistOnCircuitPause.cs b/src/Components/Web/src/PersistOnCircuitPause.cs
new file mode 100644
index 000000000000..11c27e8d4de0
--- /dev/null
+++ b/src/Components/Web/src/PersistOnCircuitPause.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Components.Web;
+
+///
+/// Represents persistence when a circuit is paused.
+///
+public sealed class PersistOnCircuitPause : IPersistenceReason
+{
+ ///
+ /// Gets the singleton instance of .
+ ///
+ public static readonly PersistOnCircuitPause Instance = new();
+
+ private PersistOnCircuitPause() { }
+
+ ///
+ public bool PersistByDefault => true;
+}
\ No newline at end of file
diff --git a/src/Components/Web/src/PersistOnEnhancedNavigation.cs b/src/Components/Web/src/PersistOnEnhancedNavigation.cs
new file mode 100644
index 000000000000..bda5f23a1831
--- /dev/null
+++ b/src/Components/Web/src/PersistOnEnhancedNavigation.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Components.Web;
+
+///
+/// Represents persistence during enhanced navigation.
+///
+public sealed class PersistOnEnhancedNavigation : IPersistenceReason
+{
+ ///
+ /// Gets the singleton instance of .
+ ///
+ public static readonly PersistOnEnhancedNavigation Instance = new();
+
+ private PersistOnEnhancedNavigation() { }
+
+ ///
+ public bool PersistByDefault => false;
+}
\ No newline at end of file
diff --git a/src/Components/Web/src/PersistOnPrerendering.cs b/src/Components/Web/src/PersistOnPrerendering.cs
new file mode 100644
index 000000000000..b14a99bc58f9
--- /dev/null
+++ b/src/Components/Web/src/PersistOnPrerendering.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.AspNetCore.Components.Web;
+
+///
+/// Represents persistence during prerendering.
+///
+public sealed class PersistOnPrerendering : IPersistenceReason
+{
+ ///
+ /// Gets the singleton instance of .
+ ///
+ public static readonly PersistOnPrerendering Instance = new();
+
+ private PersistOnPrerendering() { }
+
+ ///
+ public bool PersistByDefault => true;
+}
\ No newline at end of file
diff --git a/src/Components/Web/src/PersistenceReasons.cs b/src/Components/Web/src/PersistenceReasons.cs
deleted file mode 100644
index f679e707a8eb..000000000000
--- a/src/Components/Web/src/PersistenceReasons.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.AspNetCore.Components.Web;
-
-///
-/// Represents persistence during prerendering.
-///
-public class PersistOnPrerendering : IPersistenceReason
-{
- ///
- public bool PersistByDefault { get; } = true;
-}
-
-///
-/// Represents persistence during enhanced navigation.
-///
-public class PersistOnEnhancedNavigation : IPersistenceReason
-{
- ///
- public bool PersistByDefault { get; }
-}
-
-///
-/// Represents persistence when a circuit is paused.
-///
-public class PersistOnCircuitPause : IPersistenceReason
-{
- ///
- public bool PersistByDefault { get; } = true;
-}
\ No newline at end of file
diff --git a/src/Components/Web/src/PublicAPI.Unshipped.txt b/src/Components/Web/src/PublicAPI.Unshipped.txt
index bb0c5f9109cb..8ff842d031a8 100644
--- a/src/Components/Web/src/PublicAPI.Unshipped.txt
+++ b/src/Components/Web/src/PublicAPI.Unshipped.txt
@@ -2,17 +2,17 @@
Microsoft.AspNetCore.Components.Web.Internal.IInternalWebJSInProcessRuntime.InvokeJS(in Microsoft.JSInterop.Infrastructure.JSInvocationInfo invocationInfo) -> string!
Microsoft.AspNetCore.Components.Web.PersistOnCircuitPause
Microsoft.AspNetCore.Components.Web.PersistOnCircuitPause.PersistByDefault.get -> bool
-Microsoft.AspNetCore.Components.Web.PersistOnCircuitPause.PersistOnCircuitPause() -> void
+static readonly Microsoft.AspNetCore.Components.Web.PersistOnCircuitPause.Instance -> Microsoft.AspNetCore.Components.Web.PersistOnCircuitPause!
Microsoft.AspNetCore.Components.Web.PersistOnCircuitPauseFilter
Microsoft.AspNetCore.Components.Web.PersistOnCircuitPauseFilter.PersistOnCircuitPauseFilter(bool persist = true) -> void
Microsoft.AspNetCore.Components.Web.PersistOnEnhancedNavigation
Microsoft.AspNetCore.Components.Web.PersistOnEnhancedNavigation.PersistByDefault.get -> bool
-Microsoft.AspNetCore.Components.Web.PersistOnEnhancedNavigation.PersistOnEnhancedNavigation() -> void
+static readonly Microsoft.AspNetCore.Components.Web.PersistOnEnhancedNavigation.Instance -> Microsoft.AspNetCore.Components.Web.PersistOnEnhancedNavigation!
Microsoft.AspNetCore.Components.Web.PersistOnEnhancedNavigationFilter
Microsoft.AspNetCore.Components.Web.PersistOnEnhancedNavigationFilter.PersistOnEnhancedNavigationFilter(bool persist = true) -> void
Microsoft.AspNetCore.Components.Web.PersistOnPrerendering
Microsoft.AspNetCore.Components.Web.PersistOnPrerendering.PersistByDefault.get -> bool
-Microsoft.AspNetCore.Components.Web.PersistOnPrerendering.PersistOnPrerendering() -> void
+static readonly Microsoft.AspNetCore.Components.Web.PersistOnPrerendering.Instance -> Microsoft.AspNetCore.Components.Web.PersistOnPrerendering!
Microsoft.AspNetCore.Components.Web.PersistOnPrerenderingFilter
Microsoft.AspNetCore.Components.Web.PersistOnPrerenderingFilter.PersistOnPrerenderingFilter(bool persist = true) -> void
virtual Microsoft.AspNetCore.Components.Routing.NavLink.ShouldMatch(string! uriAbsolute) -> bool
\ No newline at end of file
diff --git a/src/Components/Web/test/PersistenceReasonFiltersTest.cs b/src/Components/Web/test/PersistenceReasonFiltersTest.cs
index f98f587a5205..65baa6677f4f 100644
--- a/src/Components/Web/test/PersistenceReasonFiltersTest.cs
+++ b/src/Components/Web/test/PersistenceReasonFiltersTest.cs
@@ -13,7 +13,7 @@ public void PersistOnPrerenderingFilter_AllowsByDefault()
{
// Arrange
var filter = new PersistOnPrerenderingFilter();
- var reason = new PersistOnPrerendering();
+ var reason = PersistOnPrerendering.Instance;
// Act
var result = filter.ShouldPersist(reason);
@@ -27,7 +27,7 @@ public void PersistOnPrerenderingFilter_CanBlock()
{
// Arrange
var filter = new PersistOnPrerenderingFilter(persist: false);
- var reason = new PersistOnPrerendering();
+ var reason = PersistOnPrerendering.Instance;
// Act
var result = filter.ShouldPersist(reason);
@@ -41,7 +41,7 @@ public void PersistOnEnhancedNavigationFilter_AllowsByDefault()
{
// Arrange
var filter = new PersistOnEnhancedNavigationFilter();
- var reason = new PersistOnEnhancedNavigation();
+ var reason = PersistOnEnhancedNavigation.Instance;
// Act
var result = filter.ShouldPersist(reason);
@@ -55,7 +55,7 @@ public void PersistOnEnhancedNavigationFilter_DoesNotMatchDifferentReason()
{
// Arrange
var filter = new PersistOnEnhancedNavigationFilter();
- var reason = new PersistOnPrerendering();
+ var reason = PersistOnPrerendering.Instance;
// Act
var result = filter.ShouldPersist(reason);
@@ -69,7 +69,7 @@ public void PersistOnCircuitPauseFilter_AllowsByDefault()
{
// Arrange
var filter = new PersistOnCircuitPauseFilter();
- var reason = new PersistOnCircuitPause();
+ var reason = PersistOnCircuitPause.Instance;
// Act
var result = filter.ShouldPersist(reason);
@@ -83,7 +83,7 @@ public void PersistOnCircuitPauseFilter_CanBlock()
{
// Arrange
var filter = new PersistOnCircuitPauseFilter(persist: false);
- var reason = new PersistOnCircuitPause();
+ var reason = PersistOnCircuitPause.Instance;
// Act
var result = filter.ShouldPersist(reason);
From 52e84883b4a2d024b5af6ed2777835b51137a99b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 18 Jun 2025 15:51:55 +0000
Subject: [PATCH 7/7] Add E2E tests for persistent component state filtering
functionality
Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
---
.../E2ETest/Tests/StatePersistenceTest.cs | 280 ++++++++++++++++++
.../PersistentState/FilteringTestPage.razor | 57 ++++
.../PageWithoutComponents.razor | 6 +
.../FilteredPersistentStateComponent.razor | 147 +++++++++
4 files changed, 490 insertions(+)
create mode 100644 src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/PersistentState/FilteringTestPage.razor
create mode 100644 src/Components/test/testassets/TestContentPackage/PersistentComponents/FilteredPersistentStateComponent.razor
diff --git a/src/Components/test/E2ETest/Tests/StatePersistenceTest.cs b/src/Components/test/E2ETest/Tests/StatePersistenceTest.cs
index d49e4fbc5704..da2449a26270 100644
--- a/src/Components/test/E2ETest/Tests/StatePersistenceTest.cs
+++ b/src/Components/test/E2ETest/Tests/StatePersistenceTest.cs
@@ -210,6 +210,286 @@ private void RenderComponentsWithPersistentStateAndValidate(
interactiveRuntime: interactiveRuntime);
}
+ [Theory]
+ [InlineData(true, typeof(InteractiveServerRenderMode), (string)null)]
+ [InlineData(true, typeof(InteractiveWebAssemblyRenderMode), (string)null)]
+ [InlineData(true, typeof(InteractiveAutoRenderMode), (string)null)]
+ [InlineData(false, typeof(InteractiveServerRenderMode), (string)null)]
+ public void CanFilterPersistentStateCallbacks(bool suppressEnhancedNavigation, Type renderMode, string streaming)
+ {
+ var mode = renderMode switch
+ {
+ var t when t == typeof(InteractiveServerRenderMode) => "server",
+ var t when t == typeof(InteractiveWebAssemblyRenderMode) => "wasm",
+ var t when t == typeof(InteractiveAutoRenderMode) => "auto",
+ _ => throw new ArgumentException($"Unknown render mode: {renderMode.Name}")
+ };
+
+ if (!suppressEnhancedNavigation)
+ {
+ // Navigate to a page without components first to test enhanced navigation filtering
+ Navigate($"subdir/persistent-state/page-no-components?render-mode={mode}&suppress-autostart");
+ if (mode == "auto")
+ {
+ BlockWebAssemblyResourceLoad();
+ }
+ Browser.Click(By.Id("call-blazor-start"));
+ Browser.Click(By.Id("filtering-test-link"));
+ }
+ else
+ {
+ EnhancedNavigationTestUtil.SuppressEnhancedNavigation(this, true);
+ if (mode == "auto")
+ {
+ BlockWebAssemblyResourceLoad();
+ }
+ }
+
+ if (mode != "auto")
+ {
+ ValidateFilteringBehavior(suppressEnhancedNavigation, mode, renderMode, streaming);
+ }
+ else
+ {
+ // For auto mode, validate both server and wasm behavior
+ ValidateFilteringBehavior(suppressEnhancedNavigation, mode, renderMode, streaming, interactiveRuntime: "server");
+
+ UnblockWebAssemblyResourceLoad();
+ Browser.Navigate().Refresh();
+
+ ValidateFilteringBehavior(suppressEnhancedNavigation, mode, renderMode, streaming, interactiveRuntime: "wasm");
+ }
+ }
+
+ [Theory]
+ [InlineData(true, typeof(InteractiveServerRenderMode))]
+ [InlineData(true, typeof(InteractiveWebAssemblyRenderMode))]
+ [InlineData(true, typeof(InteractiveAutoRenderMode))]
+ [InlineData(false, typeof(InteractiveServerRenderMode))]
+ public void CanFilterPersistentStateForEnhancedNavigation(bool suppressEnhancedNavigation, Type renderMode)
+ {
+ var mode = renderMode switch
+ {
+ var t when t == typeof(InteractiveServerRenderMode) => "server",
+ var t when t == typeof(InteractiveWebAssemblyRenderMode) => "wasm",
+ var t when t == typeof(InteractiveAutoRenderMode) => "auto",
+ _ => throw new ArgumentException($"Unknown render mode: {renderMode.Name}")
+ };
+
+ if (!suppressEnhancedNavigation)
+ {
+ // Navigate to a page without components first to test enhanced navigation filtering
+ Navigate($"subdir/persistent-state/page-no-components?render-mode={mode}&suppress-autostart");
+ if (mode == "auto")
+ {
+ BlockWebAssemblyResourceLoad();
+ }
+ Browser.Click(By.Id("call-blazor-start"));
+ // Click link that enables persistence during enhanced navigation
+ Browser.Click(By.Id("filtering-test-link-with-enhanced-nav"));
+ }
+ else
+ {
+ EnhancedNavigationTestUtil.SuppressEnhancedNavigation(this, true);
+ if (mode == "auto")
+ {
+ BlockWebAssemblyResourceLoad();
+ }
+ }
+
+ if (mode != "auto")
+ {
+ ValidateEnhancedNavigationFiltering(suppressEnhancedNavigation, mode, renderMode);
+ }
+ else
+ {
+ // For auto mode, validate both server and wasm behavior
+ ValidateEnhancedNavigationFiltering(suppressEnhancedNavigation, mode, renderMode, interactiveRuntime: "server");
+
+ UnblockWebAssemblyResourceLoad();
+ Browser.Navigate().Refresh();
+
+ ValidateEnhancedNavigationFiltering(suppressEnhancedNavigation, mode, renderMode, interactiveRuntime: "wasm");
+ }
+ }
+
+ [Theory]
+ [InlineData(typeof(InteractiveServerRenderMode))]
+ [InlineData(typeof(InteractiveWebAssemblyRenderMode))]
+ [InlineData(typeof(InteractiveAutoRenderMode))]
+ public void CanDisablePersistenceForPrerendering(Type renderMode)
+ {
+ var mode = renderMode switch
+ {
+ var t when t == typeof(InteractiveServerRenderMode) => "server",
+ var t when t == typeof(InteractiveWebAssemblyRenderMode) => "wasm",
+ var t when t == typeof(InteractiveAutoRenderMode) => "auto",
+ _ => throw new ArgumentException($"Unknown render mode: {renderMode.Name}")
+ };
+
+ // Navigate to a page without components first
+ Navigate($"subdir/persistent-state/page-no-components?render-mode={mode}&suppress-autostart");
+ if (mode == "auto")
+ {
+ BlockWebAssemblyResourceLoad();
+ }
+ Browser.Click(By.Id("call-blazor-start"));
+ // Click link that disables persistence during prerendering
+ Browser.Click(By.Id("filtering-test-link-no-prerendering"));
+
+ if (mode != "auto")
+ {
+ ValidatePrerenderingFilteringDisabled(mode, renderMode);
+ }
+ else
+ {
+ // For auto mode, validate both server and wasm behavior
+ ValidatePrerenderingFilteringDisabled(mode, renderMode, interactiveRuntime: "server");
+
+ UnblockWebAssemblyResourceLoad();
+ Browser.Navigate().Refresh();
+
+ ValidatePrerenderingFilteringDisabled(mode, renderMode, interactiveRuntime: "wasm");
+ }
+ }
+
+ private void ValidateFilteringBehavior(
+ bool suppressEnhancedNavigation,
+ string mode,
+ Type renderMode,
+ string streaming,
+ string interactiveRuntime = null)
+ {
+ if (suppressEnhancedNavigation)
+ {
+ Navigate($"subdir/persistent-state/filtering-test?render-mode={mode}&suppress-autostart");
+
+ // Validate server-side state before Blazor starts
+ AssertFilteringPageState(
+ mode: mode,
+ renderMode: renderMode.Name,
+ interactive: false,
+ interactiveRuntime: interactiveRuntime);
+
+ Browser.Click(By.Id("call-blazor-start"));
+ }
+
+ // Validate state after Blazor is interactive
+ AssertFilteringPageState(
+ mode: mode,
+ renderMode: renderMode.Name,
+ interactive: true,
+ interactiveRuntime: interactiveRuntime);
+ }
+
+ private void ValidateEnhancedNavigationFiltering(
+ bool suppressEnhancedNavigation,
+ string mode,
+ Type renderMode,
+ string interactiveRuntime = null)
+ {
+ if (suppressEnhancedNavigation)
+ {
+ Navigate($"subdir/persistent-state/filtering-test?render-mode={mode}&persist-enhanced-nav=true&suppress-autostart");
+
+ // Validate server-side state before Blazor starts
+ AssertEnhancedNavFilteringPageState(
+ mode: mode,
+ renderMode: renderMode.Name,
+ interactive: false,
+ interactiveRuntime: interactiveRuntime);
+
+ Browser.Click(By.Id("call-blazor-start"));
+ }
+
+ // Validate state after Blazor is interactive
+ AssertEnhancedNavFilteringPageState(
+ mode: mode,
+ renderMode: renderMode.Name,
+ interactive: true,
+ interactiveRuntime: interactiveRuntime);
+ }
+
+ private void ValidatePrerenderingFilteringDisabled(
+ string mode,
+ Type renderMode,
+ string interactiveRuntime = null)
+ {
+ // When prerendering persistence is disabled, components should show fresh state
+ AssertPrerenderingFilteringDisabledPageState(
+ mode: mode,
+ renderMode: renderMode.Name,
+ interactive: true,
+ interactiveRuntime: interactiveRuntime);
+ }
+
+ private void AssertFilteringPageState(
+ string mode,
+ string renderMode,
+ bool interactive,
+ string interactiveRuntime = null)
+ {
+ Browser.Equal($"Render mode: {renderMode}", () => Browser.FindElement(By.Id("render-mode")).Text);
+ Browser.Equal($"Interactive: {interactive}", () => Browser.FindElement(By.Id("interactive")).Text);
+
+ if (interactive)
+ {
+ interactiveRuntime = mode == "server" || mode == "wasm" ? mode : (interactiveRuntime ?? throw new InvalidOperationException("Specify interactiveRuntime for auto mode"));
+ Browser.Equal($"Interactive runtime: {interactiveRuntime}", () => Browser.FindElement(By.Id("interactive-runtime")).Text);
+
+ // Default behavior: persist during prerendering, not during enhanced navigation
+ Browser.Equal("Prerendering state found:true", () => Browser.FindElement(By.Id("prerendering-state-found")).Text);
+ Browser.Equal("Enhanced nav state found:false", () => Browser.FindElement(By.Id("enhanced-nav-state-found")).Text);
+ Browser.Equal("Circuit pause state found:false", () => Browser.FindElement(By.Id("circuit-pause-state-found")).Text);
+ Browser.Equal("Combined filters state found:true", () => Browser.FindElement(By.Id("combined-filters-state-found")).Text);
+ }
+ }
+
+ private void AssertEnhancedNavFilteringPageState(
+ string mode,
+ string renderMode,
+ bool interactive,
+ string interactiveRuntime = null)
+ {
+ Browser.Equal($"Render mode: {renderMode}", () => Browser.FindElement(By.Id("render-mode")).Text);
+ Browser.Equal($"Interactive: {interactive}", () => Browser.FindElement(By.Id("interactive")).Text);
+
+ if (interactive)
+ {
+ interactiveRuntime = mode == "server" || mode == "wasm" ? mode : (interactiveRuntime ?? throw new InvalidOperationException("Specify interactiveRuntime for auto mode"));
+ Browser.Equal($"Interactive runtime: {interactiveRuntime}", () => Browser.FindElement(By.Id("interactive-runtime")).Text);
+
+ // Enhanced navigation persistence enabled
+ Browser.Equal("Prerendering state found:true", () => Browser.FindElement(By.Id("prerendering-state-found")).Text);
+ Browser.Equal("Enhanced nav state found:true", () => Browser.FindElement(By.Id("enhanced-nav-state-found")).Text);
+ Browser.Equal("Circuit pause state found:false", () => Browser.FindElement(By.Id("circuit-pause-state-found")).Text);
+ Browser.Equal("Combined filters state found:true", () => Browser.FindElement(By.Id("combined-filters-state-found")).Text);
+ }
+ }
+
+ private void AssertPrerenderingFilteringDisabledPageState(
+ string mode,
+ string renderMode,
+ bool interactive,
+ string interactiveRuntime = null)
+ {
+ Browser.Equal($"Render mode: {renderMode}", () => Browser.FindElement(By.Id("render-mode")).Text);
+ Browser.Equal($"Interactive: {interactive}", () => Browser.FindElement(By.Id("interactive")).Text);
+
+ if (interactive)
+ {
+ interactiveRuntime = mode == "server" || mode == "wasm" ? mode : (interactiveRuntime ?? throw new InvalidOperationException("Specify interactiveRuntime for auto mode"));
+ Browser.Equal($"Interactive runtime: {interactiveRuntime}", () => Browser.FindElement(By.Id("interactive-runtime")).Text);
+
+ // Prerendering persistence disabled - should show fresh values
+ Browser.Equal("Prerendering state found:false", () => Browser.FindElement(By.Id("prerendering-state-found")).Text);
+ Browser.Equal("Prerendering state value:fresh-prerendering", () => Browser.FindElement(By.Id("prerendering-state-value")).Text);
+ Browser.Equal("Enhanced nav state found:false", () => Browser.FindElement(By.Id("enhanced-nav-state-found")).Text);
+ Browser.Equal("Circuit pause state found:false", () => Browser.FindElement(By.Id("circuit-pause-state-found")).Text);
+ Browser.Equal("Combined filters state found:false", () => Browser.FindElement(By.Id("combined-filters-state-found")).Text);
+ }
+ }
+
private void AssertPageState(
string mode,
string renderMode,
diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/PersistentState/FilteringTestPage.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/PersistentState/FilteringTestPage.razor
new file mode 100644
index 000000000000..e58980227b7c
--- /dev/null
+++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/PersistentState/FilteringTestPage.razor
@@ -0,0 +1,57 @@
+@page "/persistent-state/filtering-test"
+@using TestContentPackage.PersistentComponents
+
+Filtered Persistent State Test Page
+
+
+ This page tests selective state persistence based on filtering criteria.
+ It renders components with different filter configurations to validate that state is persisted or skipped based on the persistence reason.
+
+
+Render mode: @_renderMode?.GetType()?.Name
+Streaming id:@StreamingId
+
+@if (_renderMode != null)
+{
+
+
+
+}
+
+Go to page with no components
+
+@code {
+ private IComponentRenderMode _renderMode;
+
+ [SupplyParameterFromQuery(Name = "render-mode")] public string RenderMode { get; set; }
+ [SupplyParameterFromQuery(Name = "streaming-id")] public string StreamingId { get; set; }
+ [SupplyParameterFromQuery(Name = "server-state")] public string ServerState { get; set; }
+ [SupplyParameterFromQuery(Name = "persist-prerendering")] public bool PersistOnPrerendering { get; set; } = true;
+ [SupplyParameterFromQuery(Name = "persist-enhanced-nav")] public bool PersistOnEnhancedNav { get; set; } = false;
+ [SupplyParameterFromQuery(Name = "persist-circuit-pause")] public bool PersistOnCircuitPause { get; set; } = true;
+
+ protected override void OnInitialized()
+ {
+ if (!string.IsNullOrEmpty(RenderMode))
+ {
+ switch (RenderMode)
+ {
+ case "server":
+ _renderMode = new InteractiveServerRenderMode(true);
+ break;
+ case "wasm":
+ _renderMode = new InteractiveWebAssemblyRenderMode(true);
+ break;
+ case "auto":
+ _renderMode = new InteractiveAutoRenderMode(true);
+ break;
+ default:
+ throw new ArgumentException($"Invalid render mode: {RenderMode}");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/PersistentState/PageWithoutComponents.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/PersistentState/PageWithoutComponents.razor
index fe7693c59523..a818fed166f4 100644
--- a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/PersistentState/PageWithoutComponents.razor
+++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/PersistentState/PageWithoutComponents.razor
@@ -6,6 +6,12 @@
Go to page with components and state
+Go to filtering test page
+
+Go to filtering test page (no prerendering)
+
+Go to filtering test page (with enhanced nav)
+
@code {
[SupplyParameterFromQuery(Name = "render-mode")] public string RenderMode { get; set; }
diff --git a/src/Components/test/testassets/TestContentPackage/PersistentComponents/FilteredPersistentStateComponent.razor b/src/Components/test/testassets/TestContentPackage/PersistentComponents/FilteredPersistentStateComponent.razor
new file mode 100644
index 000000000000..40e12ebf22c0
--- /dev/null
+++ b/src/Components/test/testassets/TestContentPackage/PersistentComponents/FilteredPersistentStateComponent.razor
@@ -0,0 +1,147 @@
+@using Microsoft.AspNetCore.Components.Web
+
+Filtered persistent state component
+
+This component demonstrates selective state persistence based on filtering criteria. It registers different callbacks with different filter combinations to test the filtering behavior.
+
+Interactive: @(!RunningOnServer)
+Interactive runtime: @_interactiveRuntime
+Prerendering state found:@_prerenderingStateFound
+Prerendering state value:@_prerenderingStateValue
+Enhanced nav state found:@_enhancedNavStateFound
+Enhanced nav state value:@_enhancedNavStateValue
+Circuit pause state found:@_circuitPauseStateFound
+Circuit pause state value:@_circuitPauseStateValue
+Combined filters state found:@_combinedFiltersStateFound
+Combined filters state value:@_combinedFiltersStateValue
+
+@code {
+ private bool _prerenderingStateFound;
+ private string _prerenderingStateValue;
+ private bool _enhancedNavStateFound;
+ private string _enhancedNavStateValue;
+ private bool _circuitPauseStateFound;
+ private string _circuitPauseStateValue;
+ private bool _combinedFiltersStateFound;
+ private string _combinedFiltersStateValue;
+ private string _interactiveRuntime;
+
+ [Inject] public PersistentComponentState PersistentComponentState { get; set; }
+
+ [CascadingParameter(Name = nameof(RunningOnServer))] public bool RunningOnServer { get; set; }
+
+ [Parameter] public string ServerState { get; set; }
+ [Parameter] public bool PersistOnPrerendering { get; set; } = true;
+ [Parameter] public bool PersistOnEnhancedNav { get; set; } = false;
+ [Parameter] public bool PersistOnCircuitPause { get; set; } = true;
+
+ protected override void OnInitialized()
+ {
+ // Register callback that only persists during prerendering
+ var prerenderingFilters = new List
+ {
+ new PersistOnPrerenderingFilter(PersistOnPrerendering),
+ new PersistOnEnhancedNavigationFilter(false),
+ new PersistOnCircuitPauseFilter(false)
+ };
+ PersistentComponentState.RegisterOnPersisting(PersistPrerenderingState, null, prerenderingFilters);
+
+ // Register callback that only persists during enhanced navigation
+ var enhancedNavFilters = new List
+ {
+ new PersistOnPrerenderingFilter(false),
+ new PersistOnEnhancedNavigationFilter(PersistOnEnhancedNav),
+ new PersistOnCircuitPauseFilter(false)
+ };
+ PersistentComponentState.RegisterOnPersisting(PersistEnhancedNavState, null, enhancedNavFilters);
+
+ // Register callback that only persists on circuit pause
+ var circuitPauseFilters = new List
+ {
+ new PersistOnPrerenderingFilter(false),
+ new PersistOnEnhancedNavigationFilter(false),
+ new PersistOnCircuitPauseFilter(PersistOnCircuitPause)
+ };
+ PersistentComponentState.RegisterOnPersisting(PersistCircuitPauseState, null, circuitPauseFilters);
+
+ // Register callback with combined filters
+ var combinedFilters = new List
+ {
+ new PersistOnPrerenderingFilter(PersistOnPrerendering),
+ new PersistOnEnhancedNavigationFilter(PersistOnEnhancedNav),
+ new PersistOnCircuitPauseFilter(PersistOnCircuitPause)
+ };
+ PersistentComponentState.RegisterOnPersisting(PersistCombinedFiltersState, null, combinedFilters);
+
+ // Try to restore state
+ _prerenderingStateFound = PersistentComponentState.TryTakeFromJson("PrerenderingState", out _prerenderingStateValue);
+ _enhancedNavStateFound = PersistentComponentState.TryTakeFromJson("EnhancedNavState", out _enhancedNavStateValue);
+ _circuitPauseStateFound = PersistentComponentState.TryTakeFromJson("CircuitPauseState", out _circuitPauseStateValue);
+ _combinedFiltersStateFound = PersistentComponentState.TryTakeFromJson("CombinedFiltersState", out _combinedFiltersStateValue);
+
+ if (!_prerenderingStateFound)
+ {
+ _prerenderingStateValue = "fresh-prerendering";
+ }
+
+ if (!_enhancedNavStateFound)
+ {
+ _enhancedNavStateValue = "fresh-enhanced-nav";
+ }
+
+ if (!_circuitPauseStateFound)
+ {
+ _circuitPauseStateValue = "fresh-circuit-pause";
+ }
+
+ if (!_combinedFiltersStateFound)
+ {
+ _combinedFiltersStateValue = "fresh-combined";
+ }
+
+ if (RunningOnServer)
+ {
+ _interactiveRuntime = "none";
+ // Use server state if provided
+ if (!string.IsNullOrEmpty(ServerState))
+ {
+ _prerenderingStateFound = true;
+ _prerenderingStateValue = $"{ServerState}-prerendering";
+ _enhancedNavStateFound = true;
+ _enhancedNavStateValue = $"{ServerState}-enhanced-nav";
+ _circuitPauseStateFound = true;
+ _circuitPauseStateValue = $"{ServerState}-circuit-pause";
+ _combinedFiltersStateFound = true;
+ _combinedFiltersStateValue = $"{ServerState}-combined";
+ }
+ }
+ else
+ {
+ _interactiveRuntime = OperatingSystem.IsBrowser() ? "wasm" : "server";
+ }
+ }
+
+ Task PersistPrerenderingState()
+ {
+ PersistentComponentState.PersistAsJson("PrerenderingState", _prerenderingStateValue);
+ return Task.CompletedTask;
+ }
+
+ Task PersistEnhancedNavState()
+ {
+ PersistentComponentState.PersistAsJson("EnhancedNavState", _enhancedNavStateValue);
+ return Task.CompletedTask;
+ }
+
+ Task PersistCircuitPauseState()
+ {
+ PersistentComponentState.PersistAsJson("CircuitPauseState", _circuitPauseStateValue);
+ return Task.CompletedTask;
+ }
+
+ Task PersistCombinedFiltersState()
+ {
+ PersistentComponentState.PersistAsJson("CombinedFiltersState", _combinedFiltersStateValue);
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file