Microsoft has introduced the highly anticipated Blazor framework in ASP.NET Core 3.0. In .NET 5.0, Blazor received significant updates to its component model, plus speed improvements and pre-rendering capabilities. Blazor's initial focus was to allow developers to target the browser using the .NET stack with little or no JavaScript required, all without a single browser plug-in. The key to Blazor's success is its ability to enable .NET developers by leveraging their existing skills. Using Blazor, .NET developers can build a full-stack application using only .NET technologies.

In .NET 6.0, the Blazor framework finds yet another path for developer success with .NET MAUI. MAUI provides a set of technologies that enable apps to run on Web, desktop, and mobile. This new pattern is named Blazor Hybrid and, once again, developers are empowered to use their existing skills to reach even more ecosystems. With Blazor Hybrid, native desktop on Android, iOS, macOS, and Windows are now within reach.

.NET MAUI stands for .NET Multi-platform App UI.

Bringing Blazor to the Desktop

Using Blazor for client-side Web UI with .NET is a fantastic solution, but sometimes full access to the native capabilities of the device is required and out of reach of Blazor on the Web. Blazor Hybrid combines Web technologies (HTML, CSS, and optionally JavaScript) with native in .NET MAUI Blazor. MAUI is a cross-platform framework for creating native mobile and desktop apps with C# and XAML. MAUI uses a single shared code-base to run on Android, iOS, macOS, and Windows, as illustrated by Figure 1.

Figure       1      : An expanded view of the MAUI platform
Figure 1 : An expanded view of the MAUI platform

Bringing Blazor to the desktop isn't a new or unique idea. It's how .NET MAUI Blazor targets cross-platform development that sets it apart from other solutions. To evaluate .NET MAUI Blazor properly, you need to first understand what the alternatives offer and what the tradeoffs are for each. There are two widely accepted methods of running Blazor on the desktop: Using Blazor as Progressive Web Applications (PWAs) or using it as an Electron shell.

Progressive Web Applications

Progressive Web Applications (PWAs) are a type of Web application that can be installed on an operating system without the need for additional bundling and distribution systems. Publishing to an app store, such as the Microsoft Store, Google Play, or Apple App Store is optional and may require additional bundling. PWAs are built with Web-standard technologies including HTML, CSS, JavaScript, and WebAssembly, which work on a standards-compliant browser. PWA features are supported to varying degrees on both desktop and mobile with Apple lagging far behind in adoption.

Once a PWA is installed, the browser's address bar and buttons (Chrome) are stripped away. Just as in a native application, the PWA has a launch icon and interacts natively with the Windows task bar. A PWA gains an important feature that further enhances the user experience: Service workers. Service workers are part of the PWA specification that's a type of Web worker. Service workers are JavaScript code that runs separately from the main browser thread, which can provide an offline mode (intercepting network requests, caching, or retrieving resources from the cache), and deliver push messages. Although PWAs can't access operating system-level APIs, they do feel much more native than a traditional Web application by mimicking their behavior. An example can be seen in Figure 2 with the Blazing Coffee demo app from the Telerik website found at https://demos.telerik.com/blazor-coffee/.

Figure       2      : The Telerik Blazing Coffee demo app installed as a PWA
Figure 2 : The Telerik Blazing Coffee demo app installed as a PWA

Blazor WebAssembly applications can easily take advantage of PWA features by simply meeting the installation criteria of a PWA. Therefore, a PWA option is already available for Blazor when starting a new project from a template. Although Blazor PWA apps can easily be created, there are tradeoffs. Because there's no .NET API support for service workers, all functionalities must be done in JavaScript. And because one of Blazor's attractions is C#, this deters some developers from venturing too deep into service workers.

Electron

Electron is an open-source framework for building native desktop applications with Web technologies like JavaScript, HTML, and CSS. Electron uses an embedded Chromium wrapper that's powered by Node.js. Electron allows you to maintain one JavaScript codebase and create cross-platform apps that work on Windows, macOS, and Linux. Many popular desktop apps are essentially Electron Web apps. Visual Studio Code, Microsoft Teams, Slack, and Figma are all Electron apps that developers use daily.

