.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.
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>
.
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).
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.
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.
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.
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"
}
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>
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.
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.
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
)main
, article
instead of div
)nav
instead of ul
in the NavBar
componenttitle
attribute added to the NavBar
toggle buttonAll of these changes can help to ensure that your application is accessible and works better with screen readers.
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.
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.
Check out the official ASP.NET blog for posts outlining the key features in each and every one of the seven (so far) preview releases.
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.