This repository demonstrates the usage of the new
[SupplyParameterFromPersistentComponentState]
attribute added in .NET 10 Preview.
Note: The .NET team has mentioned that this attribute should be given a shorter name for the final release of .NET 10.
When building a Blazor Web App, pages are prerendered by default before interactivity is established. This means that Blazor initializes components rendered on a page server-side to deliver initial HTML to the browser while the components become interactive.
As a result, the OnInitialized
or OnParametersSet
methods are called twice for
prerendered components: once during prerendering and again when they are created in an
interactive render mode. This can result in fetching data twice and a UI "flash"
when the prerendered data is lost, since state is not maintained by default when a
component changes render mode.
Persistent Component State allows Blazor to serialize a component's state into the prerendered HTML so that the same data from pre-rendering can be reused when the component becomes interactive.
A typical example of fetching data without persisting the state can be found in
ProductView.razor
and viewed in the application on the home page or at /shop/none
.
Persistent component state can be achieved by injecting the PersistentComponentState
service and registering a callback to persist state used by the component. An example
can be found in PersistentProductView.razor.
[Inject] PersistentComponentState PersistentState { get; set; }
List<Product>? products;
List<Category>? categories;
string productsKey = $"{nameof(PersistentProductView)}-{nameof(products)}";
string categoriesKey = $"{nameof(PersistentProductView)}-{nameof(categories)}";
PersistingComponentStateSubscription persistentStateSubscription;
// method to explicitly persist state
private Task PersistComponentState()
{
PersistentState.PersistAsJson(productsKey, products);
PersistentState.PersistAsJson(categoriesKey, categories);
return Task.CompletedTask;
}
// method to explicitly restore state
private void RestoreComponentState()
{
if (PersistentState.TryTakeFromJson(productsKey, out List<Product>? persistedProducts))
{
products = persistedProducts;
}
if (PersistentState.TryTakeFromJson(categoriesKey, out List<Category>? persistedCategories))
{
categories = persistedCategories;
}
}
// required to dispose of registered callback.
// Components must explicitly implement IDisposable
public void Dispose()
{
persistentStateSubscription.Dispose();
}
protected override async Task OnInitializedAsync()
{
// attempt to restore state before fetching data
RestoreComponentState();
products ??= await GalacticRelicsService.GetProductsAsync();
categories ??= await GalacticRelicsService.GetCategoriesAsync();
// register callback to persist data
persistentStateSubscription = PersistentState.RegisterOnPersisting(PersistComponentState);
}
In .NET 10, the [SupplyParameterFromPersistentComponentState]
attribute was introduced
to automatically persist component state without the ceremony involved in the previous
example. An example can be found in
PersistentAttributeProductView.razor.
[SupplyParameterFromPersistentComponentState]
public List<Product>? Products { get; set; }
[SupplyParameterFromPersistentComponentState]
public List<Category>? Categories { get; set; }
protected override async Task OnInitializedAsync()
{
// if state was persisted, these values will not be null
Products ??= await GalacticRelicsService.GetProductsAsync();
Categories ??= await GalacticRelicsService.GetCategoriesAsync();
}
- .NET 10 Preview 5 SDK or newer
- IDE with .NET 10 support, e.g. Visual Studio 2022 Preview or VS Code with C# Extension preview installed
- Clone this repo and open the solution in your IDE
- Run the app from your IDE or use
dotnet run --project PersistentStateDemo -lp https
- View the site at https://localhost:7113
- https://localhost:7113/shop/none - The shop page with no persistent state
- https://localhost:7113/shop/manual - The shop page with manual state persistence
- https://localhost:7113/shop/attribute - The shop page with attribute state persistence
The shop pages include a RenderModeIndicator
component displayed in the top right corner of the page to easily see whether the page
is displayed from static rendering (prerendered) or InteractiveWebAssembly
. On the home
page or /shop/none
, you will notice a UI flash when the render mode changes and the
products must be re-fetched from the API. In the other pages, there should be no flash
when the render mode changes because the state has been persisted.
This can be seen by setting a breakpoint in your debugger in the OnInitializedAsync
method
of each product view component. This method is called twice - once during prerendering and
again during interactive rendering - and the values in products
and categories
can be
inspected each time. In all cases, these values will be null
on the first execution of
the method. In the second execution, the components which persist component state will
have values for products
and categories
before they are fetched by the
GalacticRelicsService
.