Telerik blogs

.NET 10’s latest preview release brings a solution to the challenging issue of lost connections resulting in lost component state.

.NET 10 Preview 6 delivers some key improvements for Blazor. Here’s a rundown of the most notable changes (and one in particular which may well just solve Blazor Server’s longest running problem).

Blazor Server State Persistence

This is possibly the biggest news out of Preview 6 for Blazor (if you’re using Interactive Server mode). It is now possible to persist your component state, when using Blazor Server, in a way that means the user can get that state back, even if they lose connection to the server.

Before this release, if a connection was lost (for any reason, including because the user was “inactive” on your site for a given period of time), then Blazor would evict the circuit on the server (to manage resources) after a short grace period.

Specifically, it would hold the state for a time (usually a few minutes), but if the user still hadn’t been active in that time, it would evict the circuit and lose the state in the process.

As of .NET 9, Blazor attempted to auto-refresh the page (to try to start again with a new connection/circuit). But even if that succeeded, your users would lose their component state.

Which was probably very annoying if you were working on a form and decided to go for lunch, only to come back later and find the state of the form had been lost!

As of .NET 10 Preview 6, it’s now possible to have Blazor Server persist component state just before the circuit is evicted, and restore that state when the user becomes active again (at which point a new connection will be made).

From the user’s perspective, they can jump right back in and carry on where they left off.

It works automatically, and for state you deliberately declare as persistent state. The easiest way to do that is to use .NET 10’s handy new declarative attribute:

Counter.razor

@code {

    [SupplyParameterFromPersistentComponentState]
    int CurrentCount { get; set; } = 0;

    private void IncrementCount()
    {
        CurrentCount++;
    }

}

The [SupplyParameterFromPersistentComponentState] attribute (to be renamed to something less verbose imminently) tells Blazor to persist CurrentCount.

This is useful for two scenarios:

  1. Prerendering (enables the exact same state to be used for both prerendering and interactive rendering)
  2. To persist the state when the circuit is evicted

This second behavior is the new one in Preview 6 and will happen automatically.

With this one change, users can now click the button to increment the counter to a number (say 10), then take as long a lunch break as they wish. When they get back, the client will reconnect to the server, at which point a new connection will be made and a new circuit will be created on the server.

Thanks to persistent state, the new circuit will have the same state as the old (evicted) state, so the counter will still show the value 10.

Store in Memory or Hybrid Cache

Of course, you might be wondering where the state is persisted.

By default it will be in-memory, which does mean a server reboot (or new deployment) is likely to cause that state to be lost.

But if you configure your app to use Hybrid Cache, the component state will be persisted using that—which means it can be stored in both memory and on a distributed cache. This should make Blazor Server apps much more robust when you’re have multiple instances of your app running.

In the past, you may have used sticky sessions to verify requests from the client are sent to the same server (when hosting multiple instances of your app on the server). Sticky sessions are still valuable, but persistent component state when used with Hybrid Cache are another way to verify circuits can always retrieve the correct data, regardless of which server the end up running on.

APIs for Pausing and Resuming Circuits

Hand in hand with this new behavior comes the ability to manually trigger pausing and resuming circuits.

Say you decide to force the issue, and instead of waiting for Blazor Server’s default circuit “timeout” (when it will decide a circuit is inactive and can be evicted), you decide you just want to go ahead and pause a circuit yourself.

You can do that with a little bit of JavaScript, like this for example:

document.addEventListener('visibilitychange', ()=> {
    if(document.hidden) {
        Blazor.pause();
    } else {
        Blazor.resume();
    }
});

This responds to changes in visibility (the user switching tabs) but could just as easily by any JavaScript event.

The key lines are the calls to either pause or resume Blazor.

When you call pause, Blazor will go ahead and persist any state flagged up to be persisted. (Remember that handy declarative attribute we saw earlier.) When you call resume, it will restore any persisted state when it resumes the circuit.

In effect, this means you can optimize the performance of Blazor Server by taking control over the pausing (and evicting) of circuits, but still retrieve component state automatically when you subsequently attempt to resume that circuit.

Validation for Nested Objects

