使用 Blazor Web 应用生成 .NET MAUIBlazor Hybrid 应用

本文介绍如何使用一个通过 Razor 类库 (RCL) 使用共享用户界面的 Blazor Web 应用生成 .NET MAUIBlazor Hybrid 应用。

先决条件和预备步骤

有关先决条件和预备步骤,请参阅生成 .NET MAUIBlazor Hybrid 应用。 建议先按照 .NET MAUIBlazor Hybrid 教程设置用于进行 .NET MAUI 开发的本地系统,然后再参照本文指南操作。

.NET MAUIBlazor Web 应用示例应用

Blazor 示例 GitHub 存储库 (dotnet/blazor-samples)(.NET 8 或更高版本)获取示例应用(名为 MauiBlazorWeb)。

示例应用是一个初学者解决方案,其中包含 .NET MAUIBlazor Hybrid(本机、跨平台)应用、Blazor Web 应用以及 Razor 类库 (RCL)(包含本机和 Web 应用使用的共享 UI(Razor 组件))。

迁移 .NET MAUIBlazor Hybrid 解决方案

可以按照此部分的指南使用 Visual Studio 迁移现有 .NET MAUIBlazor Hybrid 应用,而不是使用示例应用

使用 Blazor Web 应用项目模板将新项目添加到解决方案。 选择以下选项:

  • 项目名称:在解决方案的名称后追加 .Web。 本文中的示例假设使用以下命名方式:
    • 解决方案:MauiBlazorWeb
    • MAUI 项目:MauiBlazorWeb.Maui
    • Blazor Web 应用:MauiBlazorWeb.Web
    • Razor 类库 (RCL)(在后面的步骤中添加):MauiBlazorWeb.Shared
  • 身份验证类型:无
  • HTTPS 配置:选中(启用)
  • 交互式呈现模式:服务器
  • 交互位置:全局
  • 示例页面:未选(禁用)