Electron is more than just a Web wrapper as the framework also provides a custom set of JavaScript APIs to interact with the host operating system. These modules control native desktop functionality, such as menus, dialogs, and tray icons. Although the API does surface some native desktop functionality, it isn't raw access to the full platform; instead, these are a select set of common features between platforms. APIs include access to the filesystem, running processes outside of the browser sandbox, kiosk mode, screen recording, system-tray support for minimized apps, and more.

Because Electron works by leveraging Web standard technologies via Chrome, it supports WebAssembly. This means that a Blazor WebAssembly app can be embedded in an Electron shell and transformed into a desktop application. Such applications use the .NET runtime in the context of Blazor WebAssembly running in interpreted mode, which is less performant than its native desktop equivalent. Using Electron's APIs using .NET is done through Electron.NET, a wrapper around a JavaScript-based Electron application with an embedded ASP.NET Core application. Via an Electron.NET IPC bridge, Electron APIs are invoked from the Blazor application. A block diagram of the architecture is shown in Figure 3.

Figure 3: A block diagram of a Blazor Electron app
Figure 3: A block diagram of a Blazor Electron app

Although Blazor apps can be successful using Electron and Electron.NET, there are tradeoffs. Electron is only for desktop applications, not mobile. Blazor apps rely on multiple frameworks from different vendors and communities and performance is not that of .NET running on the native operating system. In addition, API access is restricted to what's provided within the Electron and Electron.NET frameworks.

Introducing the Blazor Hybrid Architecture

MAUI is the evolution of Xamarin.Forms, which initially targeted iOS and Android, and with MAUI, expanded into desktop. Using MAUI, you'll write cross-platform applications in a single solution with the option of writing platform-specific code as needed. Because MAUI is full-stack .NET, sharing code, logic, testing, and tooling across the solution is possible. The Blazor Hybrid pattern is built upon MAUI and implemented through the BlazorWebView, a MAUI component used to render an embedded Blazor Web view using the WebView2 runtime.

.NET MAUI uses a single API to unify Android, iOS, macOS, and Windows APIs into a write-once run-anywhere developer experience. MAUI apps provide deep access into each native platform. .NET 6 introduces a series of platform-specific frameworks: .NET for Android, .NET for iOS, .NET for macOS, and Windows UI (WinUI) Library. The .NET 6 Base Class Library is shared among all the platforms while abstracting the individual characteristics of each platform from your code. The .NET runtime is used for the execution environment for MAUI applications, even though the underlying implementations of the runtime may be different, depending on the host. For example, on Android, iOS, and macOS, the environment is impended by Mono (a .NET runtime), and on Windows, WinRT provides the environment with optimizations for the Windows platform.

In a .NET MAUI app, you write code that primarily interacts with the .NET MAUI API, shown in Figure 4 (1). .NET MAUI then directly consumes the native platform APIs, as in Figure 4 (3). In addition, app code may directly exercise platform APIs, shown in Figure 4 (2), if required.

Figure       4      : A block diagram of the MAUI platform
Figure 4 : A block diagram of the MAUI platform

MAUI is more than an abstract BCL to share common business logic on different platforms - it also unifies user interface (UI) development too. .NET MAUI provides a single framework for building the UIs for mobile and desktop apps. Because each platform has their own models and elements used to describe their UI, using individual platform-specific frameworks would be difficult to maintain. Instead, MAUI provides a common multi-platform framework for creating user interfaces, while having the flexibility to target specific platforms as needed. In addition to native UI frameworks, MAUI also introduces the BlazorWebView. Through a BlazorWebView component, MAUI apps can use the Blazor Web framework creating a .NET MAUI Blazor application.

BlazorWebView and .NET MAUI Blazor

The Blazor Hybrid pattern uses a BlazorWebView component that enables Blazor within a MAUI application, creating a .NET MAUI Blazor application. .NET MAUI Blazor enables both native and Web UI in a single application and they can co-exist in a single view. With .NET MAUI Blazor, applications can leverage Blazor's component model (Razor Components), which uses HTML, CSS, and the Razor syntax. The Blazor part of an app can reuse components, layouts, and styles that are used in an existing regular Web app. BlazorWebView can be composed alongside native elements; additionally, they leverage platform features and share state with their native counterparts.