Say you have a model with nested objects, like this:

public class Order
{     
    [Required(ErrorMessage = "Customer information is required")]   
    public Customer Customer { get; set; } = new();
   
    [Required(ErrorMessage = "At least one order item is required")]
    [MinLength(1, ErrorMessage = "Order must contain at least one item")]
    [MaxLength(50, ErrorMessage = "Order cannot contain more than 50 items")]   
    public List<OrderItem> Items { get; set; } = new();
 
    public ShippingInfo? ShippingInfo { get; set; }

    [StringLength(500, ErrorMessage = "Notes cannot exceed 500 characters")]
    public string? Notes { get; set; }
}

Previously only the top-level properties would be validated when using the DataAnnotationsValidator for Blazor with your forms. There were workarounds, including using a separate NuGet package from Microsoft to make validation work for nested types (like the Shipping Info and Order Items here).

.NET 10 fixes this so your complex nested type validation work out of the box.

To opt in to the new validation system, you’ll need to add this line to Program.cs:

builder.Services.AddValidation();

With that, any form using <DataAnnotationsValidator /> will also validate your nested objects and collections.

Preloading for Blazor WebAssembly Assets

One of the challenges of using Blazor WebAssembly is the large initial download when a user first visits your site. There are a number of assets which must be downloaded for Blazor WASM to work (framework files, your compiled application code, etc.)

When loading assets like this, it’s possible to speed up the process by instructing the browser to preload those resources. So, instead of waiting for the page to load, checking and then downloading any linked resources one by one, the browser can start downloading those linked assets right away.

To make use of this preloading, you can include the new <LinkPreload /> component in your application’s head element.

<head>
    <!-- other code -->
    
    <LinkPreload /> 
</head>

That will now automatically change the preload behavior of assets.

Without that tag, your Blazor WASM application would load like this:

  1. Parse the HTML.
  2. Discover and download blazor.web.js.
  3. That script then discovers and downloads the .NET runtime files.
  4. Downloads your application assemblies.
  5. Finally start your app.

With the new <LinkPreload /> component, Steps 3-4 start immediately when the HTML loads, speeding up the loading of WASM and thereby making your app spring to life quicker from the user’s perspective.

Opt In to Disable Navigation Exceptions

There’s an issue when using static server-side rendering. If you try to navigate to a new route using NavigationManager.NavigateTo, you’ll notice that your code is throwing a NavigationException.

Under the hood, the framework then captures that exception to convert it into a redirect. However if you’re debugging, this is a suboptimal experience, as you’ll see the debugger break when that exception is thrown (even though the app behaves as expected).

There’s also a knock-on effect whereby any code after the NavigateTo call is executed in interactive render modes, but not in SSR scenarios.

.NET 10 has a mode where it won’t throw those exceptions, and can instead signal to the renderer that a redirection is requested.

This new behavior was introduced in a previous preview release. In Preview 6 it’s now “opt in” via an AppContext switch:

AppContext.SetSwitch("Microsoft.AspNetCore.Components.Endpoints.NavigationManager.DisableThrowNavigationException", isEnabled:true);

Passkey Support

Finally, if you create a new Blazor project using Individual Accounts for auth, you’ll notice a new feature. Users, once they’ve set up their account, can now add a passkey for their account.

This means they’ll be able to use services like Windows Hello to log in to your Blazor application (as opposed to logging in with social auth and/or a username/password every time).

In Summary

Blazor Server sees the biggest wins in this release, with a considerably improved experience for end users (no more losing state because they went for a lunch break).

Other areas see improvement too as .NET 10 nears its final preview release. (One more to go, then a couple of Release Candidates, at which point any new features should be in place and the focus will shift to quality/performance.)


Jon Hilton
About the Author

Jon Hilton

Jon spends his days building applications using Microsoft technologies (plus, whisper it quietly, a little bit of JavaScript) and his spare time helping developers level up their skills and knowledge via his blog, courses and books. He's especially passionate about enabling developers to build better web applications by mastering the tools available to them. Follow him on Twitter here.

Related Posts

Comments

Comments are disabled in preview mode.