Bringing Blazor to Desktop and Mobile with MAUI

I wanted to host a Blazor WebAssembly application natively as a desktop application, and to achieve that, I planned to use a MAUI Blazor app. However, I wanted to avoid duplicating the Razor pages between both of my projects since I intended to continue using the web version as well. In this blog post, I will show you how to accomplish this. Consider using this approach to host your SaaS application in the cloud and simultaneously offer support for a native desktop app, much like Slack.

To keep things simple, I’ll be using the Tic Tac Toe project we made in our previous blog post. We’ve already created a Blazor WebAssembly app and will now adapt it to run on both desktop and mobile. You can find the complete source code here: mhdbouk/tictactoe-blazor.

What is .NET MAUI?

.NET MAUI is a multi-platform native UI framework developed by Microsoft that allows deployment to multiple devices across mobile and desktop using a single project codebase.

On the other hand, Blazor is also used to build native client apps, but it takes a hybrid approach. Hybrid apps are native applications that make use of web technologies for their functionality. In a Blazor Hybrid app or MAUI Blazor App, Razor components run directly within the native app (rather than on WebAssembly), alongside other .NET code, and they render the web UI (HTML, CSS) to a web view control called BlazorWebView. Utilizing Blazor with .NET MAUI provides a convenient way to create cross-platform Blazor Hybrid apps for both mobile and desktop.

source: Microsoft

Setting Up the Development Environment

If you don’t have a Blazor web app and a MAUI Blazor app in your solution, you can easily create them using the following commands or by using the Visual Studio interface.

dotnet workload install wasm-tools // install the blazor wasm tools
dotnet workload install maui // install maui

// Create the solution & the projects
dotnet new sln -n TicTacToe

dotnet new blazorwasm -o TicTacToe.Web
dotnet new maui-blazor -o TicTacToe.Maui

dotnet sln add TicTacToe.Web/TicTacToe.Web.csproj
dotnet sln add TicTacToe.Maui/TicTacToe.Maui.csproj

New Shared Class Library Project

In your solution, start by creating a new Class Library project named Shared. This project will serve as a central repository for all the common Blazor functionality, including Razor files, components, pages, and any static CSS/JS files.

After creating the project, proceed to update the .csproj file by replacing its content with the following:

<Project Sdk="Microsoft.NET.Sdk.Razor">

    <PropertyGroup>
        <TargetFramework>net7.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="7.0.0" />
    </ItemGroup>

</Project>

First, we changed the SDK type from Microsoft.NET.Sdk to Microsoft.NET.Sdk.Razor. This is a very important step, as, without it, the Blazor and MAUI projects will not be able to communicate with the Shared project. Also, make sure to add the Microsoft.AspNetCore.Components.Web NuGet package so we can resolve our Blazor dependencies.

As of the date of writing this blog post, I had to downgrade all my projects to the initial release of .NET 7 due to an unsupported package (Microsoft.Extensions.Logging.Abstractions ).
reference: https://github.com/dotnet/maui/issues/16244
and this is the error message
TicTacToe.Maui.csproj: Error NU1605 : Warning As Error: Detected package downgrade: Microsoft.Extensions.Logging.Abstractions from 7.0.1 to 7.0.0. Reference the package directly from the project to select a different version.
TicTacToe.Maui -> TicTacToe.Shared -> Microsoft.AspNetCore.Components.Web 7.0.11 -> Microsoft.AspNetCore.Components 7.0.11 -> Microsoft.AspNetCore.Authorization 7.0.11 -> Microsoft.Extensions.Logging.Abstractions (>= 7.0.1)
TicTacToe.Maui -> Microsoft.Extensions.Logging.Abstractions (>= 7.0.0)

Move common files to Shared

Start by moving everything that is shared between the two projects. This includes pages, components, and layout files like the MainLayout.razor file. This step will ensure that both projects have access to the same components and resources.

Create a new file called _Imports.razor in the Shared project and add the following to it:

@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web

Once you’ve copied all the shared logic, make sure you update all the namespaces to reflect the Shared project. For example, if you are moving files from your web Blazor project and the namespace is called TicTacToe.Web, rename it to TicTacToe.Shared:

namespace TicTacToe.Web; // <- change this to

namespace TicTacToe.Shared; // <- this one

Another thing you should do is create a new wwwroot folder and add all the CSS/JS/assets files from your web project (we will update the references in index.html later in the post).

Update Blazor Web App and MAUI Blazor App projects

We are going to make a few changes in both of our projects (Blazor Web and MAUI Blazor App), These changes are necessary to establish a connection between the projects and make shared components accessible.

First, we are going to add a reference to the Shared project, update the _Imports.razor file, update the App.razor & Main.razor files with the additional assembly attribute, and fix our index.html static file mapping.

_Index.razor

A few things are needed here. First, add a reference to the new Shared project in the web project. Then, add the using statement in the _Imports.razor file in both the Blazor Web app and the MAUI Blazor App. This step will let other files like App.razor or Main.razor to resolve the shared component and files.

... // other using

@using TicTacToe.Shared;

App.razor / Main.razor

We need to update the App.razor in the Blazor web project and the Main.razor in the MAUI app project, by including a reference to the MainLayout class using the AdditionalAssemblies attribute. Open both files and update the content with the following:

<Router AppAssembly="@typeof(App).Assembly" AdditionalAssemblies="new[] { typeof(MainLayout).Assembly }" PreferExactMatches="true">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>
        <PageTitle>Not found</PageTitle>
        <LayoutView Layout="@typeof(MainLayout)">
            <p role="alert">Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

Notice how we added AdditionalAssemblies="new[] { typeof(MainLayout).Assembly }." This is needed to let the project resolve the MainLayout class from outside of the current assembly.

index.html

Open the index.html file and make sure to reference the static files in the following format:

...
<head>
  ...
  
  <link href="_content/TicTacToe.Shared/css/app.css" rel="stylesheet" />
  
  ...
</head>
...

Start the hypertext reference (href) with _content/{{Shared project Name}}/{{the old file path}}.

Repeat the same for other files (JS, fonts, …).

While working on this, XCode automatically updated to version 15, and unfortunately, MAUI in .NET 7 does not yet offer support for this version. The good news is that it will be supported in .NET 8. To address this situation, I had to use Xcodes to install the older version of Xcode, allowing me to run the app on an iPhone with the older iOS 16 simulation.

Conclusion

Tic Tac Toe app running in the browser, iPhone simulator, and native desktop app

In completing these steps, you’ve successfully achieved seamless integration between your Blazor WebAssembly application and the MAUI platform, allowing you to run your app both in a web browser and as a desktop or mobile application. Now, it’s time to enjoy the flexibility of this setup. Happy coding!

Recent Posts

HybridCache in .NET 9 is Awesome!
.NET 9 is now live, and it comes with a new set of features. Some are great, and some are just icing on the cake. …
Adding Custom Formatting to Your Classes with IFormattable
DateTime has a great feature that I often replicate in my classes: the ability for users to format the ToString output however they want. Using …
Azure-Sync: Sync your Azure App Settings to local
Azure-Sync is a handy shell script tool designed to help .NET developers working with Azure App Services. Inspired by the functionality provided by the Azure …
Implement Builders easily with Source Generator in .NET
I created a YouTube video on Source Generator in which I showcased one possible implementation. However, I feel that I didn’t fully highlight its capabilities. …

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.