What’s New in .NET 6 for Blazor?

.NET 6 is coming and, with it, some notable Blazor improvements. Dynamic components, hot reload, Blazor MAUI apps—the list goes on and on. Here are some of the main highlights.

.NET 6 is shaping up nicely ahead of its November release, but what’s in it for Blazor?

Here are a few of the standout changes coming in Blazor’s next significant release.

Dynamic Components

The standard way to build a Blazor app is to break the UI down into a number of smaller components which you can then compose together to build a larger section of the UI.

Greeting.razor

<p>Hello @Name</p>
    
@code {
	[Parameter]
	public string Name { get; set; }    
}

Index.razor

@page "/"

<Greeting Name="Jon"/>
<Greeting Name="Esme"/>

So in this case we’d see two Hello messages if we visited the application’s home page.

This is great for those occasions when you know ahead of time exactly which components you want to display.

But what about those times when you need to render varying components at runtime, based on data.

Take, for example, a customizable dashboard where your users could pick and choose which widgets they wanted displayed.

In this case it might be desirable to store those dashboard preferences/design as data, then read that data and dynamically render the relevant component(s).

With .NET 6 we now have access to a DynamicComponent which enables exactly that.

Say we have a List of Dashboard components, stored as types:

List<Type> widgets = new List<Type>{ typeof(Counter), typeof(FetchData) }

We can loop over that list and render each component (according to its type):

@foreach(var widgetType in widgets){
    <DynamicComponent Type="@widgetType"/>
}

This would render the Counter and FetchData components.

But what if we need to pass extra parameters to the dynamic component? We can’t just pass them to the DynamicComponent when we declare it because we can’t know at compile-time which component is going to be rendered.

For this scenario we can pass a dictionary of parameters.

Extending our widget example, if we create a class or record to represent a widget:

public record Widget {
    public Type Type;    
    public IDictionary<string, object> Parameters;
}

Then store a list of them in our Blazor component, we can iterate over each one and pass the Parameters along to DynamicComponent.

@page "/"

@foreach (var widget in widgets)
{   
    <DynamicComponent Type="@widget.Type" Parameters="@widget.Parameters"/>
}

@code {
    private readonly List<Widget> widgets = new()
    {
        new Widget { Type = typeof(FetchData) },
        new Widget
        {
            Type = typeof(Greeting), 
            Parameters = new Dictionary<string, object>
            {
                ["Name"] = "Jon"
            }
        }
    };
}

This is equivalent to:

@page "/"

<FetchData />
<Greeting Name="Jon" />

Except now we can change the dashboard at runtime by adding/removing items to the List<Widget>.

Hot Reload

It’s hard to overstate the importance of a fast feedback loop when developing for the web.

So much of “modern” web development involves making small changes to HTML, CSS, UI logic and it’s hard to rapidly iterate your design when you find yourself waiting to see those changes spring to life in the browser.

Improving this feedback loop was a priority for .NET 6, and the preview releases have shown steady improvements with each release.

Hot Reload support now exists for both Visual Studio and dotnet watch and, in general, is working well. The effect is a near-instant update in the browser when you tweak your components and if, for any reason, hot reload fails (a “rude edit”) you will be asked if you wish to perform a full rebuild (with an option to always do this in future).

Ahead of Time Compilation (Blazor Wasm)

Blazor WebAssembly now supports ahead-of-time (AOT) compilation, but before you dash off to enable it for all your projects, it’s worth just pausing to consider what it actually means for your application.

AOT directly compiles your .NET code to WebAssembly as part of the compilation process.

This is different from the standard way your Blazor Wasm apps run today, where your code is never compiled to WebAssembly, instead running on a .NET IL interpreter (which is itself implemented in WebAssembly).

This use of an interpreter means, in practice, your standard Blazor Wasm app will perform more slowly than it would on a normal .NET runtime (for example an MVC or Razor Pages app running on a server).

AOT addresses this performance issue by compiling your code to WebAssembly. For CPU-intensive tasks the performance improvement is significant (up to 5 times faster), but AOT compilation brings its own tradeoffs.

It requires an additional build tool to be installed, the actual compilation itself is quite slow (as of Preview 7) and AOT-compiled apps are larger (about 2x larger on average).

