使用 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-namespace
和 assembly
设置正确的值:
xmlns:shared="clr-namespace:MauiBlazorWeb.Shared;assembly=MauiBlazorWeb.Shared"
此外,在 MainPage.xaml
文件中,将 BlazorWebView 根组件 ComponentType 从 local
更新为 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 组件,而不用在每个组件中都设置呈现模式。
- MAUI (
项目引用:MauiBlazorWeb.Maui
和 MauiBlazorWeb.Web
具有对 MauiBlazorWeb.Shared
的项目引用。
全局自动或 WebAssembly 交互
- 交互式呈现模式:自动或 WebAssembly
- 交互位置:全局
- 解决方案项目
- MAUI (
MauiBlazorWeb.Maui
) - Blazor Web 应用
- 服务器项目:
MauiBlazorWeb.Web
- 客户项目:
MauiBlazorWeb.Web.Client
- 服务器项目:
- RCL (
MauiBlazorWeb.Shared
):包含共享的 Razor 组件,而不用在每个组件中都设置呈现模式。
- MAUI (
项目引用:
MauiBlazorWeb.Maui
、MauiBlazorWeb.Web
和MauiBlazorWeb.Web.Client
项目具有对MauiBlazorWeb.Shared
的项目引用。MauiBlazorWeb.Web
具有对MauiBlazorWeb.Web.Client
的项目引用。
每页/每组件服务器交互
- 交互式呈现模式:服务器
- 交互位置:每页/每组件
- 解决方案项目
- MAUI (
MauiBlazorWeb.Maui
):在InteractiveRenderSettings.ConfigureBlazorHybridRenderModes
中调用MauiProgram.cs
。 - Blazor Web 应用 (
MauiBlazorWeb.Web
):不对App
组件 (Components/App.razor
) 的HeadOutlet
和Routes
组件设置@rendermode
指令属性。 - RCL (
MauiBlazorWeb.Shared
):包含在每个组件中设置InteractiveServer
呈现模式的共享 Razor 组件。
- MAUI (
MauiBlazorWeb.Maui
和 MauiBlazorWeb.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.cs
的 MauiProgram.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
) 的HeadOutlet
和Routes
组件设置@rendermode
指令属性。 - 客户项目:
MauiBlazorWeb.Web.Client
- 服务器项目:
- RCL (
MauiBlazorWeb.Shared
):包含在每个组件中设置InteractiveAuto
呈现模式的共享 Razor 组件。
- MAUI (
项目引用:
MauiBlazorWeb.Maui
、MauiBlazorWeb.Web
和MauiBlazorWeb.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.cs
的 MauiProgram.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
) 的HeadOutlet
和Routes
组件设置@rendermode
指令属性。 - 客户项目:
MauiBlazorWeb.Web.Client
- 服务器项目:
- RCL
MauiBlazorWeb.Shared
MauiBlazorWeb.Shared.Client
:包含在每个组件中设置InteractiveWebAssembly
呈现模式的共享 Razor 组件。.Shared.Client
RCL 与.Shared
RCL 分开维护,因为应用须将需要在 WebAssembly 上运行的组件与在服务器上运行且保持在服务器上的组件分开维护。
- MAUI (
项目引用:
MauiBlazorWeb.Maui
和MauiBlazorWeb.Web
具有对MauiBlazorWeb.Shared
的项目引用。MauiBlazorWeb.Web
具有对MauiBlazorWeb.Web.Client
的项目引用。MauiBlazorWeb.Web.Client
和MauiBlazorWeb.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.cs
的 MauiProgram.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.cs
的 Services
文件夹中。
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 存储库。
其他资源
反馈
https://aka.ms/ContentUserFeedback。
即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:提交和查看相关反馈