In .NET MAUI Blazor apps, all code, both for the native UI parts and the Web UI parts, runs as .NET code on the platform's runtime using a single process. There's no local or remote Web server and no WebAssembly (WASM) in the Blazor Hybrid pattern. The native UI components run as the device's standard UI components (button, label, etc.) and the Web UI components are hosted in a Web view. The components can share state using standard .NET patterns, such as event handlers, dependency injection, or anything else you're already using in your apps today.

Creating a .NET MAUI Blazor Application

Creating your first .NET MAUI Blazor Application is familiar, yet new, territory for most developers. Although .NET technologies like Xamarin.Forms and Blazor have been around for some time, using them together is a new experience. The best way to ensure success is to install the latest updates for all the technologies involved. Thankfully, there are installers for the required SDKs and the version of Visual Studio 2022 with support for MAUI is just a few clicks away. Once the prerequisites are installed, I'll look at the .NET MAUI Blazor template and get an understanding of how the project is structured. Let's install everything now.

Installing .NET MAUI

On Windows, from the Visual Studio Installer for Visual Studio 2022, select the new .NET MAUI workload. This MAUI workload includes the .NET 6 SDK, the MAUI SDK, and MAUI templates. For non-Windows users, choose your development computer's .NET 6 SDK installer from https://dotnet.microsoft.com/, and then use the command line tool to install the MAUI workload by running dotnet workload install maui. When the installation is complete, MAUI will be available from Visual Studio or the command line.

With the prerequisites installed, a new .NET MAUI Blazor app is created using the MAUI-Blazor template. From Visual Studio, this is as simple as choosing the template from the File > New dialog. For all other platforms, use the CLI command dotnet new maui-blazor. Once the template has created a new project, you can see how a .NET MAUI Blazor app is structured.

A .NET MAUI Blazor app shares some similarities with a traditional Blazor app with the addition of MAUI features, such as a platform-specific feature folder and XAML files. Let's examine the project and identify the importance of each part. A newly constructed .NET MAUI Blazor app is shown in Figure 5.

Figure       5      : A newly created .NET MAUI Blazor app created from the template
Figure 5 : A newly created .NET MAUI Blazor app created from the template

At first glance, some familiar concepts may appear in Table 1, as .NET MAUI Blazor apps use patterns found in many .NET app types. Some key differences are the duality of the hybrid scenario where there's overlap between the concepts of root-level component views and routing. Let's examine some of the files to get an understanding of the purpose of each.

At the time of writing, the project uses .NET 6 preview 7. Some of the project structure, files, and code may differ from the final .NET 6 release. The MAUI.WinUI project will likely consolidate under the /Platforms folder and isn't mentioned exclusively in the examples.

Startup.cs

Like most .NET apps, Startup is the entry point of the application. As Startup is initialized, the application is constructed and services are registered, the application is configured, and despondency injection is resolved. In the following code snippet, an application builder (appBuilder) is used to add application features. You can see the Blazor Web application registered in the first item with the method RegisterBlazorMauiWebView. Further down, the UseMauiApp<App> method initializes the App application root. The App class specifies the application's MainPage, which is initialized to the MainPage class. The remainder of the appBuilder registers and configures services that are used with dependency injection (DI) throughout the entire application.

public class Startup : IStartup
{
    public void Configure(IAppHostBuilder appBuilder)
    {
        appBuilder
            .RegisterBlazorMauiWebView()
            .UseMicrosoftExtensionsServiceProviderFactory()
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
            })
            .ConfigureServices(services =>
            {
                services.AddBlazorWebView();
                services.AddSingleton<WeatherForecastService>();
            });
    }
}

MainPage.xaml(.cs)

In the MainPage shown in the snippet below, you can see the first introduction of Blazor as a BlazorWebView. The MainPage wraps the BlazorWebView directly within a ContentPage, essentially creating a full-page Blazor view inside of the application's UI shell.

<ContentPage ...>

    <b:BlazorWebView HostPage="wwwroot/index.html">
        <b:BlazorWebView.RootComponents>
            <b:RootComponent Selector="app" ComponentType="{x:Type local:Main}" />
        </b:BlazorWebView.RootComponents>
    </b:BlazorWebView>

</ContentPage>

The BlazorWebView uses a HostPage parameter to identify the HTML page, which will bootstrap the Blazor application. In the index.html shown in Listing 3, you'll find the root document that hosts the Blazor application within the view. Unlike Blazor WebAssembly, this html file initializes Blazor using blazor.webview.js instead of blazor.webassembly.js. The distinction here is that Blazor isn't using WebAssembly, but rather, the .NET runtime of the host application.