To use AOT you’ll need to install the latest .NET WebAssembly tools (watch out, the name of these tools changed between Preview 4 and Preview 7 of .NET 6. The new name is shown below).

dotnet workload install wasm-tools

The main tradeoff is that you’ll sacrifice load time for runtime performance, so the decision as to whether this makes sense will vary depending on the specific app you’re building.

Blazor Query String Component Parameters

As of Preview 7 you can now populate your component parameters from the query string.

You could do this previously but you had to lean on Microsoft.AspNetCore.WebUtilities and write your own code to handle it.

Now it’s much simpler as you can simply decorate your parameters with the new SupplyParameterFromQuery attribute:

[Parameter]
[SupplyParameterFromQuery]
public string Filter { get; set; }

With this, if you append ?filter=x to the URL for your Blazor page, the corresponding parameter (Filter) will be populate with the value from query string.

Note there’s currently a debate ongoing about the name of the attribute, so it may change in a future release but the functionality will remain the same.

Required Parameters

If your component relies on a certain parameter you can now make sure that parameter is always provided by marking the parameter as EditorRequired.

[EditorRequired]
[Parameter]
public string Name { get; set; }

If you attempt to use the component without specifying required parameters, you’ll get an error at design time (in your IDE and/or when the app is compiled).

Worth noting it isn’t enforced at runtime so doesn’t guarantee that your parameters values will not be null.

Modify HTML Head From Blazor

Originally mooted for .NET 5 but now coming in .NET 6, direct support for updating the HTML head from your components will make a few scenarios much easier.

You can easily change the page title and add meta tags for SEO without resorting to JS interop to get the job done.

For this, .NET 6 introduces two new components: PageTitle and HeadContent.

You can change the page title using PageTitle.

<PageTitle>@title</PageTitle>

@code {
	private string title = "Dynamic title!"
}

And add anything else you like to the HTML <head> via HeadContent.

<HeadContent>
	<meta name="description" content="@description">
</HeadContent>

@code {
	private string description = "Comprehensive dynamic description"
}

Error Boundaries

With the current/previous release of Blazor, any exception thrown in any component would generally result in your entire application failing, often requiring a restart of the entire app.

With .NET 6 you can contain the chaos and wrap any part of your application in an error boundary.

<h1>Super Fancy Application</h1>
<div>
    <ErrorBoundary>
    	<Counter />
    </ErrorBoundary>
</div>

Now if Counter throws an exception, an error will be displayed where the ErrorBoundary is declared, leaving the rest of the UI unaffected.

By default error boundaries render an empty div with a blazor-error-boundary CSS class when an exception occurs. However, you can take more control of that error if you wish:

<h1>Super Fancy Application</h1>
<div>
    <ErrorBoundary>
        <ChildContent>
            <Counter />
        </ChildContent>
    	<ErrorContent>
        	<p class="bad-error">
                Oh dear, something went horribily, horribly wrong whilst attempting to count...
            </p>
        </ErrorContent>
    </ErrorBoundary>
</div>

Improved Prerendering With Preserved State

Did you know you can opt to prerender your Blazor Wasm apps on the server?

This provides a “best of both worlds” experience where the initial load time for your Blazor app is snappy (because the page is rendered on the server and returned as straight HTML), but then your components become fully interactive in the browser once Blazor Wasm has finished loading.

There’s a big caveat to this in .NET 5 however, where data-driven pages would load once (after prerendering) then potentially “flash” as the data is retrieved for a second time in the browser (once Blazor Wasm had kicked in).

.NET 6 brings a welcome improvement whereby you can store the initial state (when the app is prerendered) and use it for the second load, thereby removing the need to fetch the same data twice (also removing the “flash” of content in the browser following the second load).

To use it, you have to first add the <persist-component-state> tag inside the closing </body> tag of your _Layout.cshtml page.

<body>
    ...
    
    <persist-component-state />
</body>

Then you can register a handler for PersistentComponentState’s OnPersisting event in the component(s) where you wish to preserve state.

@page "/fetchUsers"
@implements IDisposable
@inject PersistentComponentState ApplicationState

<!-- html here -->
    
