The latest release of .NET MAUI 9 includes many updates to Blazor, and the MAUI framework in general.
The improvements include new hybrid and web app templates, detection of component render mode, and an improved reconnection experience. Developers can take advantage of these improvements to build better user experiences that more accurately react and respond to changes in the application. The templates specifically are a great benefit to developers as they enable cross-platform projects right from the start.
This article will cover the upgrades and how they generally improve the Blazor experience with .NET MAUI.
Formally known as the Multi-platform App UI, MAUI is the latest evolution of Xamarin Forms and is designed to be a streamlined approach to building cross-platform applications. With MAUI, you can build an application in one codebase and then deploy it to multiple platforms. This includes desktop, mobile, and now even web applications, all while using .NET C#. The platforms supported include:
MAUI includes modern architecture features such as:
MAUI also offers developer-friendly features like hot reloading, debugging, built-in templates, and even design with XAML UI. The framework also provides a streamlined approach to application development making it a great tool for teams that may not have larger organization support when building and deploying applications.
Blazor is a free and open-source web framework developed by Microsoft. It allows you to build interactive applications using .NET C# instead of traditional JavaScript. As part of the .NET platform, Blazor is regularly supported by Microsoft. It provides different hosting models that serve different team requirements:
Blazor Server runs applications server-side and handles UI updates with Microsoft’s SignalR service. All C# code in Blazor Server is executed server-side, and this offering tends to work well for internal corporate networks and applications.
Blazor WebAssembly (WASM) runs applications entirely in the browser. The .NET runtime is downloaded to the client and can run offline once loaded. Blazor WASM is great for public-facing applications that need to scale and use larger runtime environments like .NET.
Blazor Hybrid allows applications to run inside desktop or mobile apps. Referring back to MAUI, Blazor Hybrid is how MAUI enables teams to have a single codebase for applications on multiple platforms. Blazor Hybrid allows for native capabilities in application development.
Generally speaking, Blazor enables a component-based architecture similar to the larger frameworks like React and Angular.
.NET developers can utilize their skills on the backend without needing as much frontend knowledge as would be normally required for something like React or Angular. Blazor lets developers utilize features like dependency injection and other .NET patterns for frontend applications.
As stated earlier, .NET MAUI enables teams to have a single codebase for application development. Teams do not have to utilize Blazor for MAUI projects, but if they do use Blazor, they can leverage the newest .NET 9 release benefits.
Utilizing the new Hybrid and Web App templates of .NET MAUI Blazor enables developers to have a single shared codebase that can then be applied to different platforms like iOS and Android.
To get started, follow the installation instructions for either Visual Studio (Windows) or VSCode (macOS). Once the installs have been accomplished, the templates are readily available. Here is what you would see in Visual Studio (if you are using a Mac, check out the VSCode extension):
If you want to take advantage of the Blazor Hybrid App and Web templates, choose .NET MAUI Blazor Hybrid and Web App. You should see something like the following created:
You’ll notice that there are three projects created:
Within the MAUI App, you’ll see that the MAUIProgram.cs
file builds with a FormFactor
depending on what is defined in the shared project:
public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); }); // Add device-specific services used by the MauiApp3.Shared project builder.Services.AddSingleton<IFormFactor, FormFactor>(); builder.Services.AddMauiBlazorWebView(); #if DEBUG builder.Services.AddBlazorWebViewDeveloperTools(); builder.Logging.AddDebug(); #endif return builder.Build(); } }
Similarly in the web project, you’ll notice that the Program.cs
file builds a Blazor Server project based on what was shared:
using MauiApp3.Shared.Services; using MauiApp3.Web.Components; using MauiApp3.Web.Services; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); // Add device-specific services used by the MauiApp3.Shared project builder.Services.AddSingleton<IFormFactor, FormFactor>(); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseAntiforgery(); app.MapRazorComponents<App>() .AddInteractiveServerRenderMode() .AddAdditionalAssemblies(typeof(MauiApp3.Shared._Imports).Assembly); app.Run();
If you open platforms within the MAUI app, you’ll see there are templates and handlers that do all of what is needed for whatever platform you are building for:
As a developer, this makes your life significantly easier. All you have to do is build out your components and application in the Shared
project and then select the platform you want to build for. VisualStudio (or the extensions for VSCode) will handle the work of running your building, running locally, and even deploying your code using the applicable simulators or binaries installed on your machine:
This is a pretty significant offering. Previously, you would have to build applications with a shared class library and then import that into your MAUI projects. Now with these templates, all of this is done for you from the start. Your development setup is greatly simplified and streamlined.
In addition to the built-in templates, the new release also includes several other optimizations that improve the experience of Blazor app development with MAUI. In the examples below, I’ll be providing code snippets that can originally be found in the Microsoft documentation on .NET 9. I recommend reviewing the .NET 9 docs for more information.
The new release includes a detection of component rendering mode. This allows developers to specify what flavor of Blazor they want to use when building. To utilize different modes across your application you first need to add support in the initial Program.cs
file so that you can use it in your project:
<Dialog @rendermode="InteractiveServer" />
To do this, you’ll also need to specify a using value for the render mode like @using static Microsoft.AspNetCore.Components.Web.RenderMode
At the top of your Razor files in your Blazor application, you can specify the render mode:
@page "..." @rendermode InteractiveServer
You can also specify a render mode for the entire application route:
<Routes @rendermode="InteractiveServer" />
You can set the render mode programmatically in the component definition:
<Routes @rendermode="PageRenderMode" /> ... @code { private IComponentRenderMode? PageRenderMode => InteractiveServer; }
With these modes set, you’ve unlocked certain application behaviors, such as only displaying when a component is interactive:
<button @onclick="Send" disabled="@(!RendererInfo.IsInteractive)"> Send </button>
There are many more options to control your app behavior utilizing these rendering behaviors: prerendering, SSR, and automatic rendering. These all significantly improve the developer experience with .NET MAUI Blazor applications, as you can better control behaviors within the user experience.
One key issue with Blazor Server development has always been the reconnection experience. Blazor Server is built on top of SignalR, and utilizes connections with SignalR for application updates.
When connections to the service would break, users would typically see a message asking for a refresh, or experience a general loss of data. This meant it was up to the developer to try and resolve connection issues or loss of data if this occurred.
With .NET MAUI 9 Blazor, the reconnection experience has been improved. There are several options for teams to customize how connections to SignalR are handled. When initially building your app in your Program.cs
file, you can specify options with interactivity:
builder.Services.AddRazorComponents().AddInteractiveServerComponents(options => { options.{OPTION} = {VALUE}; });
With these options, you can specify what to do with idle timeouts and authentication windows. You can also log and handle connections with a CircuitId
like in this example from the Microsoft Docs:
using Microsoft.AspNetCore.Components.Server.Circuits; using Microsoft.Extensions.Options; using Timer = System.Timers.Timer; public sealed class IdleCircuitHandler : CircuitHandler, IDisposable { private Circuit? currentCircuit; private readonly ILogger logger; private readonly Timer timer; public IdleCircuitHandler(ILogger<IdleCircuitHandler> logger, IOptions<IdleCircuitOptions> options) { timer = new Timer { Interval = options.Value.IdleTimeout.TotalMilliseconds, AutoReset = false }; timer.Elapsed += CircuitIdle; this.logger = logger; } private void CircuitIdle(object? sender, System.Timers.ElapsedEventArgs e) { logger.LogInformation("{CircuitId} is idle", currentCircuit?.Id); } public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken) { currentCircuit = circuit; return Task.CompletedTask; } public override Func<CircuitInboundActivityContext, Task> CreateInboundActivityHandler( Func<CircuitInboundActivityContext, Task> next) { return context => { timer.Stop(); timer.Start(); return next(context); }; } public void Dispose() => timer.Dispose(); } public class IdleCircuitOptions { public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(5); } public static class IdleCircuitHandlerServiceCollectionExtensions { public static IServiceCollection AddIdleCircuitHandler( this IServiceCollection services, Action<IdleCircuitOptions> configureOptions) { services.Configure(configureOptions); services.AddIdleCircuitHandler(); return services; } public static IServiceCollection AddIdleCircuitHandler( this IServiceCollection services) { services.AddScoped<CircuitHandler, IdleCircuitHandler>(); return services; } }
The general approach is to try and handle the “circuit,” or connection to SignalR, gracefully. There are many good defaults built in, such as how many times to attempt a reconnect, or reconnection intervals. There is a nice write-up on this behavior in Jon Hilton’s post on the same topic.
.NET MAUI 9 also offers UI updates so that what is shown to the user can have custom styles applied to it. Specifically, you now can see something like this (as seen in this Microsoft article):
Overall, the new release of .NET MAUI 9 brings several great improvements for developers.
The hybrid and web templates are a great addition, helping to streamline the creation of new applications. The ability to control rendering allows Blazor developers to more tightly control their UI experience for their customers. Controlling reconnection states with SignalR is also a great win for teams, as they can create a more graceful way to handle this historical issue with Blazor applications.
Hopefully, this article has shown you some of the improvements and helped you to learn more about improvements with Blazor and MAUI in the new release of .NET 9. Thanks for reading!
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowReact Islands integrates React into legacy codebases, enabling modernization without requiring a complete rewrite.
Onlook bridges design and development, integrating design tools into IDEs for seamless collaboration and faster workflows.
JavaScript generators offer a powerful and often overlooked way to handle asynchronous operations, manage state, and process data streams.
webpack’s Module Federation allows you to easily share code and dependencies between applications, helpful in micro-frontend architecture.