Update: We now have an official tutorial and sample in the Blazor hybrid docs: Build a .NET MAUI Blazor Hybrid app with a Blazor Web App. Starting in .NET 9, we also have a solution template that will set this up automatically for you. See the the 9.0 version of the tutorial.
This repo demonstrates a starter solution that contains a MAUI hybrid (native, cross-platform) app, a Blazor web app and a Razor class library that contains all the shared UI that is used by both native and web apps. Select a different branch to see the different rendering mode options.
It also demonstrates how to use Blazor render modes on the web app but ignore them in the MAUI app. It does this by setting up a helper class in the RCL called InteractiveRenderSettings that has properties that are used as render mode in the components while running in the web app. The MauiProgram.cs sets these properties to null so they are ignored on the client instead of throwing an exception.
To manually set this up yourself in Visual Studio, follow these steps
-
Create an empty Solution and name it
MyApp -
Add new project MAUI Blazor Hybrid app and name it
MyApp.MAUI -
Add new project Blazor Web App and name it
MyApp.Web. Select the following options:a. Authentication type = none
b. Configure for HTTPS is checked
c. Interactive render mode = Server
d. Interactivity location = Global <-- This setting is important because hybrid apps always run interactive and will throw errors on pages or components that explicitly specify a render mode. See #51235. If you do not use a global render mode, you will need to implement the pattern shown in this repository.
e. Uncheck Include sample pages
-
Add new project Razor Class Library (RCL) and name it
MyApp.Shareda. don't select "support pages and views" (default)
-
Now add project references to
MyApp.Sharedfrom bothMyApp.MAUI&MyApp.Webproject -
Move the
Componentsfolder and all of its contents fromMyApp.MAUItoMyApp.Shared(Ctrl+X, Ctrl+V) -
Move
wwwroot/cssfolder and all of its contents from fromMyApp.MAUItoMyApp.Shared(Ctrl+X, Ctrl+V) -
Move
_Imports.razorfromMyApp.MAUItoMyApp.Shared(overwrite the one that is there) and rename the last two@usings toMyApp.Shared
...
@using MyApp.Shared
@using MyApp.Shared.Components
- Open the
_Imports.razorinMyApp.Webadd a@usingtoMyApp.Shared
...
@using MyApp.Shared
-
Move
Routes.razorfromMyApp.MAUItoMyApp.Shared(Ctrl+X, Ctrl+V). -
Open the
Routes.razorfile and changeMauiProgramtoRoutes:
<Router AppAssembly="@typeof(Routes).Assembly">
...
- Open the
MainPage.xamlin theMyApp.MAUIproject and add axmlns:sharedreference to theMyApp.SharedRCL and update theBlazorWebViewRootComponentComponentTypefromlocaltoshared:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyApp.MAUI"
xmlns:shared="clr-namespace:MyApp.Shared;assembly=MyApp.Shared"
x:Class="MyApp.MAUI.MainPage"
BackgroundColor="{DynamicResource PageBackgroundColor}">
<BlazorWebView x:Name="blazorWebView" HostPage="wwwroot/index.html">
<BlazorWebView.RootComponents>
<RootComponent Selector="#app" ComponentType="{x:Type shared:Routes}" />
</BlazorWebView.RootComponents>
</BlazorWebView>
</ContentPage>- In the
MyApp.MAUIproject openwwwroot/index.htmland change stylesheets to point to_content/MyApp.Shared/:
<link rel="stylesheet" href="_content/MyApp.Shared/css/bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="_content/MyApp.Shared/css/app.css" />- Open
App.razorfromMyApp.WebprojectComponentsfolder and add the stylesheet references to theMyApp.Sharedthere too:
<link rel="stylesheet" href="_content/MyApp.Shared/css/bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="_content/MyApp.Shared/css/app.css" /> -
In the
MyApp.Webproject, delete filesRoutes.razor,Layoutsfolder & all its contents, andPages\Home.razor(leave theError.razorpage) -
Open
MyApp.WebprojectProgram.csfile andAddAddionalAssembliestoMapRazorComponents:
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddAdditionalAssemblies(typeof(MyApp.Shared._Imports).Assembly);
You should now be all set! F5 and party on.
This sample also shows how to use interfaces on the UI to call into different implementations across the web app and the native (MAUI Hybrid) app. We will make a component that displays the device form factor. We can use the MAUI abstraction layer for all the native apps but we will need to provide our own implementation for the web app.
- In the
MyApp.Sharedproject, create anInterfacesfolder and add file calledIFormFactor.cswith the following code:
namespace MyApp.Shared.Interfaces
{
public interface IFormFactor
{
public string GetFormFactor();
public string GetPlatform();
}
}- Move
Component1.razorin theMyApp.Sharedproject toComponentsfolder and add the following code:
@using MyApp.Shared.Interfaces
@inject IFormFactor FormFactor
<div class="my-component">
<p>You are running on:</p>
<h3>@factor</h3>
<h3>@platform</h3>
<em>This component is defined in the <strong>MyApp.Shared</strong> library.</em>
</div>
@code {
private string factor => FormFactor.GetFormFactor();
private string platform => FormFactor.GetPlatform();
}- Now that we have the interface defined we need to provide implementations in the web and native apps. In the
MyApp.Webproject, add a folder calledServicesand add a file calledFormFactor.cs. Add the following code:
using MyApp.Shared.Interfaces;
namespace MyApp.Web.Services
{
public class FormFactor : IFormFactor
{
public string GetFormFactor()
{
return "Web";
}
public string GetPlatform()
{
return Environment.OSVersion.ToString();
}
}
}- Now in the
MyApp.MAUIproject, add a folder calledServicesand add a file calledFormFactor.cs. We can use the MAUI abstractions layer to write code that will work on all the native device platforms. Add the following code:
using MyApp.Shared.Interfaces;
namespace MyApp.MAUI.Services
{
public class FormFactor : IFormFactor
{
public string GetFormFactor()
{
return DeviceInfo.Idiom.ToString();
}
public string GetPlatform()
{
return DeviceInfo.Platform.ToString() + " - " + DeviceInfo.VersionString;
}
}
}- Use dependency injection to get the implementations of these services into the right place. In the
MyApp.MAUIproject openMauiProgram.csand add theusings at the top:
using Microsoft.Extensions.Logging;
using MyApp.MAUI.Services;
using MyApp.Shared.Interfaces;- And right before the call to
builder.Build();add the following code:
...
// Add device specific services used by Razor Class Library (MyApp.Shared)
builder.Services.AddSingleton<IFormFactor, FormFactor>();
return builder.Build();- Similarly, in the
MyApp.Webproject, open theProgram.csand right before the call tobuilder.Build();add theusings at the top:
using MyApp.Web.Components;
using MyApp.Shared.Interfaces;
using MyApp.Web.Services; - And right before the call to
builder.Build();add the following code:
...
// Add device specific services used by Razor Class Library (MyApp.Shared)
builder.Services.AddScoped<IFormFactor, FormFactor>();
var app = builder.Build();You can also use compiler preprocessor directives in your RCL to implement different UI depending on the device you are running on. In that case you need to multi-target your RCL like you do in your MAUI app. For an example of that see: BethMassi/BethTimeUntil repo.
That's it! Have fun.