Counter.razor

The Counter page is a simple component decorated with the page directive. This component demonstrates the basic composition of a Razor Component (aka Blazor) including routing, data binding, and event binding/handling. Each portion of component composition is highlighted in Figure 6.

Figure       6      : The composition of the counter component has directives, markup, and logic connected by data binding.
Figure 6 : The composition of the counter component has directives, markup, and logic connected by data binding.

The counter component uses a basic HTML button to increment a counter field that's displayed within a paragraph tag. Updates to the BlazorWebView's DOM are handled by the Blazor framework though data binding. You can see the rendering of the Counter component in Figure 7.

Figure       7      :       The Counter component rendered in .NET MAUI Blazor
Figure 7 : The Counter component rendered in .NET MAUI Blazor

FetchData.razor

In the .NET MAUI Blazor project type, the Fetch Data page is a component that uses data from a service. The Fetch Data component demonstrates dependency injection and basic Razor template concepts. This version of Fetch Data is very similar to the example found in other Blazor application templates.

In Listing 1, at the top of the component following the routing directive, dependency injection is declared. The @inject directive instructs Blazor to resolve an instance of ForecastService. The WeatherForecast is then used by the component's logic to fetch data and bind to an array of WeatherForecast objects. Displaying the WeatherForecast data is done by iterating over the forecasts collection and binding the values to an HTML table.

Listing 1: FetchData.razor

@inject WeatherForecastService ForecastService

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data...</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
            <thead>
                <tr>
                    <th>Date</th>
                    <th>Temp. (C)</th>
                    <th>Temp. (F)</th>
                    <th>Summary</th>
                </tr>
            </thead>
        <tbody>
        @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[] forecasts;
    protected override void OnInitialized()
    {
        forecasts = ForecastService.GetForecast();
    }
}

Running .NET MAUI Blazor

When running the application from Visual Studio, you immediately see the cross-platform nature of MAUI, as shown in Figure 8. Choosing to “run” from Visual Studio involves selecting from emulators and/or physical connected devices. Thankfully, making connections to emulators and devices is made easy through Visual Studio, a feature that has evolved from many years of supporting Xamarin. When the application launches, you can see the Blazor application running in a native shell, all without needing a Web browser or plug-in. In the sample provided, the Blazor Web UI is used for all navigation, routing, and views in the application. Included are the familiar counter and fetch data examples, which are routine to Blazor Web projects.

Figure       8      : The run dialog for a MAUI app in Visual Studio 2022
Figure 8 : The run dialog for a MAUI app in Visual Studio 2022

The template is just a glimpse into how a Blazor application is integrated with MAUI to form a .NET MAUI Blazor app. There is much more to Blazor Hybrid than just a BlazorWebView component.

Blazor Hybrid's Superpowers

When Blazor was introduced for the Web, one of the primary goals was to enable developers to build Web application UIs using .NET. Blazor has been successful in this regard. Blazor allows existing .NET code to work by using the .NET runtime via WebAssembly. With Blazor Hybrid, the primary goal has shifted slightly by extending the capabilities of .NET developers beyond the Web into the desktop and mobile space while reusing HTML and CSS skills in addition to .NET. The Blazor Hybrid pattern with .NET MAUI Blazor offers some unique abilities that are not available with Web centric development.

With Blazor Hybrid, the primary goal has shifted slightly by extending the capabilities of .NET developers beyond the Web into desktop and mobile development.

Minimizing Tradeoffs by Running Native .NET

Blazor WebAssembly and Blazor Server come with tradeoffs. You gain the capabilities of .NET in place of JavaScript at the cost of abstraction. When using WebAssembly, the .NET runtime operates in interpreted mode and isn't as performant as .NET running natively. Although advances to this technology are coming in .NET 6 with Ahead of Time Compilation (AOT), it remains a tradeoff when choosing WebAssembly. Similarly, Blazor server has tradeoffs as well. Although Blazor Server gains the ability to run .NET natively on the server, it requires a constant connection to the client and the performance is indicative of the client's latency.

