Skip to content

API proposal for Declarative Model for Persistent Component and Services State #61138

Open
@javiercn

Description

@javiercn

Overview and Motivation

Currently, Blazor's persistent component state feature requires imperative API calls to store and retrieve state during prerendering. This proposal introduces a declarative model using attributes to simplify state persistence, allowing developers to annotate properties in components and services that should be persisted during prerendering and restored when the application becomes interactive.

Proposed API

namespace Microsoft.AspNetCore.Components
{
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    public class SupplyParameterFromPersistentComponentStateAttribute : Attribute
    {
        public SupplyParameterFromPersistentComponentStateAttribute() { }
    }
}

namespace Microsoft.AspNetCore.Components.Infrastructure
{
    public class ComponentStatePersistenceManager
    {
        public ComponentStatePersistenceManager(ILogger<ComponentStatePersistenceManager> logger, IServiceProvider serviceProvider) { }

        public void SetPlatformRenderMode(IComponentRenderMode renderMode) { }
    }

    public static class RegisterPersistentComponentStateServiceCollectionExtensions
    {
        public static IServiceCollection AddPersistentServiceRegistration<TService>(
            this IServiceCollection services, 
            IComponentRenderMode componentRenderMode) { }
    }
}

namespace Microsoft.Extensions.DependencyInjection
{
    public static class SupplyParameterFromPersistentComponentStateProviderServiceCollectionExtensions
    {
        public static IServiceCollection AddSupplyValueFromPersistentComponentStateProvider(
            this IServiceCollection services) { }
    }

    public static class RazorComponentsRazorComponentBuilderExtensions
    {
        public static IRazorComponentsBuilder RegisterPersistentService<TPersistentService>(
            this IRazorComponentsBuilder builder,
            IComponentRenderMode renderMode) { }
    }
}

Usage Examples

Component State Persistence

@page "/counter"

<h1>Counter</h1>
<p>Current count: @CurrentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    [SupplyParameterFromPersistentComponentState]
    private int CurrentCount { get; set; }

    private void IncrementCount()
    {
        CurrentCount++;
    }
}

Multiple Components of the Same Type

<!-- ParentComponent.razor -->
@page "/parent"

@foreach (var element in elements)
{
    <ChildComponent @key="element.Name" />
}

<!-- ChildComponent.razor -->
<div>
    <p>Current count: @Element.CurrentCount</p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</div>

@code {
    [SupplyParameterFromPersistentComponentState]
    public State Element { get; set; }

    private void IncrementCount()
    {
        Element.CurrentCount++;
    }

    protected override void OnInitialized()
    {
        Element ??= new State();
    }

    private class State
    {
        public int CurrentCount { get; set; }
    }
}

Service State Persistence

public class CounterService
{
    [SupplyParameterFromPersistentComponentState]
    public int CurrentCount { get; set; }

    public void IncrementCount()
    {
        CurrentCount++;
    }
}

builder.Services.AddPersistentService<CounterService>(RenderMode.InteractiveAuto);

Alternative Designs

The existing imperative API for state persistence offers more flexibility for complex scenarios, but is more verbose and error-prone. The declarative model provides a simpler approach for common scenarios while still allowing fallback to the imperative API when needed.

Risk Considerations

  1. Serialization Limitations: By default, System.Text.Json is used for serialization which is not trimmer-safe. Developers must ensure that types being serialized are preserved through other means.

  2. Key Computation Constraints: The key computation algorithm only considers a subset of the component hierarchy, limiting its use in deeply nested or recursive component hierarchies.

  3. Service Persistence Scope: Only scoped services are supported for persistence, and implementations must expose the same properties for consistent serialization.

  4. Type Compatibility: Developers must ensure that types marked for persistence can be properly serialized and deserialized by the default serializer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-ready-for-reviewAPI is ready for formal API review - https://github.com/dotnet/apireviewsarea-blazorIncludes: Blazor, Razor Components

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions