Skip to content

Commit

Permalink
New .NET MAUI Blazor Hybrid template (#32793)
Browse files Browse the repository at this point in the history
  • Loading branch information
guardrex authored Jun 10, 2024
1 parent 0239c05 commit 3b62b2a
Showing 1 changed file with 183 additions and 18 deletions.
201 changes: 183 additions & 18 deletions aspnetcore/blazor/hybrid/tutorials/maui-blazor-web-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Learn how to build a .NET MAUI Blazor Hybrid app with a Blazor Web
monikerRange: '>= aspnetcore-8.0'
ms.author: riande
ms.custom: mvc
ms.date: 04/25/2024
ms.date: 06/10/2024
uid: blazor/hybrid/tutorials/maui-blazor-web-app
---
# Build a .NET MAUI Blazor Hybrid app with a Blazor Web App
Expand All @@ -16,15 +16,40 @@ This article shows you how to build a .NET MAUI Blazor Hybrid app with a Blazor

For prerequisites and preliminary steps, see <xref:blazor/hybrid/tutorials/maui>. We recommend using the .NET MAUI Blazor Hybrid tutorial to set up your local system for .NET MAUI development before using the guidance in this article.

## .NET MAUI Blazor Web App sample app
:::moniker range=">= aspnetcore-9.0"

## .NET MAUI Blazor Hybrid and Web App solution template

The .NET MAUI Blazor Hybrid and Web App solution template sets up a solution that targets Android, iOS, Mac, Windows and Web that reuses UI. You can choose a Blazor interactive render mode for the web app and it creates the appropriate projects for the app, including a Blazor Web App and a .NET MAUI Blazor Hybrid app. A shared Razor class library (RCL) maintains the Razor components for the app's UI. The template also provides sample code to show you how to use dependency injection to provide different interface implementations for the Blazor Hybrid and Blazor Web App, which is covered in the [Using interfaces to support different device implementations](#using-interfaces-to-support-different-device-implementations) section of this article.

Create an app from the project template with the following .NET CLI command:

```dotnetcli
dotnet new maui-blazor-web -o MauiBlazorWeb -int Server -ai
```

In the preceding command:

* The `-o|--output` option creates a new folder for the app named `MauiBlazorWeb`.
* The `-int|--interactivity` option sets up the interactivity location to `Server`.
* The `-ai|--all-interactive` option specifies global interactivity, which is important because MAUI apps always run interactively and throw errors on Razor component pages that explicitly specify a render mode. If you don't use a global render mode, you must implement the approach described in the [Use Blazor render modes](#use-blazor-render-modes) section. For more information, see [BlazorWebView needs a way to enable overriding ResolveComponentForRenderMode (`dotnet/aspnetcore` #51235)](https://github.com/dotnet/aspnetcore/issues/51235).

<!-- UPDATE 9.0 Provide the project template's name here for VS, possibly
using a tooling pivot for the article. -->

:::moniker-end

:::moniker range="< aspnetcore-9.0"

## .NET MAUI Blazor Hybrid and Web App sample app

[Obtain the sample app](xref:blazor/fundamentals/index#sample-apps) named `MauiBlazorWeb` from the [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) (.NET 8 or later).

The sample app is a starter solution that contains a .NET MAUI Blazor Hybrid (native, cross-platform) app, a Blazor Web App, and a Razor class library (RCL) that contains the shared UI (Razor components) used by the native and web apps.

## Migrating a .NET MAUI Blazor Hybrid solution

Instead of [using the sample app](#net-maui-blazor-web-app-sample-app), you can migrate an existing .NET MAUI Blazor Hybrid app with the guidance in this section using Visual Studio.
Instead of [using the sample app](#net-maui-blazor-hybrid-and-web-app-sample-app), you can migrate an existing .NET MAUI Blazor Hybrid app with the guidance in this section using Visual Studio.

Add new project to the solution with the **Blazor Web App** project template. Select the following options:

Expand All @@ -39,8 +64,8 @@ Add new project to the solution with the **Blazor Web App** project template. Se
* **Interactivity location**: **Global**
* **Sample pages**: Unselected (disabled)

<!-- UPDATE 9.0 Check on PU issue and revise the following
for >=9.0 accordingly -->
<!-- UPDATE 9.0 Check on PU issue mentioned below and
revise accordingly. -->

The **Interactivity location** setting to **Global** is important because MAUI apps always run interactively and throw errors on Razor component pages that explicitly specify a render mode. If you don't use a global render mode, you must implement the approach described in the [Use Blazor render modes](#use-blazor-render-modes) section after following the guidance in this section. For more information, see [BlazorWebView needs a way to enable overriding ResolveComponentForRenderMode (`dotnet/aspnetcore` #51235)](https://github.com/dotnet/aspnetcore/issues/51235).

Expand Down Expand Up @@ -149,6 +174,8 @@ Run the Blazor Web App project by selecting the Blazor Web App project in **Solu

If you receive a build error that the RCL's assembly can't be resolved, build the RCL project first. If any MAUI project resource errors occur on build, rebuild the MAUI project to clear the errors.

:::moniker-end

## Use Blazor render modes

Use the guidance in one of the following subsections that matches your app's specifications for applying Blazor [render modes](xref:blazor/components/render-modes) for a given interactivity location in the Blazor Web App but ignore the render mode assignments in the MAUI project.
Expand All @@ -163,6 +190,21 @@ Render mode and interactivity specification subsections:

### Global Server interactivity

:::moniker range=">= aspnetcore-9.0"

* Interactive render mode: **Server**
* Interactivity location: **Global**
* Solution projects
* MAUI (`MauiBlazorWeb`)
* Blazor Web App (`MauiBlazorWeb.Web`)
* RCL (`MauiBlazorWeb.Shared`): Contains the shared Razor components without setting render modes in each component.

Project references: `MauiBlazorWeb` and `MauiBlazorWeb.Web` have a project reference to `MauiBlazorWeb.Shared`.

:::moniker-end

:::moniker range="< aspnetcore-9.0"

* Interactive render mode: **Server**
* Interactivity location: **Global**
* Solution projects
Expand All @@ -172,8 +214,30 @@ Render mode and interactivity specification subsections:

Project references: `MauiBlazorWeb.Maui` and `MauiBlazorWeb.Web` have a project reference to `MauiBlazorWeb.Shared`.

:::moniker-end

### Global Auto or WebAssembly interactivity

:::moniker range=">= aspnetcore-9.0"

* Interactive render mode: **Auto** or **WebAssembly**
* Interactivity location: **Global**
* Solution projects
* MAUI (`MauiBlazorWeb`)
* Blazor Web App
* Server project: `MauiBlazorWeb.Web`
* Client project: `MauiBlazorWeb.Web.Client`
* RCL (`MauiBlazorWeb.Shared`): Contains the shared Razor components without setting render modes in each component.

Project references:

* `MauiBlazorWeb`, `MauiBlazorWeb.Web`, and `MauiBlazorWeb.Web.Client` projects have a project reference to `MauiBlazorWeb.Shared`.
* `MauiBlazorWeb.Web` has a project reference to `MauiBlazorWeb.Web.Client`.

:::moniker-end

:::moniker range="< aspnetcore-9.0"

* Interactive render mode: **Auto** or **WebAssembly**
* Interactivity location: **Global**
* Solution projects
Expand All @@ -188,8 +252,25 @@ Project references:
* `MauiBlazorWeb.Maui`, `MauiBlazorWeb.Web`, and `MauiBlazorWeb.Web.Client` projects have a project reference to `MauiBlazorWeb.Shared`.
* `MauiBlazorWeb.Web` has a project reference to `MauiBlazorWeb.Web.Client`.

:::moniker-end

### Per-page/component Server interactivity

:::moniker range=">= aspnetcore-9.0"

* Interactive render mode: **Server**
* Interactivity location: **Per-page/component**
* Solution projects
* MAUI (`MauiBlazorWeb`): Calls `InteractiveRenderSettings.ConfigureBlazorHybridRenderModes` in `MauiProgram.cs`.
* Blazor Web App (`MauiBlazorWeb.Web`): Doesn't set an `@rendermode` directive attribute on the `HeadOutlet` and `Routes` components of the `App` component (`Components/App.razor`).
* RCL (`MauiBlazorWeb.Shared`): Contains the shared Razor components that set the `InteractiveServer` render mode in each component.

`MauiBlazorWeb` and `MauiBlazorWeb.Web` have a project reference to `MauiBlazorWeb.Shared`.

:::moniker-end

:::moniker range="< aspnetcore-9.0"

* Interactive render mode: **Server**
* Interactivity location: **Per-page/component**
* Solution projects
Expand All @@ -199,6 +280,8 @@ Project references:

`MauiBlazorWeb.Maui` and `MauiBlazorWeb.Web` have a project reference to `MauiBlazorWeb.Shared`.

:::moniker-end

Add the following `InteractiveRenderSettings` class to the RCL. The class properties are used to set component render modes.

The MAUI project is interactive by default, so no action is taken at the project level in the MAUI project other than calling `InteractiveRenderSettings.ConfigureBlazorHybridRenderModes`.
Expand Down Expand Up @@ -226,6 +309,26 @@ In the RCL's `_Imports.razor` file, add the following global static `@using` dir
### Per-page/component Auto interactivity

:::moniker range=">= aspnetcore-9.0"

* Interactive render mode: **Auto**
* Interactivity location: **Per-page/component**
* Solution projects
* MAUI (`MauiBlazorWeb`): Calls `InteractiveRenderSettings.ConfigureBlazorHybridRenderModes` in `MauiProgram.cs`.
* Blazor Web App
* Server project: `MauiBlazorWeb.Web`: Doesn't set an `@rendermode` directive attribute on the `HeadOutlet` and `Routes` components of the `App` component (`Components/App.razor`).
* Client project: `MauiBlazorWeb.Web.Client`
* RCL (`MauiBlazorWeb.Shared`): Contains the shared Razor components that set the `InteractiveAuto` render mode in each component.

Project references:

* `MauiBlazorWeb`, `MauiBlazorWeb.Web`, and `MauiBlazorWeb.Web.Client` have a project reference to `MauiBlazorWeb.Shared`.
* `MauiBlazorWeb.Web` has a project reference to `MauiBlazorWeb.Web.Client`.

:::moniker-end

:::moniker range="< aspnetcore-9.0"

* Interactive render mode: **Auto**
* Interactivity location: **Per-page/component**
* Solution projects
Expand All @@ -240,6 +343,8 @@ Project references:
* `MauiBlazorWeb.Maui`, `MauiBlazorWeb.Web`, and `MauiBlazorWeb.Web.Client` have a project reference to `MauiBlazorWeb.Shared`.
* `MauiBlazorWeb.Web` has a project reference to `MauiBlazorWeb.Web.Client`.

:::moniker-end

Add the following `InteractiveRenderSettings` class is added to the RCL. The class properties are used to set component render modes.

The MAUI project is interactive by default, so no action is taken at the project level in the MAUI project other than calling `InteractiveRenderSettings.ConfigureBlazorHybridRenderModes`.
Expand Down Expand Up @@ -267,6 +372,29 @@ In the RCL's `_Imports.razor` file, add the following global static `@using` dir
### Per-page/component WebAssembly interactivity

:::moniker range=">= aspnetcore-9.0"

* Interactive render mode: **WebAssembly**
* Interactivity location: **Per-page/component**
* Solution projects
* MAUI (`MauiBlazorWeb`)
* Blazor Web App
* Server project: `MauiBlazorWeb.Web`: Doesn't set an `@rendermode` directive attribute on the `HeadOutlet` and `Routes` components of the `App` component (`Components/App.razor`).
* Client project: `MauiBlazorWeb.Web.Client`
* RCLs
* `MauiBlazorWeb.Shared`
* `MauiBlazorWeb.Shared.Client`: Contains the shared Razor components that set the `InteractiveWebAssembly` render mode in each component. The `.Shared.Client` RCL is maintained separately from the `.Shared` RCL because the app should maintain the components that are required to run on WebAssembly separately from the components that run on server and that stay on the server.

Project references:

* `MauiBlazorWeb` and `MauiBlazorWeb.Web` have project references to `MauiBlazorWeb.Shared`.
* `MauiBlazorWeb.Web` has a project reference to `MauiBlazorWeb.Web.Client`.
* `MauiBlazorWeb.Web.Client` and `MauiBlazorWeb.Shared` have a project reference to `MauiBlazorWeb.Shared.Client`.

:::moniker-end

:::moniker range="< aspnetcore-9.0"

* Interactive render mode: **WebAssembly**
* Interactivity location: **Per-page/component**
* Solution projects
Expand All @@ -284,6 +412,8 @@ Project references:
* `MauiBlazorWeb.Web` has a project reference to `MauiBlazorWeb.Web.Client`.
* `MauiBlazorWeb.Web.Client` and `MauiBlazorWeb.Shared` have a project reference to `MauiBlazorWeb.Shared.Client`.

:::moniker-end

Add the following <xref:Microsoft.AspNetCore.Components.Routing.Router.AdditionalAssemblies%2A> parameter to the `Router` component instance for the `MauiBlazorWeb.Shared.Client` project assembly (via its `_Imports` file) in the `MauiBlazorWeb.Shared` project's `Routes.razor` file:

```razor
Expand Down Expand Up @@ -365,19 +495,46 @@ In the `_Imports.razor` file of the `.Shared.Client` RCL, add `@using static Int

The following example demonstrates how to use an interface to call into different implementations across the web app and the native (MAUI) app. The following example creates a component that displays the device form factor. Use the MAUI abstraction layer for native apps and provide an implementation for the web app.

In the Razor class library (RCL), create an `Interfaces` folder and add file named `IFormFactor.cs` with the following code.
In the Razor class library (RCL), an `Interfaces` folder contains an `IFormFactor` interface.

`Interfaces/IFormFactor.cs`:

:::code language="csharp" source="~/../blazor-samples/8.0/MauiBlazorWeb/MauiBlazorWeb.Shared/Interfaces/IFormFactor.cs":::

In the RCL's `Components` folder, add the following `DeviceFormFactor` component.
:::moniker range=">= aspnetcore-9.0"

The `Home` component (`Components/Pages/Home.razor`) of the RCL displays the form factor and platform.

`Components/Pages/Home.razor`:

```razor
@page "/"
@using MyApp.Shared.Services
@inject IFormFactor FormFactor
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app running on <em>@factor</em> using <em>@platform</em>.
@code {
private string factor => FormFactor.GetFormFactor();
private string platform => FormFactor.GetPlatform();
}
```

:::moniker-end

:::moniker range="< aspnetcore-9.0"

The following `DeviceFormFactor` component is present in the RCL's `Components` folder.

`Components/Pages/DeviceFormFactor.razor`:

:::code language="razor" source="~/../blazor-samples/8.0/MauiBlazorWeb/MauiBlazorWeb.Shared/Components/Pages/DeviceFormFactor.razor":::

In the RCL, add an entry for the `DeviceFormFactor` component to the navigation menu.
In the RCL, an entry for the `DeviceFormFactor` component is part of the navigation menu in the `NavMenu` component.

In `Components/Layout/NavMenu.razor`:

Expand All @@ -389,43 +546,51 @@ In `Components/Layout/NavMenu.razor`:
</div>
```

Provide implementations in the web and native apps.
:::moniker-end

In the Blazor Web App, add a folder named `Services`. Add a file to the `Services` folder named `FormFactor.cs` with the following code.
The web and native apps contain the implementations for `IFormFactor`.

In the Blazor Web App, a folder named `Services` contains the following `FormFactor.cs` file with the `FormFactor` implementation for web app use.

`Services/FormFactor.cs` (Blazor Web App project):

:::code language="csharp" source="~/../blazor-samples/8.0/MauiBlazorWeb/MauiBlazorWeb.Web/Services/FormFactor.cs":::

In the MAUI project, add a folder named `Services` and add a file named `FormFactor.cs`. The MAUI abstractions layer is used to write code that works on all native device platforms.
In the MAUI project, a folder named `Services` contains the following `FormFactor.cs` file with the `FormFactor` implementation for native use. The MAUI abstractions layer is used to write code that works on all native device platforms.

`Services/FormFactor.cs` (MAUI project):


<!-- NOTE: The following link points to the 8.0
sample but we don't need to update it
for 9.0 or later coverage as long as
the code in the project template
doesn't change. -->

:::code language="csharp" source="~/../blazor-samples/8.0/MauiBlazorWeb/MauiBlazorWeb.Maui/Services/FormFactor.cs":::

Use dependency injection to obtain the implementations of these services.
Dependency injection is used to obtain the implementations of these services.

In the MAUI project, open the `MauiProgram.cs` file and add the following `using` statements to the top of the file:
In the MAUI project, the `MauiProgram.cs` file has following `using` statements at the top of the file:

```csharp
using MauiBlazorWeb.Maui.Services;
using MauiBlazorWeb.Services;
using MauiBlazorWeb.Shared.Interfaces;
```

Immediately before the call to `builder.Build()`, add the following code to add device-specific services used by the RCL:
Immediately before the call to `builder.Build()`, `FormFactor` is registered to add device-specific services used by the RCL:

```csharp
builder.Services.AddSingleton<IFormFactor, FormFactor>();
```

In the Blazor Web App, open the `Program` file and add the following `using` statements to the top of the file:
In the Blazor Web App, the `Program` file has the following `using` statements at the top of the file:

```csharp
using MauiBlazorWeb.Shared.Interfaces;
using MauiBlazorWeb.Web.Services;
```

Immediately before the call to `builder.Build()`, add the following code to add device-specific services used by the RCL:
Immediately before the call to `builder.Build()`, `FormFactor` is registered to add device-specific services used by the Blazor Web App:

```csharp
builder.Services.AddScoped<IFormFactor, FormFactor>();
Expand Down

0 comments on commit 3b62b2a

Please sign in to comment.