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.

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.

Viewing the source code of a Blazor WebAssembly application

Viewing the source code of a Blazor WebAssembly application

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:

Using the ErrorBoundary tag in Blazor for .NET 6.

Using the ErrorBoundary tag in Blazor for .NET 6.

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.