Skip to content

This is an example of a starter solution that sets up a MAUI hybrid app and a Blazor web app that shares all of its UI

Notifications You must be signed in to change notification settings

BethMassi/HybridSharedUI

Repository files navigation

Setting up a solution for MAUI hybrid and Blazor web with shared UI

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 Preview 5, we also have a solution template that will set this up automatically for you. Take a look at the .NET 9 Preview 5 release notes for more info or the 9.0 version of the tutorial.

(Rendering mode: Server - Global)

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.

Apps running on Windows, Android, and Web with shared UI

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.

Manually setting up the solution

To manually set this up yourself in Visual Studio, follow these steps

  1. Create an empty Solution and name it MyApp

  2. Add new project MAUI Blazor Hybrid app and name it MyApp.MAUI

  3. 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

  4. Add new project Razor Class Library (RCL) and name it MyApp.Shared

    a. don't select "support pages and views" (default)

  5. Now add project references to MyApp.Shared from both MyApp.MAUI & MyApp.Web project

  6. Move the Components folder and all of its contents from MyApp.MAUI to MyApp.Shared (Ctrl+X, Ctrl+V)

  7. Move wwwroot/css folder and all of its contents from from MyApp.MAUI to MyApp.Shared (Ctrl+X, Ctrl+V)

  8. Move _Imports.razor from MyApp.MAUI to MyApp.Shared (overwrite the one that is there) and rename the last two @usings to MyApp.Shared

...
@using MyApp.Shared
@using MyApp.Shared.Components
  1. Open the _Imports.razor in MyApp.Web add a @using to MyApp.Shared
...
@using MyApp.Shared
  1. Move Routes.razor from MyApp.MAUI to MyApp.Shared (Ctrl+X, Ctrl+V).

  2. Open the Routes.razor file and change MauiProgram to Routes:

<Router AppAssembly="@typeof(Routes).Assembly">
...
  1. Open the MainPage.xaml in the MyApp.MAUI project and add a xmlns:shared reference to the MyApp.Shared RCL and update the BlazorWebView RootComponent ComponentType from local to shared:
<?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>
  1. In the MyApp.MAUI project open wwwroot/index.html and 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" />
  1. Open App.razor from MyApp.Web project Components folder and add the stylesheet references to the MyApp.Shared there too:
<link rel="stylesheet" href="_content/MyApp.Shared/css/bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="_content/MyApp.Shared/css/app.css" />   
  1. In the MyApp.Web project, delete files Routes.razor, Layouts folder & all its contents, and Pages\Home.razor (leave the Error.razor page)

  2. Open MyApp.Web project Program.cs file and AddAddionalAssemblies to MapRazorComponents:

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .AddAdditionalAssemblies(typeof(MyApp.Shared._Imports).Assembly);

You should now be all set! F5 and party on.

Using Interfaces to support different device implementations

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.

  1. In the MyApp.Shared project, create an Interfaces folder and add file called IFormFactor.cs with the following code:
namespace MyApp.Shared.Interfaces
{
    public interface IFormFactor
    {
        public string GetFormFactor();
        public string GetPlatform();
    }
}
  1. Move Component1.razor in the MyApp.Shared project to Components folder 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();
}
  1. Now that we have the interface defined we need to provide implementations in the web and native apps. In the MyApp.Web project, add a folder called Services and add a file called FormFactor.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();
        }
    }
}
  1. Now in the MyApp.MAUI project, add a folder called Services and add a file called FormFactor.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;
        }
    }
}
  1. Use dependency injection to get the implementations of these services into the right place. In the MyApp.MAUI project open MauiProgram.cs and add the usings at the top:
using Microsoft.Extensions.Logging;
using MyApp.MAUI.Services;
using MyApp.Shared.Interfaces;
  1. 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();
  1. Similarly, in the MyApp.Web project, open the Program.cs and right before the call to builder.Build(); add the usings at the top:
using MyApp.Web.Components;
using MyApp.Shared.Interfaces;
using MyApp.Web.Services;  
  1. 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.

About

This is an example of a starter solution that sets up a MAUI hybrid app and a Blazor web app that shares all of its UI

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published