Skip to content
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

Declarative model to serialize state on prerendering with Blazor and restore it on the client #26794

Open
Eirenarch opened this issue Oct 11, 2020 · 13 comments
Assignees
Labels
affected-medium This issue impacts approximately half of our customers area-blazor Includes: Blazor, Razor Components enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-blazor-component-model Any feature that affects the component model for Blazor (Parameters, Rendering, Lifecycle, etc) feature-blazor-state-preservation Pillar: Complete Blazor Web Priority:1 Work that is critical for the release, but we could probably ship without severity-major This label is used by an internal tool

Comments

@Eirenarch
Copy link

Eirenarch commented Oct 11, 2020

Consider the following scenario which is quite common:
A page with a grid with a pager. The grid loads data with an AJAX call and while the data is being loaded displays a loader. It is desirable that the data is displayed prerendered when using Blazor prerendering. Currently the most straight-forward implementation of this scenario works like this

  • component lifecycle starts on the server
  • a call is made to the service to get the page of data
  • the data is prerendered and the component html is sent to the browser
  • the browser renders the html. The user sees the data
  • the component lifecycle starts on the client. The component makes a call to the service for a page of data. The data on the screen is replaced with a loader because according to the component on the client the data is null and a loader should be displayed while the call finishes.
  • the call finishes and the data appears again

To sum it up the user sees data - loader - data. In addition two calls are made to get the data. The first issue can be mitigated by additional flags checking if we're in a first load situation on the client but it complicates the code significantly and it won't solve the second issue.

For me the additional complexity introduced by this issue was enough to turn off prerendering in my Blazor app. Maybe there is an easy workaround that I am not aware of but when I bring this issue up nobody has suggested a solution yet. The docs also seem to advise that we use caching but I don't consider this good enough as it will not remove the issue with the additional calls in Blazor wasm scenarios and still results in additional complexity.

Possible Solution

One solution I can think of is to mark certain component state with attributes and have that state be serialized while prerendering maybe as JSON and dump it into a hidden element on the page. Then when Blazor starts on the client it can read that state and initialize the component with it. This will avoid the second call for data and will simplify the code in the component.

Note that this issue is present with Blazor Server as well but with Blazor Server different approaches can be used like storing the whole component in memory and passing it upon the instantiation of the SignalR connection

@jotosmurf
Copy link

I was also put off by having to cache and write hacky code to avoid the FOOC effect when using prerendering. Indeed, even when the user does not see a flash, Blazor wasm still does unnecessary work as the actual data is being rendered twice.

Referencing @jonhilt here because he demonstrated the "double rendering" in this scenario in a blog post two days ago: scroll down to OnInitializedAsync is called twice.

@mkArtakMSFT
Copy link
Member

Thanks for contacting us.
This is potentially related to #26802
@javiercn is there another issue which we can dupe against?

@mkArtakMSFT mkArtakMSFT added the enhancement This issue represents an ask for new feature or an enhancement to an existing one label Oct 12, 2020
@mkArtakMSFT mkArtakMSFT added this to the Backlog milestone Oct 12, 2020
@ghost
Copy link

ghost commented Oct 12, 2020

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@Eirenarch
Copy link
Author

@mkArtakMSFT I don't know how React works and the issue doesn't explain the details but looking at the docs for React's hydrate() it seems like it is used to attach events not to restore state from the DOM

@SteveSandersonMS SteveSandersonMS added affected-medium This issue impacts approximately half of our customers severity-major This label is used by an internal tool labels Oct 12, 2020 — with ASP.NET Core Issue Ranking
@jotosmurf
Copy link

The concept needed here is called SSR client side hydradation. Examples in js SSR land explained in the first paragraph of the links below:

Issue #26802 should probably be deleted.

@Eirenarch
Copy link
Author

@jotosmurf the links you provided talk about attaching events to existing HTML which is fine and great but how is the state of the component restored? If I have a List<Customer> property in my component how will this list be transferred from the server to the client?

@mifopen
Copy link

mifopen commented Feb 21, 2021

@mkArtakMSFT @javiercn This issue duplicates #26802 in terms of what effect both authors want to achieve.
Let's remove #26802 from the backlog in favor of this issue?
Also, any chance to see a solution in net6? It would be awesome to have SPA framework with SSR done right ;) (as far as I know no JS framework offer this feature for now, or, at least, without some hacks)