将“交互位置”设置为“全局”非常重要,因为 MAUI 应用始终以交互方式运行,并会在显式指定呈现模式的 Razor 组件页面上引发错误。 如果不使用全局呈现模式,则需要在按照本部分指南操作后实现“使用 Blazor 呈现模式”部分中所述的方法。 有关详细信息,请参阅 BlazorWebView 需要一种方法来启用 ResolveComponentForRenderMode 的重写 (dotnet/aspnetcore #51235)

将新的 Razor 类库 (RCL) 项目添加到解决方案。 本文中的示例假设项目命名为 MauiBlazorWeb.Shared。 将项目添加到解决方案时,请勿选择“支持页面和视图”。

从 MAUI 项目和 Blazor Web App 项目添加对 RCL 的项目引用。

Components 文件夹及其所有内容从 MAUI 项目移动到 RCL。 确认已从 MAUI 项目中删除 Components 文件夹。

提示

在 Visual Studio 中移动文件夹或文件时,通过右键单击使用剪切和粘贴操作,使用键盘命令或快捷菜单。 在 Visual Studio 中拖动文件夹只能将其从一个位置复制到另一个位置,得需要额外步骤才能删除原始文件夹。

css 文件夹从 MAUI 项目的 wwwroot 文件夹移动到 RCL 的 wwwroot 文件夹中。

从 RCL 的 wwwroot 文件夹中删除以下文件:

  • background.png
  • exampleJsInterop.js

在 RCL 中,将根 _Imports.razor 文件替换为 RCL Components 文件夹中的文件,重写 RCL Components 文件夹中的现有文件并删除原始文件。 移动该文件后,打开该文件并重命名最后两个 @using 语句以匹配 RCL 的命名空间。 在以下示例中,RCL 的命名空间是 MauiBlazorWeb.Shared

@using MauiBlazorWeb.Shared
@using MauiBlazorWeb.Shared.Components

在 RCL 项目的根目录中,删除以下文件:

  • Component1.razor
  • ExampleJsInterop.cs

在 RCL 中,打开 Components/Routes.razor 文件并将 MauiProgram 更改为 Routes

- <Router AppAssembly="@typeof(MauiProgram).Assembly">
+ <Router AppAssembly="@typeof(Routes).Assembly">

在 MAUI 项目中打开 MainPage.xaml 文件。 在 ContentPage 属性中添加对 RCL 的 xmlns:shared 引用。 在以下示例中,RCL 的命名空间是 MauiBlazorWeb.Shared。 为 clr-namespaceassembly 设置正确的值:

xmlns:shared="clr-namespace:MauiBlazorWeb.Shared;assembly=MauiBlazorWeb.Shared"

此外,在 MainPage.xaml 文件中,将 BlazorWebView 根组件 ComponentTypelocal 更新为 shared

- <RootComponent Selector="#app" ComponentType="{x:Type local:Components.Routes}" />
+ <RootComponent Selector="#app" ComponentType="{x:Type shared:Components.Routes}" />

在 MAUI 项目中,打开 wwwroot/index.html 文件并更改样式表以指向 RCL 的静态资产路径。

删除以下各行:

- <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
- <link rel="stylesheet" href="css/app.css" />

将前面的行替换为以下标记。 在以下示例中,RCL 的静态资产路径为 _content/MauiBlazorWeb.Shared/

<link rel="stylesheet" href="_content/MauiBlazorWeb.Shared/css/bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="_content/MauiBlazorWeb.Shared/css/app.css" />

在 Blazor Web 应用中,打开 _Imports.razor 文件,并添加以下两个用于 RCL 的 @using 语句。 在以下示例中,RCL 的命名空间是 MauiBlazorWeb.Shared

@using MauiBlazorWeb.Shared
@using MauiBlazorWeb.Shared.Components

在 Blazor Web 应用项目中,打开 App 组件 (Components/App.razor)。 删除 app.css 样式表:

- <link rel="stylesheet" href="app.css" />

将前面的行替换为 RCL 的静态资产样式表引用。 在以下示例中,RCL 的静态资产路径为 _content/MauiBlazorWeb.Shared/

<link rel="stylesheet" href="_content/MauiBlazorWeb.Shared/css/bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="_content/MauiBlazorWeb.Shared/css/app.css" />

在 Blazor Web 应用项目中,删除以下文件夹和文件:

  • Components/Layout 文件夹
  • Components/Routes.razor
  • Components/Pages/Home.razor
  • wwwroot/app.css

打开 Blazor Web 应用的 Program.cs 文件,并向应用中再添加一个用于 RCL 的程序集。 在以下示例中,RCL 的命名空间是 MauiBlazorWeb.Shared

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

通过在解决方案资源管理器中选择项目并使用 Visual Studio 的“开始”按钮来运行 MAUI 项目。

通过在解决方案资源管理器中选择 Blazor Web 应用项目并使用 Visual Studio 的“开始”按钮和 https 生成配置来运行 Blazor Web 应用项目。

如果收到无法解析 RCL 程序集的生成错误,请先生成 RCL 项目。 如果生成时出现任何 MAUI 项目资源错误,则重新生成 MAUI 项目以消除错误。

使用 Blazor 呈现模式

在 Blazor Web 应用中为给定的交互位置应用Blazor呈现模式时,请使用以下子部分中与你的应用规范相匹配的指南,但请忽略 MAUI 项目中的呈现模式分配。

呈现模式和交互规范子部分:

全局服务器交互

  • 交互式呈现模式:服务器
  • 交互位置:全局
  • 解决方案项目
    • MAUI (MauiBlazorWeb.Maui)
    • Blazor Web 应用 (MauiBlazorWeb.Web)
    • RCL (MauiBlazorWeb.Shared):包含共享的 Razor 组件,而不用在每个组件中都设置呈现模式。

项目引用:MauiBlazorWeb.MauiMauiBlazorWeb.Web 具有对 MauiBlazorWeb.Shared 的项目引用。

全局自动或 WebAssembly 交互

  • 交互式呈现模式:自动或 WebAssembly
  • 交互位置:全局
  • 解决方案项目
    • MAUI (MauiBlazorWeb.Maui)
    • Blazor Web 应用
      • 服务器项目:MauiBlazorWeb.Web
      • 客户项目:MauiBlazorWeb.Web.Client
    • RCL (MauiBlazorWeb.Shared):包含共享的 Razor 组件,而不用在每个组件中都设置呈现模式。

项目引用:

  • MauiBlazorWeb.MauiMauiBlazorWeb.WebMauiBlazorWeb.Web.Client 项目具有对 MauiBlazorWeb.Shared 的项目引用。
  • MauiBlazorWeb.Web 具有对 MauiBlazorWeb.Web.Client 的项目引用。

每页/每组件服务器交互

  • 交互式呈现模式:服务器
  • 交互位置:每页/每组件
  • 解决方案项目
    • MAUI (MauiBlazorWeb.Maui):在 InteractiveRenderSettings.ConfigureBlazorHybridRenderModes 中调用 MauiProgram.cs
    • Blazor Web 应用 (MauiBlazorWeb.Web):不对 App 组件 (Components/App.razor) 的 HeadOutletRoutes 组件设置 @rendermode 指令属性。
    • RCL (MauiBlazorWeb.Shared):包含在每个组件中设置 InteractiveServer 呈现模式的共享 Razor 组件。

MauiBlazorWeb.MauiMauiBlazorWeb.Web 具有对 MauiBlazorWeb.Shared 的项目引用。

将以下 InteractiveRenderSettings 类添加到 RCL。 类属性用于设置组件呈现模式。

默认情况下,MAUI 项目是交互式的,因此 MAUI 项目中在项目级别,除了调用 InteractiveRenderSettings.ConfigureBlazorHybridRenderModes,不执行任何其他操作。

对于 Web 客户端上的 Blazor Web 应用,属性值从 RenderMode 分配。 将组件加载到 MAUI 项目本机客户端的 BlazorWebView 时,呈现模式是未分配的 (null),因为 MAUI 项目在调用 ConfigureBlazorHybridRenderModes 时显式将呈现模式属性设置为 null

InteractiveRenderSettings.cs

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;

namespace MauiBlazorWeb.Shared;

public static class InteractiveRenderSettings
{
    public static IComponentRenderMode? InteractiveServer { get; set; } = 
        RenderMode.InteractiveServer;
    public static IComponentRenderMode? InteractiveAuto { get; set; } = 
        RenderMode.InteractiveAuto;
    public static IComponentRenderMode? InteractiveWebAssembly { get; set; } = 
        RenderMode.InteractiveWebAssembly;

    public static void ConfigureBlazorHybridRenderModes()
    {
        InteractiveServer = null;
        InteractiveAuto = null;
        InteractiveWebAssembly = null;
    }
}

MauiProgram.csMauiProgram.CreateMauiApp 中,调用 ConfigureBlazorHybridRenderModes

InteractiveRenderSettings.ConfigureBlazorHybridRenderModes();

在 RCL 的 _Imports.razor 文件中,添加以下全局静态 @using 指令,使类的属性可供组件使用:

@using static InteractiveRenderSettings

注意

通过 RCL 的 InteractiveRenderSettings 类属性分配呈现模式的操作与典型的独立 Blazor Web 应用不同。 在 Blazor Web 应用中,呈现模式通常由 RenderMode 通过 Blazor Web 应用 _Import 文件中的 @using static Microsoft.AspNetCore.Components.Web.RenderMode 语句提供。

每页/每组件自动交互

  • 交互式呈现模式:自动
  • 交互位置:每页/每组件
  • 解决方案项目
    • MAUI (MauiBlazorWeb.Maui):在 InteractiveRenderSettings.ConfigureBlazorHybridRenderModes 中调用 MauiProgram.cs
    • Blazor Web 应用
      • 服务器项目:MauiBlazorWeb.Web:不对 App 组件 (Components/App.razor) 的 HeadOutletRoutes 组件设置 @rendermode 指令属性。
      • 客户项目:MauiBlazorWeb.Web.Client
    • RCL (MauiBlazorWeb.Shared):包含在每个组件中设置 InteractiveAuto 呈现模式的共享 Razor 组件。

项目引用:

  • MauiBlazorWeb.MauiMauiBlazorWeb.WebMauiBlazorWeb.Web.Client 具有对 MauiBlazorWeb.Shared 的项目引用。
  • MauiBlazorWeb.Web 具有对 MauiBlazorWeb.Web.Client 的项目引用。

将以下 InteractiveRenderSettings 类添加到 RCL。 类属性用于设置组件呈现模式。

默认情况下,MAUI 项目是交互式的,因此 MAUI 项目中在项目级别,除了调用 InteractiveRenderSettings.ConfigureBlazorHybridRenderModes,不执行任何其他操作。

对于 Web 客户端上的 Blazor Web 应用,属性值从 RenderMode 分配。 将组件加载到 MAUI 项目本机客户端的 BlazorWebView 时,呈现模式是未分配的 (null),因为 MAUI 项目在调用 ConfigureBlazorHybridRenderModes 时显式将呈现模式属性设置为 null

InteractiveRenderSettings.cs

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;

namespace MauiBlazorWeb.Shared;

public static class InteractiveRenderSettings
{
    public static IComponentRenderMode? InteractiveServer { get; set; } = 
        RenderMode.InteractiveServer;
    public static IComponentRenderMode? InteractiveAuto { get; set; } = 
        RenderMode.InteractiveAuto;
    public static IComponentRenderMode? InteractiveWebAssembly { get; set; } = 
        RenderMode.InteractiveWebAssembly;

    public static void ConfigureBlazorHybridRenderModes()
    {
        InteractiveServer = null;
        InteractiveAuto = null;
        InteractiveWebAssembly = null;
    }
}

MauiProgram.csMauiProgram.CreateMauiApp 中,调用 ConfigureBlazorHybridRenderModes

InteractiveRenderSettings.ConfigureBlazorHybridRenderModes();

在 RCL 的 _Imports.razor 文件中,添加以下全局静态 @using 指令,使类的属性可供组件使用:

@using static InteractiveRenderSettings

注意

通过 RCL 的 InteractiveRenderSettings 类属性分配呈现模式的操作与典型的独立 Blazor Web 应用不同。 在 Blazor Web 应用中,呈现模式通常由 RenderMode 通过 Blazor Web 应用 _Import 文件中的 @using static Microsoft.AspNetCore.Components.Web.RenderMode 语句提供。

每页/每组件 WebAssembly 交互

  • 交互式呈现模式:WebAssembly
  • 交互位置:每页/每组件
  • 解决方案项目
    • MAUI (MauiBlazorWeb.Maui)
    • Blazor Web 应用
      • 服务器项目:MauiBlazorWeb.Web:不对 App 组件 (Components/App.razor) 的 HeadOutletRoutes 组件设置 @rendermode 指令属性。
      • 客户项目:MauiBlazorWeb.Web.Client
    • RCL
      • MauiBlazorWeb.Shared
      • MauiBlazorWeb.Shared.Client:包含在每个组件中设置 InteractiveWebAssembly 呈现模式的共享 Razor 组件。 .Shared.Client RCL 与 .Shared RCL 分开维护,因为应用须将需要在 WebAssembly 上运行的组件与在服务器上运行且保持在服务器上的组件分开维护。

项目引用:

  • MauiBlazorWeb.MauiMauiBlazorWeb.Web 具有对 MauiBlazorWeb.Shared 的项目引用。
  • MauiBlazorWeb.Web 具有对 MauiBlazorWeb.Web.Client 的项目引用。
  • MauiBlazorWeb.Web.ClientMauiBlazorWeb.Shared 具有对 MauiBlazorWeb.Shared.Client 的项目引用。

MauiBlazorWeb.Shared 项目的 Routes.razor 文件中,将以下 AdditionalAssemblies 参数添加到 MauiBlazorWeb.Shared.Client 项目程序集的 Router 组件实例(通过其 _Imports 文件):

<Router AppAssembly="@typeof(Routes).Assembly" 
        AdditionalAssemblies="new [] { typeof(MauiBlazorWeb.Shared.Client._Imports).Assembly }">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(Components.Layout.MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
</Router>

MauiBlazorWeb.Web 项目的 Program.cs 文件中使用以下 AddAdditionalAssemblies 调用添加 MauiBlazorWeb.Shared.Client 项目程序集(通过其 _Imports 文件):

app.MapRazorComponents<App>()    
    .AddInteractiveWebAssemblyRenderMode()
    .AddAdditionalAssemblies(typeof(MauiBlazorWeb.Shared._Imports).Assembly)
    .AddAdditionalAssemblies(typeof(MauiBlazorWeb.Shared.Client._Imports).Assembly); 

将以下 InteractiveRenderSettings 类添加到 .Shared.Client RCL。 类属性用于为基于服务器的组件设置组件呈现模式。

默认情况下,MAUI 项目是交互式的,因此 MAUI 项目中在项目级别,除了调用 InteractiveRenderSettings.ConfigureBlazorHybridRenderModes,不执行任何其他操作。

对于 Web 客户端上的 Blazor Web 应用,属性值从 RenderMode 分配。 将组件加载到 MAUI 项目本机客户端的 BlazorWebView 时,呈现模式是未分配的 (null),因为 MAUI 项目在调用 ConfigureBlazorHybridRenderModes 时显式将呈现模式属性设置为 null

InteractiveRenderSettings.cs (.Shared.Client RCL):

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;

namespace MauiBlazorWeb.Shared;

public static class InteractiveRenderSettings
{
    public static IComponentRenderMode? InteractiveServer { get; set; } = 
        RenderMode.InteractiveServer;
    public static IComponentRenderMode? InteractiveAuto { get; set; } = 
        RenderMode.InteractiveAuto;
    public static IComponentRenderMode? InteractiveWebAssembly { get; set; } = 
        RenderMode.InteractiveWebAssembly;

    public static void ConfigureBlazorHybridRenderModes()
    {
        InteractiveServer = null;
        InteractiveAuto = null;
        InteractiveWebAssembly = null;
    }
}

.Shared RCL 中添加了与之前略有不同的 InteractiveRenderSettings 类。 在添加到 .Shared RCL 的类中,将调用 .Shared.Client RCL 的 InteractiveRenderSettings.ConfigureBlazorHybridRenderModes。 这可确保在 MAUI 客户端上呈现的 WebAssembly 组件的呈现模式是未分配的 (null),因为它们在本机客户端上默认是交互式的。

InteractiveRenderSettings.cs (.Shared RCL):

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;

namespace MauiBlazorWeb.Shared
{
    public static class InteractiveRenderSettings
    {
        public static IComponentRenderMode? InteractiveServer { get; set; } = 
            RenderMode.InteractiveServer;
        public static IComponentRenderMode? InteractiveAuto { get; set; } = 
            RenderMode.InteractiveAuto;
        public static IComponentRenderMode? InteractiveWebAssembly { get; set; } = 
            RenderMode.InteractiveWebAssembly;

        public static void ConfigureBlazorHybridRenderModes()
        {
            InteractiveServer = null;
            InteractiveAuto = null;
            InteractiveWebAssembly = null;
            MauiBlazorWeb.Shared.Client.InteractiveRenderSettings
                .ConfigureBlazorHybridRenderModes();
        }
    }
}

MauiProgram.csMauiProgram.CreateMauiApp 中,调用 ConfigureBlazorHybridRenderModes

InteractiveRenderSettings.ConfigureBlazorHybridRenderModes();

.Shared.Client RCL 的 _Imports.razor 文件中,添加 @using static InteractiveRenderSettings 以使 InteractiveRenderSettings 类的属性可供组件使用:

@using static InteractiveRenderSettings

注意

通过 RCL 的 InteractiveRenderSettings 类属性分配呈现模式的操作与典型的独立 Blazor Web 应用不同。 在 Blazor Web 应用中,呈现模式通常由 RenderMode 通过 Blazor Web 应用 _Import 文件中的 @using static Microsoft.AspNetCore.Components.Web.RenderMode 语句提供。

使用接口来支持不同的设备实现

以下示例演示如何使用接口跨 Web 应用和本机 (MAUI) 应用调用不同的实现。 以下示例创建一个显示设备外形规格的组件。 为本机应用使用 MAUI 抽象层,并为 Web 应用提供实现。

在 Razor 类库 (RCL) 中使用以下代码创建 Interfaces 文件夹并添加名为 IFormFactor.cs 的文件。

Interfaces/IFormFactor.cs

namespace MauiBlazorWeb.Shared.Interfaces;

public interface IFormFactor
{
    public string GetFormFactor();
    public string GetPlatform();
}

在 RCL 的 Components 文件夹中,添加以下 DeviceFormFactor 组件。

Components/Pages/DeviceFormFactor.razor

@page "/device-form-factor"
@using MauiBlazorWeb.Shared.Interfaces
@inject IFormFactor FormFactor

<PageTitle>Form Factor</PageTitle>

<h1>Device Form Factor</h1>

<p>You are running on:</p>

<ul>
    <li>Form Factor: @factor</li>
    <li>Platform: @platform</li>
</ul>

<p>
    <em>This component is defined in the MauiBlazorWeb.Shared library.</em>
</p>

@code {
    private string factor => FormFactor.GetFormFactor();
    private string platform => FormFactor.GetPlatform();
}

在 RCL 中,将 DeviceFormFactor 组件的条目添加到导航菜单中。

Components/Layout/NavMenu.razor中:

<div class="nav-item px-3">
    <NavLink class="nav-link" href="device-form-factor">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Form Factor
    </NavLink>
</div>

在 Web 和本机应用中提供实现。

在 Blazor Web 应用中,添加名为 Services 的文件夹。 使用以下代码将文件添加到名为 FormFactor.csServices 文件夹中。

Services/FormFactor.cs(Blazor Web App 项目):

using MauiBlazorWeb.Shared.Interfaces;

namespace MauiBlazorWeb.Web.Services;

public class FormFactor : IFormFactor
{
    public string GetFormFactor()
    {
        return "Web";
    }
    public string GetPlatform()
    {
        return Environment.OSVersion.ToString();
    }
}

在 MAUI 项目中,添加名为 Services 的文件夹,并添加名为 FormFactor.cs 的文件。 MAUI 抽象层用于编写适用于所有本机设备平台的代码。

Services/FormFactor.cs(MAUI 项目):

using MauiBlazorWeb.Shared.Interfaces;

namespace MauiBlazorWeb.Maui.Services;

public class FormFactor : IFormFactor
{
    public string GetFormFactor()
    {
        return DeviceInfo.Idiom.ToString();
    }
    public string GetPlatform()
    {
        return DeviceInfo.Platform.ToString() + " - " + DeviceInfo.VersionString;
    }
}

使用依赖项注入获取这些服务的实现。

在 MAUI 项目中,打开 MauiProgram.cs 文件,并将以下 using 语句添加到文件顶部:

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

紧接着对 builder.Build() 的调用前,添加以下代码以添加 RCL 使用的设备特定的服务:

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

在 Blazor Web 应用中,打开 Program 文件,并将以下 using 语句添加到文件顶部:

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

紧接着对 builder.Build() 的调用前,添加以下代码以添加 RCL 使用的设备特定的服务:

builder.Services.AddScoped<IFormFactor, FormFactor>();

如果解决方案还通过 .Web.Client 项目来面向 WebAssembly,则 .Web.Client 项目中也需要上述 API 的实现。

还可以在 RCL 中使用编译器预处理器指令来实现不同的 UI,具体取决于应用正在运行的设备。 对于此方案,应用必须像 MAUI 应用那样以多目标方式面向 RCL。 有关示例,请参阅 BethMassi/BethTimeUntil GitHub 存储库

其他资源