Skip to content

Declarative state persistence base case failure #61456

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

Open
1 task done
guardrex opened this issue Apr 11, 2025 · 8 comments
Open
1 task done

Declarative state persistence base case failure #61456

guardrex opened this issue Apr 11, 2025 · 8 comments
Labels
area-blazor Includes: Blazor, Razor Components
Milestone

Comments

@guardrex
Copy link
Contributor

guardrex commented Apr 11, 2025

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Javier ...

Working on validating/updating the code demos in articles for the new persistent component state model and hit a gremlin 😈.

Expected Behavior

No exception and retain the generated value in a counter component that uses the new model.

Steps To Reproduce

Akin to what you show in the OP of the PR at #60634 (but I made the property public), BUT this demo should set an initial value on initialization to demo how a value created on the server can be held for final rendering.

This issue is really just asking how to approach this scenario more so than a bug report. I don't think it's a bug, of course! 😄

Vanilla BWA 10.0 Preview 3 with global Interactive Server rendering.

Counter2.razor:

@page "/counter-2"
@inject ILogger<Counter2> Logger

<PageTitle>Prerendered Counter 2</PageTitle>

<h1>Prerendered Counter 2</h1>

<p role="status">Current count: @CurrentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

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

    protected override void OnInitialized()
    {
        CurrentCount ??= Random.Shared.Next(100);
        Logger.LogInformation("CurrentCount set to {Count}", CurrentCount);
    }

    private void IncrementCount() => CurrentCount++;
}

Exceptions (if any)

Output ...

info: BlazorSample.Components.Pages.Counter2[0]
    CurrentCount set to 77
fail: Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager[1000]
      There was an error executing a callback while pausing the application.
      System.ArgumentException: Cannot bind to the target method because its signature is not compatible with that of the delegate type.
         at System.Reflection.RuntimeMethodInfo.CreateDelegateInternal(Type delegateType, Object firstArgument, DelegateBindingFlags bindingFlags)
         at Microsoft.AspNetCore.Components.Reflection.PropertyGetter..ctor(Type targetType, PropertyInfo property)
         at Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateValueProvider.PropertyGetterFactory(ValueTuple`2 key)
         at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
         at Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateValueProvider.ResolvePropertyGetter(Type type, String propertyName)
         at Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateValueProvider.<>c__DisplayClass11_0.<Subscribe>b__0()
         at Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager.<TryPauseAsync>g__TryExecuteCallback|18_0(Func`1 callback, ILogger`1 logger)
info: BlazorSample.Components.Pages.Counter2[0]
    CurrentCount set to 43

.NET Version

10.0.100-preview.3.25201.16

Anything else?

No response

@ghost ghost added the area-blazor Includes: Blazor, Razor Components label Apr 11, 2025
@soundaranbu
Copy link

You beat me to it. I was about to click submit on the same issue that I encountered after trying Preview 3. I also commented about similar issue I faced on the other day after trying the daily bits #26794 (comment)

@guardrex
Copy link
Contributor Author

guardrex commented Apr 11, 2025

Indeed, @soundaranbu! Actually, the docs that I put up yesterday for the use of this are currently wrong 😢, but I only had the PR to go on. We (the whole docs team) even thought that release was next Tuesday, so we thought we had more time to iron bugs out of things. I'll have the guidance fixed by EOD.

However, a Weather component approach akin to the counter example that I showed does work ...

[SupplyParameterFromPersistentComponentState]
public WeatherForecast[]? Forecasts { get; set; }

protected override async Task OnInitializedAsync()
{
    // Simulate asynchronous loading to demonstrate a loading indicator
    await Task.Delay(500);

    var startDate = DateOnly.FromDateTime(DateTime.Now);
    var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };

    Forecasts ??= Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = startDate.AddDays(index),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = summaries[Random.Shared.Next(summaries.Length)]
    }).ToArray();
}
      XXXXXXXXXXX Logger: Executing OnInitializedAsync! 4/11/2025 1:12:44 PM
info: BWA10P3TestDeclarativePersistStateDELETEME.Components.Pages.Weather[0]
      4/12/2025 - Bracing - 7 - 44
info: BWA10P3TestDeclarativePersistStateDELETEME.Components.Pages.Weather[0]
      4/13/2025 - Balmy - 5 - 40
info: BWA10P3TestDeclarativePersistStateDELETEME.Components.Pages.Weather[0]
      4/14/2025 - Chilly - -13 - 9
info: BWA10P3TestDeclarativePersistStateDELETEME.Components.Pages.Weather[0]
      4/15/2025 - Cool - 39 - 102
info: BWA10P3TestDeclarativePersistStateDELETEME.Components.Pages.Weather[0]
      4/16/2025 - Bracing - 44 - 111
info: BWA10P3TestDeclarativePersistStateDELETEME.Components.Pages.Weather[0]
      XXXXXXXXXXX Logger: Executing OnInitializedAsync! 4/11/2025 1:12:45 PM
info: BWA10P3TestDeclarativePersistStateDELETEME.Components.Pages.Weather[0]
      4/12/2025 - Bracing - 7 - 44
info: BWA10P3TestDeclarativePersistStateDELETEME.Components.Pages.Weather[0]
      4/13/2025 - Balmy - 5 - 40
info: BWA10P3TestDeclarativePersistStateDELETEME.Components.Pages.Weather[0]
      4/14/2025 - Chilly - -13 - 9
info: BWA10P3TestDeclarativePersistStateDELETEME.Components.Pages.Weather[0]
      4/15/2025 - Cool - 39 - 102
info: BWA10P3TestDeclarativePersistStateDELETEME.Components.Pages.Weather[0]
      4/16/2025 - Bracing - 44 - 111

@javiercn
Copy link
Member

BTW ... I'm also not sure if we're supposed to account for the restoration of a persisted value during initialization ...

if (CurrentCount == 0)
{
CurrentCount = Random.Shared.Next(100);
}
I'm getting the impression that if the dev wants to set an initial value that they must adopt the direct use of the > PersistentComponentState service and not use the [SupplyParameterFromPersistentComponentState] attribute.

The way to set an initial value is through null coalescing within OnInitialized(Async)

Property ??= default

@javiercn
Copy link
Member

@guardrex can you push the repro to a GH repo so that I can read through the details?

@guardrex

This comment has been minimized.

@guardrex
Copy link
Contributor Author

guardrex commented Apr 11, 2025

Placed it here ...

https://github.com/guardrex/DeclarativeStateTesting

The Counter2 component is the one throwing and not restoring state 😈.

UPDATE (5/12): I modified the Counter2 component to make ...

  • The counter property a nullable int.
  • The OnInitialized method use null-coalescing.

@guardrex
Copy link
Contributor Author

Good news BTW ...

I checked all of the guidance that I placed yesterday; and except for a few minor NITs here and there, all of the other examples of the approach are correct.

This counter component problem/exception is the only actually broken 😈 example.

@guardrex
Copy link
Contributor Author

guardrex commented Apr 11, 2025

One more note ...

For demo purposes in the article, I need to show the assignment and the restoration, if possible. I was hoping something like this would work ...

if (CurrentCount is null)
{
    CurrentCount = Random.Shared.Next(100);
    Logger.LogInformation("CurrentCount set to {Count}", CurrentCount);
}
else
{
    Logger.LogInformation("CurrentCount restored to {Count}", CurrentCount);
}

... but since the approach is breaking, I just get the first block of the conditional called twice ...

info: BWA10P3TestDeclarativePersistStateDELETEME.Components.Pages.Counter2[0]
      CurrentCount set to 69
fail: Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager[1000]
      There was an error executing a callback while pausing the application.
      System.ArgumentException: Cannot bind to the target method because its signature is not compatible with that of the delegate type.
         at System.Reflection.RuntimeMethodInfo.CreateDelegateInternal(Type delegateType, Object firstArgument, DelegateBindingFlags bindingFlags)
         at Microsoft.AspNetCore.Components.Reflection.PropertyGetter..ctor(Type targetType, PropertyInfo property)
         at Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateValueProvider.PropertyGetterFactory(ValueTuple`2 key)
         at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
         at Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateValueProvider.ResolvePropertyGetter(Type type, String propertyName)
         at Microsoft.AspNetCore.Components.SupplyParameterFromPersistentComponentStateValueProvider.<>c__DisplayClass11_0.<Subscribe>b__0()
         at Microsoft.AspNetCore.Components.Infrastructure.ComponentStatePersistenceManager.<TryPauseAsync>g__TryExecuteCallback|18_0(Func`1 callback, ILogger`1 logger)
