.NET 9 is shaping up ahead of its November 2024 release. Here are the highlights for Blazor so far.
.NET 8 was a big release for Blazor, introducing new interactive render modes and an option to build entire web apps using static server-side rendering. It was also an LTS (Long Term Support) release, meaning it’s fully supported by Microsoft until November 2026.
Now Microsoft is focused on delivering a smaller set of changes, tweaks and improvements for Blazor in the upcoming .NET 9 release, slated for November 2024. This will be a STS (Standard Term Support) release with support for 18 months post-launch.
Here are some of the key Blazor changes released so far (as of Preview 4).
One of the biggest changes in .NET 8 is the ability to run your app using different render modes.
You can opt to run your pages using static server-side rendering, where the component is rendered on the server and plain HTML returned to the browser.
Or you can opt for one of the interactive render modes (Server, WASM or Auto).
In .NET 8, it’s possible to configure your entire app to run in one mode, globally. For example, if you want your app to run using Interactive Server mode. But then you’re stuck with that interactive render mode for all pages/components in your app.
In .NET 9, it will be possible to configure a page component to run using static SSR, even within an app which is otherwise configured to run interactively.
It’s worth saying this is something you would want to avoid in most cases. It will force a full page reload, which is less efficient and responsive than the same component running interactively (where Blazor can be efficient about fetching the UI and patching it into the browser’s DOM).
But this will be useful when you absolutely need a specific page to run this way, for example if you need it to read/write HTTP cookies.
To understand how this works, consider this typical App.razor component:
<!DOCTYPE html>
<html>
<head>
...
<HeadOutlet @rendermode="InteractiveServer" />
</head>
<body>
<Routes @rendermode="InteractiveServer" />
...
</body>
</html>
When a user attempts to navigate to a specific page (e.g., “/counter”), this top-level component will be rendered. In turn it will render the Routes
component using the specified @rendermode
.
In this case that means all routing will be handled by our Routes
component which is running interactively using Blazor server.
To make this page in our app run using static server-side rendering, we need a way to force this one request (for our non-interactive page) to be handled by a router that is running “non-interactively” (using static SSR).
We can do that in .NET 9 via two steps:
The first step is to exclude the component from using Blazor’s interactive router.
PageToBeRenderedUsingSSR.razor
@attribute [ExcludeFromInteractiveRouting]
By decorating a page with the ExcludeFromInteractiveRouting
attribute, it will be excluded from interactive routing. However, this attribute by itself won’t do much.
Next we need to modify App.razor so the Routes
component will use static SSR as its render mode for this component.
Here’s an example, taken from the official docs:
<!DOCTYPE html>
<html>
<head>
...
<HeadOutlet @rendermode="@PageRenderMode" />
</head>
<body>
<Routes @rendermode="@PageRenderMode" />
...
</body>
</html>
@code {
[CascadingParameter]
private HttpContext HttpContext { get; set; } = default!;
private IComponentRenderMode? PageRenderMode
=> HttpContext.AcceptsInteractiveRouting() ? InteractiveServer : null;
}
This brings in a parameter for HttpContext
.
We’re then able to call the new HttpContext.AcceptsInteractiveRouting
method to check if the current request is for a page that accepts interactive routing (based on the ExcludeFromInteractiveRouting
attribute).
If so, it will return InteractiveServer
as the render mode; otherwise it returns null
. null
indicates that the component should be rendered using static SSR. Our Routes
component will then be rendered using the relevant render mode.
In .NET 9, your Interactive Server components will now enable compression for the underlying WebSocket connections by default.
This will result in reduced bandwidth usage (less data being transmitted to and received back from the server). As a result, you should see faster data transfer and lower latency when interacting with these components.
However, using compression with WebSocket connections increases the vulnerability of an app to side-channel attacks (where an attacker can attack the TLS encryption of the connection). If you want to dig into why that’s the case, check out the official docs here for interacting with these components).
To counter this increased security risk, these components will also specify a frame-ancestors
Content Security Policy (CSP) of self
.
CSPs are an an important security feature that specify which sources of content are allowed to be loaded and executed by a webpage. Here the frame-ancestors
CSP means the component can only be embedded in an iframe
with the same origin as the app serving the component with compression enabled.
This restriction prevents someone from embedding a page from your Blazor app in an iframe
on a different site, thereby reducing the chances of a successful side-channel attack.
Of course, if you want to disable this compression, you can:
.AddInteractiveServerRenderMode(o => o.ConfigureWebSocketOptions = null)
You can also tighten up security further by blocking all embedding in an iframe
.
.AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = "'none'")
There are a few places where you can put the code for your C# Razor components.
You can use a @code {}
block in the .razor file itself.
Greeting.razor
<h2>Hello @name</h2>
@code {
private string name = "Alice";
}
Alternatively you might opt to declare a separate class (in this case by using the partial
keyword).
Greeting.razor.cs
public partial class Greeting(){
private string name = "Alice";
}
With the latter approach, you’ve always been able to inject dependencies using property injection.
Say, for example, we want to inject and use NavigationManager
(to programmatically navigate to a different page).
Greeting.razor.cs
using Microsoft.AspNetCore.Components;
namespace BlazorExperiments.Components.Pages;
public partial class Greeting
{
private string name = "Alice";
[Inject] protected NavigationManager NavMan { get; set; }
private void NavigateHome()
{
NavMan.NavigateTo("/");
}
}
This injects an instance of NavigationManager
via the NavMan
property (then invokes it from the NavigateHome
method).
In the UI, we can call NavigateHome
to trigger the navigation.
Greeting.razor
@page "/greeting"
@rendermode InteractiveServer
<h2>Hello @name</h2>
<button @onclick="NavigateHome">Go Home</button>
The above example will work in .NET 8 (and earlier versions of .NET).
Now in .NET 9 there’s a new option to inject dependencies via constructor injection:
public partial class Greeting(NavigationManager navMan)
{
private string name = "Alice";
private void NavigateHome()
{
navMan.NavigateTo("/");
}
}
This uses C# 12’s primary constructors, but you can of course use a regular constructor here too.
You could be forgiven for not knowing about composition events (I didn’t!), but it turns out they’re important when handling international character input methods. Languages like Chinese, Japanese and Korean have massive sets of characters (thousands) that aren’t easily mapped to a standard keyboard layout.
For those languages, you can use an Input Method Editor (IME). An IME enables users to type sequences of keys that are then converted into the desired characters. This is a multi-step composition process, where the user starts the composition, selects various characters, then finalizes the input.
Composition events are fired when the IME starts a new composition session, updates the session with a new input and eventually finalizes the composition session.
To properly handle user input in these scenarios, you need to be able to distinguish between regular key events and those that are part of a composition session.
.NET 9 will introduce a KeyboardEventArgs.IsComposing
property to indicate if a keyboard event is part of a composition session.
It will then be possible to use that to trigger the appropriate logic for your specific components.
Finally, we have a new OverscanCount
parameter for the QuickGrid
component.
QuickGrid
is a handy way to quickly show data in your Blazor components.
It also supports virtualization, where you can load an initial amount of data and then, as the user scrolls down through the data, fetch more and load that into the existing HTML.
This results in a seemingly infinite scroll for the user, without blowing up your browser by attempting to load potentially thousands, or even millions, of rows of data all at once.
To reduce the number of times it has to render, QuickGrid will typically render a number of additional items before and after the visible region.
Say the user is viewing rows 20 to 30. QuickGrid will also render a number of rows before row 10, and after row 30.
The number of rows is determined by the OverscanCount
. If the OverscanCount
is set to 5, in our example QuickGrid would render rows 15 to 35. It doesn’t then need to render again until the user scrolls up to (or above) row 14 or down to (or past) row 36.
The downside of a large overscan count is that it can result in an increase in initial load times so the trick is to find a balance based on your data and optimal experience for the user.
So there we have it, a handful of small but useful and important enhancements for Blazor in .NET 9.
After the big changes in .NET 8, it’s perhaps no surprise that we’re seeing smaller, incremental improvements in .NET 9.
That said, there are some significant items still to come according to the .NET 9 roadmap, including:
For now, .NET 9 Preview 4 smooths out some of Blazor’s rougher edges.
From here, we can expect a few more preview releases, a couple of release candidates, and then .NET 9 will ship 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.