Improved error handling for lost Blazor Server connections takes the limelight as .NET 9 takes shape ahead of its November release.
Work on Blazor and .NET 9 continues ahead of its November 2024 release date.
After the sweeping changes in .NET 8, this release is on track to smooth off some of Blazor’s rougher edges. Here are the notable changes released in Previews 5 and 6.
You can download the latest (preview) version of .NET 9 here.
In .NET 8 you can drop a static file (for example, a CSS file) in your wwwroot folder and reference it in your component, like this:
App.razor
<head>
<link href="css/app.css" rel="stylesheet"/>
</head>
This works just fine, except for a couple of niggling issues.
Firstly, the file is served uncompressed by default. This means your users have to download a potentially large file (or files) when they first visit your app. The file will then be cached by your browser, but this introduces a new problem.
When you subsequently update the file (for example, by adding new styles to it if it’s a stylesheet), there’s no easy way to ensure your users get the latest version. If their browser has already cached the previous version, they’re stuck with it, even though an updated file is available.
There are various hacks you can use to work around this, including adding an arbitrary query string parameter when you reference the asset (which will trick the browser into assuming its a new file). But these hacks are manual and error-prone.
.NET 9 Preview delivers a new API for handling static web assets, called MapStaticAssets
.
This can typically replace the older UseStaticFiles
in your Program.cs file, and does a few things:
With MapStaticAssets
the compressed versions of your static assets will be significantly smaller than the originals (up to 93% smaller in some cases).
The ETag for an asset will be generated based on the file’s contents.
Plus, a unique filename will be generated, also based on the file’s contents, so the browser sees a new version as an entirely new file (which it needs to download, rather than attempting to serve from cache).
To reference a fingerprinted asset, you can use the Assets
property in your components.
<link rel="stylesheet" href="@Assets["site.css"]" />
Under the hood, this will resolve to the unique filename for the current version of that file (based on its contents).
Take this example, where we reference a number of stylesheets using the new @Assets
property.
<head>
<link href="@Assets["css/bootstrap/bootstrap.min.css"]" rel="stylesheet"/>
<link href="@Assets["css/app.css"]" rel="stylesheet"/>
<link href="@Assets["BlazorDemoApp.styles.css"]" rel="stylesheet"/>
</head>
Run this, inspect it in the browser and you’ll see unique filenames for each file:
<link href="css/bootstrap/bootstrap.min.bpk8xqwxhs.css" rel="stylesheet">
<link href="css/app.qbqo6dzkt5.css" rel="stylesheet">
<link href="BlazorDemoApp.jibil5dzda.styles.css" rel="stylesheet">
This, along with those correctly set ETags, will enable the browser to happily use the cached version of an asset until you publish a modified version, at which point the browser will automatically fetch (and cache) the new version.
If you’ve run a Blazor Server app in production, you’ve no doubt encountered the problem where your users lose connection to the server and are left staring at an ominous error message.
The problem comes because Blazor Server relies on being able to connect with a SignalR circuit on the server.
In previous versions of .NET, any interruption to this connection resulted in an error message, and the user being told to reload the app to continue.
In .NET 9, Blazor will now attempt to reconnect to the server using an exponential backoff strategy. This means it will try to quickly reconnect a few times before gradually increasing the delay between retries.
Alternatively, if a user navigates to an app with a disconnected circuit (say because they open an existing browser tab for your app) the reconnection attempt will happen immediately.
In the event that connection is reestablished to the server but the server has already released the SignalR circuit, the app will refresh automatically.
Combined, these changes make it much less likely that your users will be interrupted and forced to reload the entire site when intermittent network issues occur, or after they’ve been away for some time before heading back to your site.
To reflect these changes, the default reconnection UI is now more “friendly,” showing a “Rejoining the server…” message and progress indicator when reconnection is being attempted.
One of the biggest changes introduced in .NET 8 is the ability to run your component in one of several different render modes: Static server-side rendering, Interactive Server, Interactive WASM and Interactive Auto.
Beyond that, if you’re using pre-rendering, your component might be rendering on the server one minute, then in the browser the next (pre-rendered first, then rendered again in the browser if using Interactive WASM or Auto modes).
This presents a challenge when you have code you only want to run in the browser (not on the server, for example during pre-rendering) or when you need different code to run depending on which render mode is in play.
To address this, your components now have access to a RendererInfo
property which exposes two properties:
RendererInfo.Name
RendererInfo.IsInteractive
Name
will tell you where your component is currently running (Static
, Server
, WebAssembly
or, if you’re using .NET MAUI, WebView
).
IsInteractive
indicates whether the component is currently running interactively (not Static
).
That last one is useful to effectively detect if your component is currently pre-rendering, especially when combined with another new property: AssignedRenderMode
.
AssignedRenderMode
tells you which render mode your component will eventually run in.
For example, if your component is pre-rendering, AssignedRenderMode
will tell you the render mode your component will use after pre-rendering.
Combined, these flags give a useful indication of how your component is being rendered, enabling you to run code during different scenarios (for example, disabling inputs during pre-rendering when you know the component is about to be rendered interactively).
Here’s a component which shows off the new properties:
<h3>Render Info</h3>
<ul>
<li>Name: @RendererInfo.Name</li>
<li>Is Interactive: @RendererInfo.IsInteractive</li>
<li>Assigned Render Mode: @AssignedRenderMode</li>
</ul>
Here’s that component being rendered using .NET’s InteractiveWebAssembly
render mode.
<Info @rendermode="@InteractiveWebAssembly"/>
By default, Blazor pre-renders components, so on first load we see this:
Then, after a short wait, the component is rendered again, this time on the client:
In .NET 8, you can spin up a project with auth handled via Individual Accounts.
Here’s what happens when a user makes a request to your site with auth enabled.
Your app handles auth on the server.
Once the server has authenticated the user, it can issue cookies, tokens, etc. and create an AuthenticationState
object.
This AuthenticationState
object contains details of the user’s auth state including their name and role, and enables Blazor’s various auth components to work.
But what if you have components you want to run using WebAssembly?
To read that auth state in those components, you need a way to take the AuthenticationState
object (which exists on the server) and make it available to your component when it’s running via WebAssembly in the browser.
In the default project template, this is achieved via some boilerplate code which serializes the AuthenticationState
object, and sends it in the HTML being returned to the browser.
Blazor running on the client (browser) then reads and deserializes that embedded auth state.
But the boilerplate code to achieve this is quite verbose, and a lot of code for you to implement (or copy) yourself if you want to implement auth in a different project.
As of the latest .NET 9 Preview release, you can now automatically enable this serialization and deserialization of AuthenticationState
in your Program.cs files.
On the server:
builder.Services.AddRazorComponents()
.AddInteractiveWebAssemblyComponents()
.AddAuthenticationStateSerialization();
And on the client:
builder.Services.AddAuthorizationCore();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddAuthenticationStateDeserialization();
This achieves the same result (see diagram above) without including the serialization/deserialization code in your project.
The latest preview releases of .NET 9 include tweaks that make it easier to use the render modes introduced in .NET 8.
Improvements to static asset handling help your files to be served in a timely and performant manner.
The long-standing issue of abrupt disconnection of users from your Blazor Server apps finally gets some attention, with better handling of disconnections and improved UX.
Finally, some verbose boilerplate code is removed from your projects when sharing auth state between server and client.
.NET 9 launches in November 2024.
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.