info: BWA10P3TestDeclarativePersistStateDELETEME.Components.Pages.Counter2[0]
      CurrentCount set to 97

decimal and double do the same thing.

Works well with a class, so I'll use the following in the article so that we don't have a broken example published ...

@page "/counter-3"
@inject ILogger<Counter3> Logger

<PageTitle>Prerendered Counter 3</PageTitle>

<h1>Prerendered Counter 3</h1>

<p role="status">Current count: @State?.CurrentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

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

    protected override void OnInitialized()
    {
        if (State is null)
        {
            State = new() { CurrentCount = Random.Shared.Next(100) };
            Logger.LogInformation("CurrentCount set to {Count}", State.CurrentCount);
        }
        else
        {
            Logger.LogInformation("CurrentCount restored to {Count}", State.CurrentCount);
        }
    }

    private void IncrementCount()
    {
        if (State is not null)
        {
            State.CurrentCount++;
        }
    }

    public class CounterState
    {
        public int CurrentCount { get; set; }
    }
}
info: BWA10P3TestDeclarativePersistStateDELETEME.Components.Pages.Counter3[0]
      CurrentCount set to 60
info: BWA10P3TestDeclarativePersistStateDELETEME.Components.Pages.Counter3[0]
      CurrentCount restored to 60

@javiercn javiercn added this to the 10.0-preview5 milestone Apr 15, 2025
@guardrex guardrex changed the title Declarative state persistence base case failure [Pre5] Declarative state persistence base case failure May 12, 2025
@guardrex guardrex changed the title [Pre5] Declarative state persistence base case failure Declarative state persistence base case failure May 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

No branches or pull requests

3 participants