@code {
    
    private IEnumerable<User> users;
    private PersistingComponentStateSubscription _subscription;
    
    protected override async Task OnInitializedAsync()
    {
        _subscription = ApplicationState.RegisterOnPersisting(PersistUsers);
      
        if (ApplicationState
            .TryTakeFromJson<User[]>("users", out var storedUsers))
        {
            users = storedUsers;           
        } else {
             users = await UserService.GetUsersAsync();
        }
    }

    private Task PersistUsers()
    {
        ApplicationState.PersistAsJson("users", users);
        return Task.CompletedTask;
    }

    void IDisposable.Dispose()
    {
         _subscription.Dispose();
    }
    
}

Here we’ve injected PersistentComponentState and are consequently able to register an OnPersisting event to fetch and then persist the user data.

On the subsequent load (in the browser, via Blazor Wasm) OnInitializedAsync will attempt to retrieve the list of users from persisted state first, only reverting to fetching the users via the UserService if persisted data isn’t found.

Performance Improvements

There are also numerous performance improvements for Blazor coming in .NET 6.

One standout is a reduced Blazor Wasm download size thanks to improved and extended relinking in the new version.

Relinking is the process whereby unused code can be trimmed from libraries, making them smaller and thereby reducing the download size of the compiled app.

Relinking is already in place for trimming unused code from .NET Core framework libraries, but in .NET 6 this is being extended to the .NET runtime itself.

Overall this will result in a smaller dotnet.wasm download, but there’s one area where you can significantly reduce this size even further.

If you know you don’t need to handle strings, numbers, dates, etc. differently based on the current culture (according to the user’s browser), then you can go ahead and opt out of globalization functionality for your app.

By setting InvariantGlobalization to true in your project files, the related logic in the .NET runtime will be removed, making the runtime considerably smaller.

If Blazor server is your thing, then you’ll be pleased to hear .NET 6 ships with smaller Blazor server scripts too, making for a faster initial startup.

The Razor compiler has been updated to use source generators (resulting in faster compilation of your Razor views).

There are also improvements in file uploading, with files larger than 2GB now supported using the InputFile component and much faster uploading thanks to native byte[] streaming.

Accessibility Improvements

It’s always a good time to focus on making your app more accessible, and the new version of Blazor nudges you in the right direction.

There is a new FocusOnNavigate component which will set the UI focus to an element of your choosing (based on a CSS selector) after navigating to a page.

You’ll find the component in use in the App component when you spin up a Blazor app using .NET 6.

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p role="alert">Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

Beyond this you’ll also find some changes in the default Blazor template which help ensure your application is accessible from the moment of its inception.

  • role="alert" and role="status" attributes used in the example components (such as the Counter)
  • Use of more semantic markup elements (mainarticle instead of div)
  • nav instead of ul in the NavBar component
  • title attribute added to the NavBar toggle button

All of these changes can help to ensure that your application is accessible and works better with screen readers.

.NET MAUI Blazor Apps

MAUI is the evolution of Xamarin Forms. Its aim is to make it easier to build cross-platform applications without requiring you, the developer, to write the same code over and over for the different platforms.

With .NET MAUI you can create one project, build your application and have it run on Android, iOS, macOS and Windows.

The primary way of building a MAUI app is using C# and XML, but with .NET 6 we also get the first glimpse at another option: building MAUI apps using Blazor.

If you find it enjoyable and productive to build your web applications using Blazor’s component model, then you can take that same approach and build your MAUI apps using Blazor.

That way, when you compile your app it will run as a native application, just like any other MAUI app.

This gives you the benefit of writing your apps using the familiar paradigm of Blazor’s component model, but with the added benefit of full access to the native capabilities of the devices on which your app will be running.

In a nutshell, if you need to build applications for Android, iOS, Windows and/or MacOS, .NET MAUI Blazor apps make that possible using Blazor’s component model.

That’s Not All!

While I’ve picked out some of the more sizeable, noteworthy features coming in this Blazor release, there are many, many more.

Custom events support, .BlazorWebView controls for WPF and Windows Forms, improved Blazor SVG support—the list goes on and on.

Anjali Punjab

Anjali Punjab is a freelance writer, blogger, and ghostwriter who develops high-quality content for businesses. She is also a HubSpot Inbound Marketing Certified and Google Analytics Qualified Professional.