8000 [Blazor] Add ability to filter persistent component state callbacks based on persistence reason by Copilot · Pull Request #62394 · dotnet/aspnetcore · GitHub
[go: up one dir, main page]

Skip to content

[Blazor] Add ability to filter persistent component state callbacks based on persistence reason #62394

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

Copilot
Copy link
Contributor
@Copilot Copilot AI commented Jun 18, 2025

This PR implements the ability to filter persistent component state callbacks based on the reason for persistence, addressing scenarios where components need different persistence behavior for prerendering, enhanced navigation, and circuit pause events.

Changes

Core Interfaces and Classes

  • Added IPersistenceReason interface with PersistByDefault property
  • Added concrete persistence reason implementations:
    • PersistOnPrerendering (default: true)
    • PersistOnEnhancedNavigation (default: false)
    • PersistOnCircuitPause (default: true)
  • Added IPersistenceReasonFilter interface for filtering logic
  • Added generic PersistReasonFilter<TReason> base class

Filter Classes in Components.Web

  • PersistOnPrerenderingFilter - Controls persistence during prerendering
  • PersistOnEnhancedNavigationFilter - Controls persistence during enhanced navigation
  • PersistOnCircuitPauseFilter - Controls persistence during circuit pause

Infrastructure Updates

  • Enhanced PersistComponentStateRegistration to include reason filters
  • Added new RegisterOnPersisting overload accepting IReadOnlyList<IPersistenceReasonFilter>
  • Added new PersistStateAsync overload accepting IPersistenceReason
  • Updated calling code to pass appropriate persistence reasons:
    • RazorComponentEndpointInvoker uses PersistOnPrerendering
    • CircuitPersistenceManager uses PersistOnCircuitPause

Filtering Logic

The persistence manager now evaluates filters in order:

  1. If any filter returns a definitive result (true/false), that decision is used
  2. If no filters match the persistence reason, the reason's PersistByDefault value is used
  3. Maintains full backward compatibility - existing code works unchanged

Usage Example

// Register callback with filtering
var filters = new List<IPersistenceReasonFilter>
{
    new PersistOnPrerenderingFilter(persist: true),
    new PersistOnEnhancedNavigationFilter(persist: false),
    new PersistOnCircuitPauseFilter(persist: true)
};

PersistentState.RegisterOnPersisting(PersistData, renderMode, filters);

This allows fine-grained control over when component state should be persisted based on the specific persistence scenario.

Fixes #62393.


💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.

Copy link
Contributor

Greetings human! You've submitted a PR that modifies code that is shared with https://github.com/dotnet/runtime . Please make sure you synchronize this code with the changes in that repo!

@javiercn
Copy link
Member

Create concrete persistence reason implementations: PersistOnPrerendering, PersistOnEnhancedNavigation, PersistOnCircuitPause must live on the Components.Web assembly.

@javiercn
Copy link
Member

RazorComponentEndpointInvoker needs to pass in Prerendering as the reason during the initial rendering and EnhancedNavigation when the request comes from an enhanced navigation

Copilot AI and others added 2 commits June 18, 2025 14:07
Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
@Copilot Copilot AI changed the title [WIP] [Blazor] Provide the ability to filter persistent component state callbacks based on the reason for persistence [Blazor] Add ability to filter persistent component state callbacks based on persistence reason Jun 18, 2025
@Copilot Copilot AI requested a review from javiercn June 18, 2025 14:16
Copilot finished work on behalf of javiercn June 18, 2025 14:16
…RazorComponentEndpointInvoker to detect enhanced navigation

Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
Copy link
Contributor Author
Copilot AI commented Jun 18, 2025

Create concrete persistence reason implementations: PersistOnPrerendering, PersistOnEnhancedNavigation, PersistOnCircuitPause must live on the Components.Web assembly.

Moved the concrete persistence reason implementations (PersistOnPrerendering, PersistOnEnhancedNavigation, PersistOnCircuitPause) to the Components.Web assembly as requested. Updated all references and PublicAPI files accordingly. Created a DefaultPersistenceReason for internal fallback use in the Components assembly to avoid dependency issues. (f938ea4)

RazorComponentEndpointInvoker needs to pass in Prerendering as the reason during the initial rendering and EnhancedNavigation when the request comes from an enhanced navigation

Updated RazorComponentEndpointInvoker to detect enhanced navigation requests using the IsProgressivelyEnhancedNavigation method. Now passes PersistOnPrerendering for initial rendering and PersistOnEnhancedNavigation when the request comes from enhanced navigation, as determined by the "text/html; blazor-enhanced-nav=on" Accept header. (f938ea4)

Copilot finished work on behalf of javiercn June 18, 2025 14:45
@@ -7,6 +7,7 @@

<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Components" />
<Reference Include="Microsoft.AspNetCore.Components.Web" />
8000
Copy link
Member

Choose a reason for hiding this comment

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

Do not add this reference here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed the Microsoft.AspNetCore.Components.Web reference from the Components.Tests project. Created test-specific implementations of persistence reasons within the test file to maintain test functionality without the dependency. (4439a8c)

Comment on lines 189 to 192
internal Task<bool> TryPauseAsync(IPersistentComponentStateStore store)
=> TryPauseAsync(store, DefaultPersistenceReason.Instance);

internal Task<bool> TryPauseAsync(IPersistentComponentStateStore store, IPersistenceReason persistenceReason)
Copy link
Member

Choose a reason for hiding this comment

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

Do not create and overload. Simply change this method and adjust all the places it's used

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed the overload from ComponentStatePersistenceManager.PersistStateAsync and modified the existing method to accept an optional nullable IPersistenceReason parameter. Updated all calling sites to work with the new signature. (a2c9f1c)

Comment on lines 9 to 16
internal sealed class DefaultPersistenceReason : IPersistenceReason
{
public static readonly DefaultPersistenceReason Instance = new();

private DefaultPersistenceReason() { }

/// <inheritdoc />
public bool PersistByDefault => true;
Copy link
Member

Choose a reason for hiding this comment

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

Use null as a value in legacy overloads (and handle that within PauseAsync) instead of a "default" reason

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Replaced DefaultPersistenceReason with null handling in legacy overloads. The ComponentStatePersistenceManager now handles null persistence reasons with default behavior (persist by default). Removed the DefaultPersistenceReason.cs file entirely. (a2c9f1c)

/// <param name="renderMode"></param>
/// <param name="reasonFilters">Filters to control when the callback should be invoked based on the persistence reason.</param>
/// <returns>A subscription that can be used to unregister the callback when disposed.</returns>
public PersistingComponentStateSubscription RegisterOnPersisting(Func<Task> callback, IComponentRenderMode? renderMode, IReadOnlyList<IPersistenceReasonFilter>? reasonFilters)
Copy link
Member

Choose a reason for hiding this comment

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

IReadOnlyList? reasonFilters <= Make this mandatory

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Made the reasonFilters parameter mandatory in the RegisterOnPersisting overload that accepts filters. Updated the legacy overloads to pass Array.Empty<IPersistenceReasonFilter>() when no filters are provided. (a2c9f1c)

{
public Func<Task> Callback { get; } = callback;

public IComponentRenderMode? RenderMode { get; } = renderMode;

public IReadOnlyList<IPersistenceReasonFilter>? ReasonFilters { get; } = reasonFilters;
Copy link
Member

Choose a reason for hiding this comment

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

public IReadOnlyList<IPersistenceReasonFilter> ReasonFilters { get; } = reasonFilters ?? Array.Empty<IPersistenceReasonFilter>();

Copy link
Contributor Author