As seen in Table 2, a .NET MAUI Blazor app has a unique position in the Blazor ecosystem where it can eliminate these tradeoffs by running the .NET runtime supplied by the native platform. Unlike Blazor WebAssembly, .NET MAUI Blazor doesn't use interpreted mode and performs as a device-native app. Because .NET MAUI Blazor is processed locally, the tradeoffs of Blazor Server are also circumvented. Although there are nuances to each platform's execution of the .NET runtime, as noted in Table 2 footnotes 1-4, .NET MAUI Blazor's execution is “closer to the metal” than that of WebAssembly.

In addition to performance, .NET MAUI Blazor also has the greatest potential for sharing a single codebase while targeting cross-platform development. This includes the ability to publish applications to all the major app stores with the added ability to receive a mobile home screen icon or desktop icon.

Leveraging APIs

.NET MAUI Blazor applications aren't restricted to the same Web sandbox that Blazor WebAssembly is. .NET MAUI Blazor apps use the .NET 6 Base Class Library (BCL), which is implemented across all platforms. .NET MAUI unifies cross-platform APIs into a single API that allows a write-once run-anywhere developer experience, while additionally providing deep access to every aspect of each native platform. .NET MAUI also provides MAUI Essentials, a cross-platform API for native device features.

Examples of functionality provided by .NET MAUI essentials include:

  • Access to sensors, such as the accelerometer, compass, and gyroscope on devices
  • Ability to check the device's network connectivity state and detect changes
  • Information is provided about the device the app is running on
  • Copying and pasting text to the system clipboard between apps
  • Picking single or multiple files from the device
  • Storing data securely as key/value pairs
  • Using built-in text-to-speech engines to read text from the device
  • Initiating browser-based authentication flows that listen for a callback to a specific app registered URL

In the sample code of Listing 2, you can read a CSV file from disk using System.IO.StreamReader from the BCL. The WeatherForecastService is a class that can be injected into any MAUI view or a BlazorWebView. When the GetForecasts method is called from the WeatherForecastService, a file from disk is loaded into an array of WeatherForecast. The service uses a cross-platform path for files Environment.SpecialFolder.LocalApplicationData to ensure device compatibility. Once the path is established, the file is read into a stream reader and processed using the open-source CSV reader, CsvHelper. In the FetchData component, the WeatherForecastService is injected and the GetForecast method is called. Once the data has been read from disk, the Razor syntax is used to iterate over the data using HTML and CSS, as shown in Listing 1. The view code is identical to that of the Blazor Maui template because only the service internals have changed.

Listing 2: WeatherForecastService.cs

public WeatherForecast[] GetForecast()
{
    string fileName = Path.Combine(
        Environment.GetFolderPath(
        Environment.SpecialFolder.LocalApplicationData), "temp.csv");

    WeatherForecast[] weather;
    using (var reader = new StreamReader(fileName))
    using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
    {
        weather = csv.GetRecords<WeatherForecast>().ToArray();
    }
    return weather;
}

Running the app either on the desktop or mobile provides the same experience. The CSV data read from disk is displayed in the hybrid view, shown in Figure 9 for mobile.

Figure       9      : A .NET MAUI Blazor app fetching weather data from disk, and running on mobile
Figure 9 : A .NET MAUI Blazor app fetching weather data from disk, and running on mobile

The CSV data read from disk is displayed in the hybrid view, shown in Figure 10 for Windows desktop.

Figure       10      : A .NET MAUI Blazor app fetching data weather from disk, running Windows
Figure 10 : A .NET MAUI Blazor app fetching data weather from disk, running Windows

The examples provided are just one approach to using the BlazorWebView with MAUI. In this scenario, Blazor is used exclusively within the application shell providing the complete UI and navigation by the Main component, which is specified in the ComponentType parameter. Overtaking the entire app with a BlazorWebView component is optional; it can be added ad hoc to any XAML view and even mixed with native UI components. Mixed UIs can share application state and fully interact, as shown in Figure 11.

Figure       11      : A .NET MAUI Blazor app with mixed Native and Web UI using a shared state.
Figure 11 : A .NET MAUI Blazor app with mixed Native and Web UI using a shared state.

Hybrid Ecosystem

