-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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
[Blazor] Initial support for persisting component state #60634
base: main
Are you sure you want to change the base?
Conversation
55fa34d
to
6e6bd2d
Compare
@@ -11777,6 +11790,8 @@ Global | |||
{01A75167-DF5A-AF38-8700-C3FBB2C2CFF5} = {225AEDCF-7162-4A86-AC74-06B84660B379} | |||
{E6D564C0-4CA5-411C-BF40-9802AF7900CB} = {01A75167-DF5A-AF38-8700-C3FBB2C2CFF5} | |||
{7899F5DD-AA7C-4561-BAC4-E2EC78B7D157} = {01A75167-DF5A-AF38-8700-C3FBB2C2CFF5} | |||
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {60D51C98-2CC0-40DF-B338-44154EFEE2FF} | |||
{E22DD5A6-06E2-490E-BD32-88D629FD6668} = {60D51C98-2CC0-40DF-B338-44154EFEE2FF} | |||
EndGlobalSection | |||
GlobalSection(ExtensibilityGlobals) = postSolution | |||
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just fixing the solution file that was broken.
public void SetPlatformRenderMode(IComponentRenderMode renderMode) | ||
{ | ||
if (_servicesRegistry == null) | ||
{ | ||
return; | ||
} | ||
else if (_servicesRegistry?.RenderMode != null) | ||
{ | ||
throw new InvalidOperationException("Render mode already set."); | ||
} | ||
|
||
_servicesRegistry!.RenderMode = renderMode; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Microsoft.AspNetCore.Components
doesn't have access to the common render modes (Server,Webassembly,Auto) so we need to give hosts a way to configure the render mode that is used for persisting the PersistentServicesRegistry
|
||
namespace Microsoft.AspNetCore.Components.Reflection; | ||
|
||
internal sealed class PropertyGetter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a copy and modification
of PropertySetter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_componentStateById.Add(componentId, componentState); | ||
_componentStateByComponent.Add(component, componentState); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this into a method that ComponentState
calls in its constructor to set this up, so that cascading value providers can access the RenderMode during the initial setup.
I'm adding more tests, but the current implementation should be working for all mainline scenarios |
This pull request includes several changes aimed at enhancing the functionality and robustness of the ASP.NET Core components. The most significant changes involve the addition of new projects to the solution, modifications to cascading parameter state management, and improvements to persistent component state handling. Solution Structure Updates:
Cascading Parameter State Enhancements:
Persistent Component State Improvements:
These changes collectively improve the modularity, state management, and persistence capabilities of the ASP.NET Core components, making the framework more robust and easier to extend. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Overview
This PR introduces initial support for persisting component state in Blazor by augmenting the existing persistence mechanism with a declarative model. Key changes include:
- New infrastructure for generating and caching unique keys for both component and service state.
- Extension methods and service registrations that integrate persistent state functionality for both components and services.
- Modifications to persistence state registration, restoration, and callback execution across the rendering and DI pipelines.
Reviewed Changes
File | Description |
---|---|
src/Components/Components/src/Reflection/PropertyGetter.cs | Introduces a generic property getter using dynamic code support. |
src/Components/Components/src/PersistentState/PersistentServiceTypeCache.cs | Adds a cache for resolving persistent service types. |
src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs | Implements service registration/restoration for persistent state. |
src/Components/Components/src/SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions.cs | Provides extension methods for persistent state-related service registration. |
src/Components/Components/src/SupplyParameterFromPersistentComponentStateValueProvider.cs | Adds a value provider to supply component parameters from persistent state. |
src/Components/Components/src/PersistentComponentState.cs | Updates the component state management to include persistence callbacks. |
src/Components/Components/src/RenderTree/Renderer.cs and others | Integrates persistent state behavior with component registration and cascading parameters. |
src/Components/Components/test/Lifetime/ComponentStatePersistenceManagerTest.cs | Updates tests to reflect the new persistent state service registration. |
src/Components/Components/src/ParameterView.cs | Adjusts cascading parameter lookup to incorporate a derived key. |
Copilot reviewed 30 out of 30 changed files in this pull request and generated 4 comments.
src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs
Show resolved
Hide resolved
src/Components/Components/src/PersistentState/PersistentServicesRegistry.cs
Outdated
Show resolved
Hide resolved
src/Components/Components/src/SupplyParameterFromPersistentComponentStateValueProvider.cs
Show resolved
Hide resolved
1c54a24
to
995d550
Compare
src/Components/Components/src/PersistentState/PersistentServiceTypeCache.cs
Show resolved
Hide resolved
|
||
private static bool IsSerializableKey(object key) => | ||
key is { } componentKey && componentKey.GetType() is Type type && | ||
(Type.GetTypeCode(type) != TypeCode.Object |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this pattern is showing up again.
// were iterating over it. | ||
// It is not allowed to register a callback while we are persisting the state, so we don't | ||
// need to worry about new callbacks being added to the list. | ||
for (var i = _registeredCallbacks.Count - 1; i >= 0; i--) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a test for this
995d550
to
997105b
Compare
997105b
to
245624e
Compare
_subscriptions[subscriber] = state.RegisterOnPersisting(() => | ||
{ | ||
var storageKey = ComputeKey(subscriber, propertyName); | ||
var property = subscriber.Component.GetType().GetProperty(propertyName)!.GetValue(subscriber.Component)!; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to use PropertyGetter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a separate cache for these properties too
Adds a declarative model for persistent component and services state
This PR augments the persistent component state feature with a declarative model that allows the developer to place an attribute on components and services properties to indicate that they should be persisted during prerendering so that it is accessible when the application becomes interactive.
Scenarios
Serializing state for a component
[SupplyParameterFromPersistentComponentState]
will be serialized and deserialized during prerendering.Serializing state for multiple components of the same type
ParentComponent.razor
ChildComponent.razor
[SupplyParameterFromPersistentComponentState]
will be serialized and deserialized during prerendering.@key
directive is used to ensure that the state is correctly associated with the component instance.Element
property is initialized in theOnInitialized
method to avoid null reference exceptions similarly to how we do it forquery parameters and form data.
Serializing state for a service
CounterService.cs
Program.cs
[SupplyParameterFromPersistentComponentState]
will be serialized during prerendering and deserialized when the application becomes interactive.AddPersistentService
method is used to register the service for persistence.RenderMode.Server
- The service will be available for interactive server mode.RenderMode.Webassembly
- The service will be available for interactive webassembly mode.RenderMode.InteractiveAuto
- The service will be available for both interactive server and webassembly modes if a component renders in any of those modes.[SupplyParameterFromPersistentComponentState]
will be deserialized.Implementation details
Key Computation
For components
We need to generate a unique key for each property that needs to be persisted. For components, this key is computed based on:
@key
directive if present and serializable (e.g.,Guid
,DateOnly
,TimeOnly
, and primitive types)The key computation ensures that even if multiple instances of the same component are present on the page (for example, in a loop), each instance's state can be uniquely identified and persisted.
The key computation algorithm only takes into account a small subset of a component hierarchy for performance reasons. This limits the ability to persist state on recursive component hierarchies. Our recommendation for those scenarios is to persist the state at the top level of the hierarchy.
It's also important to indicate that the imperative API is still available for more advanced scenarios, which offers more flexibility on how to handle the more complex cases.
For services
Only persisting scoped services is supported. We need to generate a unique key for each property that needs to be persisted. The key for services is derived from:
Properties to be serialized are identified from the actual service instance.
Serialization and Deserialization
By default properties are serialized using the System.Text.Json serializer with default settings. Note that this method is not trimmer safe and requires the user to ensure that the types used are preserved through some other means.
This is consistent with our usage of System.Text.Json across other areas of the product, like root component parameters or JSInterop.
We plan to add an extensibility point to control the serialization mechanism used in this scenario in a future change.