@Eirenarch
Copy link
Author

It is great that we already have a solution for this in the latest preview but the solution seems too imperative and "manual". While having this as an option is fine I hope there will be a declarative and more transparent solution. I imagine something like marking a component with an attribute like [PreservesState] and then all the state preserving is done automatically and if the user does not want some field to be preserved they mark it with [NotPreserved] (might be good to exclude members marked with [Parameter]). Maybe even consider totally skipping the initialize event if the state has been restored declaratively.

@danroth27 danroth27 changed the title Serialize state on prerendering with Blazor and restore it on the client Declarative model to serialize state on prerendering with Blazor and restore it on the client Mar 12, 2021
@danroth27
Copy link
Member

@javiercn Feedback from .NET 6 Preview 2 on state persistence.

@javiercn javiercn added the feature-blazor-component-model Any feature that affects the component model for Blazor (Parameters, Rendering, Lifecycle, etc) label Apr 20, 2021
@ghost
Copy link

ghost commented Oct 28, 2021

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@ghost
Copy link

ghost commented Dec 19, 2023

Thanks for contacting us.

We're moving this issue to the .NET 9 Planning milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues.
To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

@mkArtakMSFT mkArtakMSFT added the Priority:1 Work that is critical for the release, but we could probably ship without label Jan 17, 2024
@mkArtakMSFT mkArtakMSFT modified the milestones: 9.0-preview5, 9.0-preview6 May 1, 2024
@javiercn
Copy link
Member

javiercn commented Jun 5, 2024

Declarative support for persisted component state

Goals

Enable a way for state to be persisted declaratively as opposed to imperatively, as to make the feature more discoverable and easier to use.

Challenges and constraints

The main challenge that we have is that we essentially emit a dictionary to the output to support persisting the state, and each key needs to be unique. The imperative API allows you to "figure this out" by yourself to a degree, but a declarative API can't easily do so in some scenarios.

Our goal is to generate a unique key for each usage of the feature. That mapping needs to be preserved when the app restarts interactively.

There are also a couple scenarios we need to account for.

  • Components
  • Services

Generating a key for components.

  • We can use the type name + property name to generate a unique key for a component that can be used to retrieve any persisted state.
    • The challenge for this is to support multiple components instances, the answer for now is to not support this scenario.
    • The alternative a not supported multiple components only requires the developer to save the state at a higher level on the hierarchy and
      pass that state to the components.
    • We might choose to leverage the @key attribute if available and serializable as a primitive type.

Generating a key for services

  • This is straightforward as we can use the typename as a unique key to save all the state for the service.

API

This will be a new [SupplyParameterFromPersistentComponentState] attribute that when applied to a property will register it for persistence.

In addition to this, there will also be a [PersistState] attribute for services that they can apply to properties to ensure that they get persisted, with an additional Mode property to specify where things need to be persisted.

Once we enable persisted component state for enhanced navs, we'll need a UpdateOnEnhancedNavigation parameter to determine if the component wants to receive updates.

Implementation details

This gets integrated as any other cascading value. When a component is rendered and contains the attributes, we register the persistence callback, when it gets removed from the dom we unregister the callback. We only register callbacks on the SSR, on wasm/server we don't.

@Eirenarch
Copy link
Author

Generating a key for components.

  • We can use the type name + property name to generate a unique key for a component that can be used to retrieve any persisted state.

    • The challenge for this is to support multiple components instances, the answer for now is to not support this scenario.
    • The alternative a not supported multiple components only requires the developer to save the state at a higher level on the hierarchy and
      pass that state to the components.
    • We might choose to leverage the @key attribute if available and serializable as a primitive type.

Is it possible to use the approach from web forms where the key is the ids of each of the parent components back to the root? I realize that Blazor components don't have explicit ID, but one could be generated in the background

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
affected-medium This issue impacts approximately half of our customers area-blazor Includes: Blazor, Razor Components enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-blazor-component-model Any feature that affects the component model for Blazor (Parameters, Rendering, Lifecycle, etc) feature-blazor-state-preservation Pillar: Complete Blazor Web Priority:1 Work that is critical for the release, but we could probably ship without severity-major This label is used by an internal tool
Projects
None yet
Development

No branches or pull requests

7 participants