.NET 6 will further strengthen the developer ecosystem that surrounds MAUI and Blazor. Since the very beginning when Blazor was just an experiment, libraries supporting the app model started springing up. The same excitement can be seen with MAUI as well. Telerik, a brand synonymous with .NET developers for nearly 20 years, has already announced Telerik UI for MAUI (https://www.telerik.com/maui-ui). Telerik has a dedicated MAUI solution and a dedicated Blazor UI offering with 85+ components, embracing a future where developers have the choice of using platform-native UIs with MAUI and XAML, Blazor with HTML, or both UI component libraries working together seamlessly. The key takeaway here is that developers have choices as to how to implement UI that is unprecedented in .NET development.

As the Blazor ecosystem continues to grow and flourish, it will be further enhanced with packages enabled by MAUI via native platform integration and runtime execution. Libraries that may have been hampered by incompatibilities prior to MAUI find a new niche with Blazor Hybrid. For example, ML.NET, an open source, cross-platform machine learning (ML) framework for .NET devs, has seen limitations around WebAssembly. With Blazor Hybrid, the compatibility problem becomes less relevant as ML.NET and MAUI push to cover the same execution environments (https://devblogs.microsoft.com/dotnet/ml-net-june-updates-model-builder/#ml-net-release).

Expectations are high here as developer options increase due to the expanding reach of MAUI and Blazor Hybrid, while new libraries and frameworks emerge to provide new and unique solutions.

Migration Paths

If the Blazor Hybrid pattern using .NET MAUI Blazor seems interesting but you're currently developing for WPF or Windows Forms, then hybrid feels out of reach. Traditionally, migrating an existing app to a new platform requires a lot of manually rewriting code, but the .NET team has provided a transitional pathway. In .NET 6, a BlazorWebView component was added to both WPF and Windows Forms. This means that existing projects using .NET can be upgraded to .NET 6.0, and, with a few steps, enable BlazorWebView.

By enabling BlazorWebView in WPF and Windows Forms, it's possible to start decoupling UI investments from WPF and Windows Forms. These Blazor-enabled projects only target the Windows platform, unlike MAUI. This is a great way to modernize existing desktop apps in a way that can be brought forward onto .NET MAUI or used on the Web. By using Blazor to modernize existing Windows Forms and WPF apps, existing investments can be leveraged with a transitional Blazor Hybrid application.

To use the new BlazorWebView controls, you first need to make sure that you have WebView2 runtime installed. This is the same WebView2 runtime used by Blazor Hybrid for .NET MAUI Blazor.

The following requirements must be met to add Blazor functionality to an existing Windows Forms app:

  • Update the Windows Forms app to target .NET 6.
  • Update the SDK used in the app's project (csproj) file to Microsoft.NET.Sdk.Razor.
  • Add a package reference to Microsoft.AspNetCore.Components.WebView.WindowsForms.
  • Add the wwwroot/index.html file from Listing 3 to the project, replacing {PROJECT NAME} with the actual project name.
  • Add the app.css file from Listing 4, with some basic styles to the wwwroot folder.
  • For all files in the wwwroot folder, set the Copy to Output Directory property to Copy if newer.
  • Add a root Blazor component Counter.razor from Listing 5 to the project.
  • Add a BlazorWebView control in Listing 6 to the desired form to render the root Blazor component.
  • Run the app to see your BlazorWebView in action, shown in Figure 12.

Listing 3: Index.html

<!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" />
    <title>Blazor app</title>
    <base href="/" />
    <link href="{PROJECT NAME}.styles.css" rel="stylesheet" />
    <link href="app.css" rel="stylesheet" />
</head>

<body>
    <div id="app"></div>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">X</a>
    </div>

    <script src="_framework/blazor.webview.js"></script>
</body>
</html>

Listing 4: app.css

html, body {font-family: 'Helvetica Neue', 
               Helvetica, Arial, sans-serif;}

.valid.modified:not([type=checkbox]) {outline: 1px solid #26b050;}

.invalid {outline: 1px solid red;}

.validation-message {color: red;}

#blazor-error-ui {
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 0.75rem;
    top: 0.5rem;
}

Listing 5: Counter.razor

@using Microsoft.AspNetCore.Components.Web

<h1>Counter</h1>

<p>The current count is: @currentCount</p>
<button @onclick="IncrementCount">Count</button>

@code {
    int currentCount = 0;

    void IncrementCount()
    {
        currentCount++;
    }
}

Listing 6: MyForm.cs

var serviceCollection = new ServiceCollection();
serviceCollection.AddBlazorWebView();
var blazor = new BlazorWebView()
{
    Dock = DockStyle.Fill,
    HostPage = "wwwroot/index.html",
    Services = serviceCollection.BuildServiceProvider(),
};
blazor.RootComponents.Add<Counter>("#app");
Controls.Add(blazor);
Figure       12      : A Blazor Hybrid enabled WinForms application
Figure 12 : A Blazor Hybrid enabled WinForms application

To add Blazor functionality to an existing WPF app, follow the same steps listed above for Windows Forms apps, except:

  • Substitute a package reference for Microsoft.AspNetCore.Components.WebView.Wpf
  • Add the BlazorWebView control in XAML, as shown in Listing 7.
  • Set up the service provider as a static resource in the XAML code-behind file (such as MainWindow.xaml.cs), as shown in Listing 8.
  • To satisfy tooling requirements for the WPF runtime, add an empty partial class from Listing 9 for the component in Counter.razor.cs.
  • Build and run your Blazor based WPF app, shown in Figure 13.

Listing 7: MainWindow.xaml


<Window x:Class="WpfApp1.MainWindow"
        xmlns="..."
        xmlns:local="clr-namespace:WpfApp1"
        xmlns:blazor="clr-namespace:Microsoft.AspNetCore.Components.WebView.Wpf;
        assembly=Microsoft.AspNetCore.Components.WebView.Wpf"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <blazor:BlazorWebView HostPage="wwwroot/index.html"
                              Services="{StaticResource services}">
            <blazor:BlazorWebView.RootComponents>
                <blazor:RootComponent Selector="#app"
                                      ComponentType="{x:Type local:Counter}" />
            </blazor:BlazorWebView.RootComponents>
        </blazor:BlazorWebView>
    </Grid>
</Window>

Listing 8: MainWindow.xaml.cs

var serviceCollection = new ServiceCollection();
serviceCollection.AddBlazorWebView();
Resources.Add("services", serviceCollection.BuildServiceProvider());

Listing 9: Counter.razor.cs

public partial class Counter { }
Figure       13      : A Blazor Hybrid-enabled WPF application
Figure 13 : A Blazor Hybrid-enabled WPF application

What to Expect Next

The Blazor Hybrid pattern and .NET MAUI Blazor marks a huge milestone for .NET 6 and the work doesn't end there. .NET 7 is expected in November 2022 and with it, even more Blazor is anticipated. Another Blazor experiment, Blazor Mobile Bindings, is likely to ship in the .NET 7 release. Blazor Mobile Bindings is extension of the Xamarin > MAUI evolution that uses Razor syntax to define UI components and behaviors. By enabling the Razor syntax as a replacement for XAML, context switching becomes almost trivial. In .NET 7 with Blazor Mobile Bindings, .NET MAUI Blazor apps will have nearly identical coding patterns whether it's a native view (XML and Razor) or Web-based view (HTML and Razor). In Listing 10, a Counter component is composed of XML using the Razor syntax to create a native UI. The Counter component looks very similar to the HTML-based Web component, except for platform-native StackLayout, Label, and Button components defined in XML.

Listing 10: Counter.razor (XML)

<StackLayout>
    <Label FontSize="30"
           Text="@("You pressed " + count + " times")" />
     <Button Text="+1"
             OnClick="@HandleClick" />
</StackLayout>

@code {
    int count;

    void HandleClick()
    {
        count++;
    }
}

Just as .NET MAUI Blazor apps using XAML, application UI logic can be mixed using the BlazorWebView component. With the Razor, the experience is even more seamless as Razor directives and code blocks can be used and follow the same conventions as HTML-based components. In Listing 11, a hybrid view uses both native UI and BlazorWebView to display a counter while sharing app state among all components. The @inject directive is safe to use in the context of Blazor Mobile Bindings and provides dependency injection for native components just as it does for Web components. The similarities continue with Razor data binding on the Label and Button components. The Web component code, which will be rendered by the BlazorWebView, looks very similar regarding syntax, as shown in Listing 12.

Listing 11: Main.razor

@inject CounterState CounterState

<ContentView>
    <StackLayout>

        <StackLayout Margin="new Thickness(20)">
            <Label Text="@($"You pressed {CounterState.CurrentCount} times") 
                "FontSize="30" />
            <Button Text="Increment from native" 
                OnClick="@CounterState.IncrementCount" Padding="10" />
        </StackLayout>

        <BlazorWebView ContentRoot="WebUI/wwwroot"
                        VerticalOptions="LayoutOptions.FillAndExpand">

            <FirstBlazorHybridApp.WebUI.App />
        </BlazorWebView>

    </StackLayout>
</ContentView>

@code {
    // initialization code
}

Listing 12: App.razor

@inject CounterState CounterState

<div style="text-align: center; background-color: lightblue;">
    <div>
        <span style="font-size: 30px; font-weight: bold;">
            You pressed @CounterState.CurrentCount times
        </span>
    </div>
    <div>
        <button style="margin: 20px;" @onclick="ClickMe">
             Increment from HTML
         </button>
     </div>
</div>

@code
{
    private void ClickMe()
    {
        CounterState.IncrementCount();
    }

    // initialization code
}

Blazor Mobile Bindings looks promising. It continues to blur the boundaries between native and Web programming, further extending the usefulness of existing .NET skills. Blazor has found an audience with Web developers by offering a solution to a .NET audience where JavaScript was the only player. Blazor Hybrid and MAUI carry that same tradition with cross-platform native app development via Blazor Mobile Bindings.

A New Era of Blazor Productivity, Again

When .NET 3.0 was released, it included Blazor for the first time. At the time, I wrote an article entitled A New Era of Blazor Productivity . The sentiment I expressed there holds true again: “As powerful as it is convenient, Blazor makes a great choice for new applications. By combining .NET technologies that you're already using with an intuitive component model, Blazor has created a new era of productivity.”

With the Blazor Hybrid pattern, .NET and related tools shorten the learning curve and makes cross-platform development approachable. When using a Blazor Hybrid pattern, the tradeoffs with WebAssembly are reduced. Xamarin evolves to MAUI and brings native APIs to Android, iOS, macOS, and Windows, while BlazorWebViews enable Web architecture. As Blazor grows, the community and ecosystem will continue to grow with solutions for common problems. Blazor's roadmap shows commitment and promise that Blazor is here to stay by delivering innovative technologies to help you build modern applications.

Table 1: A breakdown of .NET MAUI Blazor project folders and files

File or FolderPurpose
/PagesRazor component-based pages or features that will be rendered within a WebView component.
/PlatformsPlatform-specific files, including resources, configurations, native business logic, or native UI components.
/ResourcesGlobal application resources and static files.
/SharedCommon Razor components and Layout components used in Blazor WebViews.
/wwwrootWeb resources used in Blazor Webviews. Ex: CSS, fonts, and images.
_imports.razorGlobal Using statements for Razor components and Pages.
App.xaml(.cs)The root-level application view.
Main.razorThe root-level Blazor view and router.
MainPage.xamlThe default view rendered by the root (App.xaml).
Startup.csApplication entry point, bootstrapping, and configuration.
/Pages/Counter.razorA sample component that counts button `click` events.
/Pages/FetchData.razorA sample component that fetches and displays data.

Table 2: Comparing .NET MAUI Blazor capabilities with Electron and PWAs

FeatureBlazor HybridBlazor ElectronBlazor PWA
InstallableYesYesYes
Publish to StoreYesYesNo
Access Native APIsvia MAUI [1][2][3][4] via Electron.NETNo
WebAssemblyNo [1][2][3][4]YesYes
HTML/CSSYesYesYes
DesktopYesYesYes
AndroidYesNoYes
iOSYesNoLimited
macOSYesYesNo
  • [1] Android apps built using .NET MAUI compile from C# into intermediate language (IL), which is then just-in-time (JIT) compiled to a native assembly when the app launches.
  • [2] iOS apps built using .NET MAUI are fully ahead-of-time (AOT) compiled from C# into native ARM assembly code.
  • [3] macOS apps built using .NET MAUI use Mac Catalyst, a solution from Apple that brings your iOS app built with UIKit to the desktop and augments it with additional AppKit and platform APIs, as required.
  • [4] Windows apps built using .NET MAUI use Windows UI Library (WinUI) 3 and WinRT execution to create native apps that can target the Windows desktop and the Universal Windows Platform (UWP).