Blazor updates for .NET 6 using Visual Studio 2022
Published: Thursday 16 September 2021
Blazor is having some significant updates in .NET 6, and we will have a look at four new features which are available to try out in Visual Studio 2022.
At present, .NET 6 is in preview mode and Visual Studio 2022 preview is the only way to use it. Once .NET 6 is released in November 2021, we will be able to use Blazor's .NET 6 changes by downloading Visual Studio 2022 as normal.
C# coding challenges
The application that we are using to demonstrate these updates comes from the application we used in our "How to use the button onclick event in Blazor WebAssembly" article.
Here are some of the Blazor updates and new features for .NET 6 that we can explore right now!
Update #1: Preserving prerendering state
Prerendering was one of the new features for Blazor in .NET 5. This allows a Blazor WebAssembly application to be loaded on the server before being present to the client.
The way it works is that we would have two applications. The host application will render the Blazor WebAssembly application to the server. Then, it will present it to the client.
This is different to how Blazor WebAssembly normally works where the client directly renders the application from the server.
The best way of demonstrating this is to start a Blazor WebAssembly application and view the source code. As we can see from the image below, no text inside the application appears in the source code.
If we wish to have SEO support for our Blazor Wasm application, this can present a problem. This is because a search crawler would have to render the WebAssembly part of our application to crawl the application's content.
Using prerendering allows us to render the application from the host, ensuring that an SEO bot is able to crawl the application's content, and make it search engine friendly.
The problem with .NET 5
However, this posed a problem in .NET 5. We were unable to store any data we had prerendered from the host and transfer it to the client. That means that we would have to load in the same data with the host and the client.
This is far from ideal, particularly if we are making an API call. We would have to make this API call twice.
However, .NET 6 has fixed this through the use of PersistentComponentState
.
With our host application, we need to add a <persist-component-state />
tag to our _Host.cshtml
file.
<!-- Host.cshtml -->
@page
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = "_Layout";
}
<component type="typeof(RoundTheCode.BlazorDotNet6.Wasm.App)" render-mode="WebAssemblyPrerendered" />
<script src="_framework/blazor.webassembly.js"></script>
<persist-component-state />
From there, we can inject our PersistentComponentState
instance into our Razor component.
<!-- NoteListingComponent.razor -->
@using RoundTheCode.BlazorDotNet6.Wasm.Models
@using System.Diagnostics
@inject PersistentComponentState ApplicationState
Now, our Blazor Wasm application is a note listing application where we can add and store notes.
When the application is initialised, we have added two default notes.
When we override the OnInitializedAsync
method, we add a new event handler to the OnPersisting
event handler. This stores our data as JSON. In this instance, it's our default notes that are being stored.
When the client calls the OnInitializedAsync
method, it will use the data stored in the application state and will mean that we don't have to create the default notes again.
<!-- NoteListingComponent.razor -->
@using RoundTheCode.BlazorDotNet6.Wasm.Models
@using System.Diagnostics
@implements IDisposable
@inject PersistentComponentState ApplicationState
@page "/"
...
@code {
...
protected override async Task OnInitializedAsync()
{
ApplicationState.RegisterOnPersisting(() => StoreNotes());
if (ApplicationState.TryTakeFromJson<Note[]>("Notes", out var storedNotes))
{
Notes = storedNotes.ToList();
}
else
{
Notes = new List<Note>();
Notes.Add(new Note("Hello there", DateTime.UtcNow));
Notes.Add(new Note("How are you doing?", DateTime.UtcNow));
}
await base.OnInitializedAsync();
}
...
private Task StoreNotes()
{
ApplicationState.PersistAsJson("Notes", Notes);
return Task.CompletedTask;
}
void IDisposable.Dispose()
{
}
}
Update #2: Querystring component parameters
With Blazor in .NET 6, there is a nice simple change where a parameter can be supplied from a querystring parameter.
For this, we have to create a new property, make sure that we use the [Parameter]
attribute, and then add a [SupplyParameterFromQuery]
attribute.
By default, Blazor will match up the querystring parameter name with the property name. However, we can supply a Name parameter in the [SupplyParameterFromQuery]
attribute if we wish to override this.
For this example, if we were to supply a Message
querystring parameter in the URL, it would be stored in the CustomMessage
property. This is because the CustomMessage
property has a [SupplyParameterFromQuery]
attribute, with the Name
parameter set as "Message"
.
<!-- NoteListingComponent.razor -->
@using RoundTheCode.BlazorDotNet6.Wasm.Models
@using System.Diagnostics
@implements IDisposable
@inject ComponentApplicationState ApplicationState
@page "/"
@if(!string.IsNullOrWhiteSpace(CustomMessage))
{
<p>My Custom Message: @CustomMessage</p>
}
...
@code {
...
[Parameter]
[SupplyParameterFromQuery(Name = "Message")]
public string CustomMessage { get; set; }
...
}
Update #3: Modify page title from a Razor component
With Blazor in .NET 6, we can add a page title into a Razor component.
Before that, we need to remove the <title>
tag from our layout, and replace it with a component. For the component, we will pass in a type of HeadOutlet
.
As we are prerendering our Blazor WebAssembly application, we need to include the render mode as WebAssemblyPrerendered
.
<!-- Host.cshtml -->
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using Microsoft.AspNetCore.Components.Web;
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<base href="/" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="RoundTheCode.BlazorOnClick.styles.css" rel="stylesheet" />
<component type="typeof(HeadOutlet)" render-mode="WebAssemblyPrerendered" />
</head>
<body>
@RenderBody()
</body>
</html>
From there, we can use the <PageTitle>
tag to insert a dynamic page title into our note listing component.
In this instance, we are going to display the number of notes added as the page title. When a note is added, the application will automatically update the page title.
<!-- NoteListingComponent.razor -->
...
@page "/"
<PageTitle>Viewing @((Notes?.Any() ?? false) ? Notes.Count() : 0) notes</PageTitle>
...
Update #4: Error boundaries
Our final update is looking at error boundaries.
This is where we can personalise an error message around a particular element in a Razor component.
For this example, we are going to throw a custom exception if one of our notes begins with the letter "a".
In our note view component, we throw this exception when the Razor component is initialised.
<!-- NoteViewComponent.razor -->
@using RoundTheCode.BlazorDotNet6.Wasm.Models
@if (Note != null)
{
<li class="@ClassName" @onmouseover="@OnMouseOver" @onmouseout="@OnMouseOut">
<span>@Note.Message</span>
<span>Created: @Note.Created.ToUniversalTime().ToString("ddd d MMM yyyy HH:mm:ss")</span>
<button type="submit" @onclick="OnDeleteNote">Delete</button>
</li>
}
@code {
...
protected override void OnInitialized()
{
if (Note?.Message.StartsWith("a") ?? false) {
throw new Exception("Note message shouldn't start with a.");
}
}
}
So how do we allow to show an error message for that particular note?
Using the <ErrorBoundary>
tag allows us to do that. Inside this tag, we can call the <ChildContent>
tag. Assuming no exceptions are called, the content inside the <ChildContent>
tag will be rendered to the application.
However, if there is an exception, it will render the content that is stored inside the <ErrorContent>
tag. With the <ErrorContent>
tag, we can supply a Context
attribute which has the exception instance.
The example below shows us using the <ErrorBoundary>
tag every time the NoteViewComponent
Razor component is called.
<!-- NoteListingComponent.razor -->
@page "/"
...
<div class="col-6">
<h2>Your saved notes</h2>
@if (Notes?.Any() ?? false)
{
<ul>
@foreach (var note in Notes)
{
<ErrorBoundary>
<ChildContent>
<NoteViewComponent Note="@note" OnDeleteNote="@((e) => OnDeleteNote(e, note))"></NoteViewComponent>
</ChildContent>
<ErrorContent Context="ex">
<li>Message has the following error: @ex.Message</li>
</ErrorContent>
</ErrorBoundary>
}
</ul>
}
...
The exception is exclusive to the note throwing the exception which we can see in the image below:
See these Blazor .NET 6 updates in action
Check out our video where we demonstrate these updates using a .NET 6 Blazor application.
Please note that since this video was recorded, ComponentApplicationState
has been changed to PersistentComponentState
in .NET 6 RC2. This has been updated in the article and the code example, but not the video.
In addition, download our code example to download our .NET 6 Blazor app, and see these updates in action.
Other .NET 6 reading
This continues our .NET 6 updates, where we also took a look at ASP.NET Core's new features, and C# 10 changes. These updates show that Microsoft is taking a keen interest in Blazor as it looks to become a serious competitor with the popular JavaScript frameworks out there.