多个托管 ASP.NET Core Blazor WebAssembly 应用
注意
此版本不是本文的最新版本。 有关本文的最新版本,请参阅 .NET 7 版本。
本文介绍如何将托管的 Blazor WebAssembly 应用配置为托管多个 Blazor WebAssembly 应用。
配置
选择与托管要求(端口/域托管(例如 :5001
/:5002
或 firstapp.com
/secondapp.com
)或路由子路径托管(例如 /FirstApp
和 /SecondApp
))匹配的本文版本。
对于当前托管选择,本文涵盖端口/域托管(例如 :5001
/:5002
或 firstapp.com
/secondapp.com
)。
在以下示例中:
- 托管的 Blazor WebAssembly 应用的项目名称是
MultipleBlazorApps
,位于名为MultipleBlazorApps
的文件夹中。 - 在添加第二个客户端应用程序之前,解决方案中的三个项目是
Client
文件夹中的MultipleBlazorApps.Client
、Server
文件夹中的MultipleBlazorApps.Server
和Shared
文件夹中的MultipleBlazorApps.Shared
。 - 初始(第一个)客户端应用是通过 Blazor WebAssembly 项目模板创建的解决方案的默认客户端项目。
- 向解决方案添加第二个客户端应用,
MultipleBlazorApps.SecondClient
位于名为SecondClient
的文件夹中。 - (可选)服务器项目 (
MultipleBlazorApps.Server
) 可以将页面或视图作为 Razor Pages 或 MVC 应用提供。 - 第一个客户端应用可在浏览器中通过端口 5001 或主机
firstapp.com
访问。 第二个客户端应用可在浏览器中通过端口 5002 或主机secondapp.com
访问。
对于当前选择,本文涵盖路由子路径托管(例如 /FirstApp
和 /SecondApp
)。
在以下示例中:
- 托管的 Blazor WebAssembly 应用的项目名称是
MultipleBlazorApps
,位于名为MultipleBlazorApps
的文件夹中。 - 在添加第二个客户端应用程序之前,解决方案中的三个项目是
Client
文件夹中的MultipleBlazorApps.Client
、Server
文件夹中的MultipleBlazorApps.Server
和Shared
文件夹中的MultipleBlazorApps.Shared
。 - 初始(第一个)客户端应用是通过 Blazor WebAssembly 项目模板创建的解决方案的默认客户端项目。
- 向解决方案添加第二个客户端应用,
MultipleBlazorApps.SecondClient
位于名为SecondClient
的文件夹中。 - (可选)服务器项目 (
MultipleBlazorApps.Server
) 可以将页面或视图作为正式的 Razor Pages 或 MVC 应用提供。 - 这两个客户端应用使用由
MultipleBlazorApps.Server
项目的Properties/launchSettings.json
文件在其applicationUrl
值中定义的默认端口。 第一个客户端应用可在/FirstApp
子路径的浏览器中访问。 第二个客户端应用可在/SecondApp
子路径的浏览器中访问。
本文中所示的示例需要对以下项进行其他配置:
- 直接访问示例主机域
firstapp.com
和secondapp.com
上的应用。 - 获取客户端应用的证书以启用 TLS/HTTPS 安全性。
- 将服务器应用配置为 Razor Pages 应用以获得以下功能:
- 将 Razor 组件集成到页面或视图中。
- 预呈现 Razor 组件。
上述配置超出了本文的范围。 有关更多信息,请参见以下资源:
- 托管和部署文章
- 在 ASP.NET Core 中强制使用 HTTPS
- 将 ASP.NET 核心Razor组件与托管Blazor WebAssembly解决方案中的 MVC 或 Razor Pages 集成
使用现有托管的 Blazor WebAssembly 解决方案,或者通过传递 -ho|--hosted
选项(如果使用 .NET CLI)或在 IDE 中创建项目后选中 Visual Studio 中的“ASP.NET Core 托管”复选框,从 Blazor WebAssembly 项目模板创建新的托管 Blazor WebAssembly 解决方案。
使用名为 MultipleBlazorApps
的解决方案文件夹并将项目命名为 MultipleBlazorApps
。
在名为 SecondClient
的解决方案中创建一个新文件夹。 在新文件夹中,添加名为 MultipleBlazorApps.SecondClient
的第二个 Blazor WebAssembly 客户端应用。 将项目添加为独立 Blazor WebAssembly 应用。 若要创建独立 Blazor WebAssembly 应用,如果使用 .NET CLI,请不要传递 -ho|--hosted
选项;如果使用 Visual Studio,请不要使用“ASP.NET Core 托管”复选框。
对 MultipleBlazorApps.SecondClient
项目进行以下更改:
- 将
FetchData
组件 (Pages/FetchData.razor
) 从Client/Pages
文件夹复制到SecondClient/Pages
文件夹。 此步骤是必需的,因为独立 Blazor WebAssembly 应用不会为天气数据调用 Server 项目的控制器,它使用静态数据文件。 通过将FetchData
组件复制到添加的项目,第二个客户端应用还会对服务器 API 进行 Web API 调用以获取天气数据。 - 删除
SecondClient/wwwroot/sample-data
文件夹,因为不使用文件夹中的weather.json
文件。
下表描述添加 SecondClient
文件夹和 MultipleBlazorApps.SecondClient
项目后,解决方案的文件夹和项目名称。
物理文件夹 | 项目名称 | 说明 |
---|---|---|
Client |
MultipleBlazorApps.Client |
Blazor WebAssembly 客户端应用 |
SecondClient |
MultipleBlazorApps.SecondClient |
Blazor WebAssembly 客户端应用 |
Server |
MultipleBlazorApps.Server |
ASP.NET Core 服务器应用 |
Shared |
MultipleBlazorApps.Shared |
共享资源项目 |
MultipleBlazorApps.Server
项目提供两个 Blazor WebAssembly 客户端应用,并通过 MVC 控制器向客户端应用的 FetchData
组件提供天气数据。 (可选)MultipleBlazorApps.Server
项目还可以将页面或视图作为传统的 Razor Pages 或 MVC 应用提供。 本文稍后将介绍启用提供页面或视图的步骤。
注意
本文中的演示将 FirstApp
的静态 web 资产路径名称用于 MultipleBlazorApps.Client
项目 SecondApp
和 MultipleBlazorApps.SecondClient
项目。 名称“FirstApp
”和“SecondApp
”仅用于演示目的。 可以接受其他名称来区分客户端应用,例如 App1
/App2
、Client1
/Client2
、1
/2
或任何类似的命名方案。
通过端口或域将请求路由到客户端应用时,“FirstApp
”和“SecondApp
”均在内部使用以路由请求并为静态资产提供响应,不显示在浏览器的地址栏中。
注意
本文中的演示将 FirstApp
的静态 web 资产路径名称用于 MultipleBlazorApps.Client
项目 SecondApp
和 MultipleBlazorApps.SecondClient
项目。 名称“FirstApp
”和“SecondApp
”仅用于演示目的。 可以接受其他名称来区分客户端应用,例如 App1
/App2
、Client1
/Client2
、1
/2
或任何类似的命名方案。
“FirstApp
”和“SecondApp
”也会显示在浏览器的地址栏中,因为请求会使用这些名称路由到两个客户端应用。 支持其他有效的 URL 路由段,路由段并不是非常需要与用于在内部路由静态 Web 资产的名称匹配。 将“FirstApp
”和“SecondApp
”用于内部静态资产路由和应用请求路由只是为了本文示例方便。
在第一个客户端应用的项目文件 (MultipleBlazorApps.Client.csproj
) 中,将 <StaticWebAssetBasePath>
属性 添加到值为 FirstApp
的 <PropertyGroup>
,以设置项目静态资产的基路径:
<StaticWebAssetBasePath>FirstApp</StaticWebAssetBasePath>
在 MultipleBlazorApps.SecondClient
应用的项目文件 (MultipleBlazorApps.SecondClient.csproj
) 中,执行以下操作:
将
<StaticWebAssetBasePath>
属性添加到值为SecondApp
的<PropertyGroup>
:<StaticWebAssetBasePath>SecondApp</StaticWebAssetBasePath>
将
MultipleBlazorApps.Shared
项目的项目引用添加到<ItemGroup>
:<ItemGroup> <ProjectReference Include="..\Shared\MultipleBlazorApps.Shared.csproj" /> </ItemGroup>
在服务器应用的项目文件 (Server/MultipleBlazorApps.Server.csproj
) 中,为 <ItemGroup>
中添加的 MultipleBlazorApps.SecondClient
客户端应用创建项目引用:
<ProjectReference Include="..\SecondClient\MultipleBlazorApps.SecondClient.csproj" />
在服务器应用的 Properties/launchSettings.json
文件中,配置 Kestrel 配置文件 (MultipleBlazorApps.Server
) 的 applicationUrl
,以访问位于端口 5001 和 5002 的客户端应用。 如果将本地环境配置为使用示例域,则 applicationUrl
的 URL 可以使用 firstapp.com
和 secondapp.com
,而不是使用端口。
注意
本演示中端口的使用让你无需配置本地托管环境就能够在本地浏览器中访问客户端项目,以便 Web 浏览器可以通过主机配置访问客户端应用 firstapp.com
和 secondapp.com
。 在生产场景中,典型的配置是使用子域来区分客户端应用。
例如:
- 端口已从此演示的配置中删除。
- 主机已更改为使用子域,例如
www.contoso.com
(适用于站点访问者)和admin.contoso.com
(适用于管理员)。 - 可以为其他客户端应用包含其他主机,如果服务器应用也是提供页面或视图的 Razor Pages 或 MVC 应用,则至少需要再加一个主机。
如果计划从服务器应用提供页面或视图,请使用 Properties/launchSettings.json
文件中的以下 applicationUrl
设置,该设置允许以下访问:
- (可选)Razor Pages 或 MVC 应用(
MultipleBlazorApps.Server
项目)在端口 5000 处响应请求。 - 对第一个客户端(
MultipleBlazorApps.Client
项目)的请求的响应位于端口 5001。 - 对第二个客户端(
MultipleBlazorApps.SecondClient
项目)的请求的响应位于端口 5002。
"applicationUrl": "https://localhost:5000;https://localhost:5001;https://localhost:5002",
如果不计划服务器应用提供页面或视图,并且只为 Blazor WebAssembly 客户端应用提供服务,请使用以下设置,该设置允许以下访问:
- 第一个客户端应用在端口 5001 上做出响应。
- 第二个客户端应用在端口 5002 上做出响应。
"applicationUrl": "https://localhost:5001;https://localhost:5002",
在服务器应用的 Program.cs
文件中,删除在调用 UseHttpsRedirection 后显示的以下代码:
如果计划从服务器应用提供页面或视图,请删除以下代码行:
- app.UseBlazorFrameworkFiles();
- app.MapFallbackToFile("index.html");
如果计划服务器应用仅提供 Blazor WebAssembly 客户端应用,请删除以下代码:
- app.UseBlazorFrameworkFiles(); ... - app.UseRouting(); - app.MapRazorPages(); - app.MapControllers(); - app.MapFallbackToFile("index.html");
保留静态文件中间件:
app.UseStaticFiles();
添加将请求映射到客户端应用的中间件。 以下示例将中间件配置为在第一个客户端应用的请求端口为 5001 或第二个客户端应用的请求端口为 5002,或者请求主机为第一个客户端应用的
firstapp.com
或第二个客户端应用的secondapp.com
时运行。注意
在具有本地浏览器的本地系统上使用主机 (
firstapp.com
/secondapp.com
) 需要额外的配置,这超出了本文的范围。 对于此场景的本地测试,建议使用端口。 典型的生产应用配置为使用子域,例如www.contoso.com
(适用于站点访问者)和admin.contoso.com
(适用于管理员)。 使用适当的 DNS 和服务器配置(这超出了本文的范围,具体取决于所使用的技术),应用会在以下代码中指定的任何主机上响应请求。在从
Program.cs
中删除app.UseBlazorFrameworkFiles();
行的位置放置以下代码:app.MapWhen(ctx => ctx.Request.Host.Port == 5001 || ctx.Request.Host.Equals("firstapp.com"), first => { first.Use((ctx, nxt) => { ctx.Request.Path = "/FirstApp" + ctx.Request.Path; return nxt(); }); first.UseBlazorFrameworkFiles("/FirstApp"); first.UseStaticFiles(); first.UseStaticFiles("/FirstApp"); first.UseRouting(); first.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapFallbackToFile("/FirstApp/{*path:nonfile}", "FirstApp/index.html"); }); }); app.MapWhen(ctx => ctx.Request.Host.Port == 5002 || ctx.Request.Host.Equals("secondapp.com"), second => { second.Use((ctx, nxt) => { ctx.Request.Path = "/SecondApp" + ctx.Request.Path; return nxt(); }); second.UseBlazorFrameworkFiles("/SecondApp"); second.UseStaticFiles(); second.UseStaticFiles("/SecondApp"); second.UseRouting(); second.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapFallbackToFile("/SecondApp/{*path:nonfile}", "SecondApp/index.html"); }); });
警告
依赖于主机头的 API(如 HttpRequest.Host 和 RequireHost)可能会受到客户端的欺骗。
若要防止主机和端口欺骗,请使用以下方法之一:
- 使用检查端口的 HttpContext.Connection (ConnectionInfo.LocalPort)。
- 采用主机筛选。
添加将请求映射到客户端应用的中间件。 以下示例将中间件配置为在请求子路径为
/FirstApp
(对于第一个客户端应用)或/SecondApp
(对于第二个客户端应用)时运行。在从
Program.cs
中删除app.UseBlazorFrameworkFiles();
行的位置放置以下代码:app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/FirstApp", StringComparison.OrdinalIgnoreCase), first => { first.UseBlazorFrameworkFiles("/FirstApp"); first.UseStaticFiles(); first.UseStaticFiles("/FirstApp"); first.UseRouting(); first.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapFallbackToFile("/FirstApp/{*path:nonfile}", "FirstApp/index.html"); }); }); app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/SecondApp", StringComparison.OrdinalIgnoreCase), second => { second.UseBlazorFrameworkFiles("/SecondApp"); second.UseStaticFiles(); second.UseStaticFiles("/SecondApp"); second.UseRouting(); second.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapFallbackToFile("/SecondApp/{*path:nonfile}", "SecondApp/index.html"); }); });
在每个客户端应用中设置基路径:
在第一个客户端应用的
index.html
文件 (Client/wwwroot/index.html
) 中,更新<base>
标记值以反映子路径。 尾部反斜杠是必需项:<base href="/FirstApp/" />
在第二个客户端应用的
index.html
文件 (SecondClient/wwwroot/index.html
) 中,更新<base>
标记值以反映子路径。 尾部反斜杠是必需项:<base href="/SecondApp/" />
有关 UseStaticFiles 的详细信息,请参阅 ASP.NET Core Blazor 静态文件。
有关 UseBlazorFrameworkFiles
和 MapFallbackToFile
的详细信息,请参阅以下资源:
- Microsoft.AspNetCore.Builder.ComponentsWebAssemblyApplicationBuilderExtensions.UseBlazorFrameworkFiles(引用源)
- Microsoft.AspNetCore.Builder.StaticFilesEndpointRouteBuilderExtensions.MapFallbackToFile(引用源)
注意
指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)。
从客户端应用到服务器 API 中的 /WeatherForecast
的请求指向 /FirstApp/WeatherForecast
或 /SecondApp/WeatherForecast
,具体取决于发出请求的客户端应用。 因此,从服务器 API 返回天气数据的控制器路由需要修改以包含路径段。
在服务器应用的天气预报控制器 (Controllers/WeatherForecastController.cs
) 中,将到 WeatherForecastController
的现有路由 ([Route("[controller]")]
) 替换为以下路由,其中考虑了客户端请求路径:
[Route("FirstApp/[controller]")]
[Route("SecondApp/[controller]")]
如果计划从服务器应用提供页面,请将 Index
Razor 页面添加到服务器应用的 Pages
文件夹:
Pages/Index.cshtml
:
@page
@model MultipleBlazorApps.Server.Pages.IndexModel
@{
ViewData["Title"] = "Home";
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Home</title>
</head>
<body>
<div class="main">
<div class="content px-4">
<div>
<h1>Welcome</h1>
<p>Hello from Razor Pages!</p>
</div>
</div>
</div>
</body>
</html>
Pages/Index.cshtml.cs
:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace MultipleBlazorApps.Server.Pages;
public class IndexModel : PageModel
{
public void OnGet()
{
}
}
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace MultipleBlazorApps.Server.Pages
{
public class IndexModel : PageModel
{
public void OnGet()
{
}
}
}
注意
之前的 Index
页面是仅用于演示目的的最小示例。 如果应用需要额外的 Razor Pages 资产(例如布局、样式、脚本和导入内容),请从使用 Razor Pages 项目模板创建的应用中获取它们。 有关详细信息,请参阅 ASP.NET Core 中的 Razor Pages 简介。
如果计划从服务器应用提供 MVC 视图,请添加一个 Index
视图和一个 Home
控制器:
Views/Home/Index.cshtml
:
@{
ViewData["Title"] = "Home";
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Home</title>
</head>
<body>
<div class="main">
<div class="content px-4">
<div>
<h1>Welcome</h1>
<p>Hello from MVC!</p>
</div>
</div>
</div>
</body>
</html>
Controllers/HomeController.cs
:
using Microsoft.AspNetCore.Mvc;
namespace MultipleBlazorApps.Server.Controllers;
public class HomeController : Controller
{
public IActionResult Index() => View();
}
注意
之前的 Index
视图是仅用于演示目的的最小示例。 如果应用需要额外的 MVC 资产(例如布局、样式、脚本和导入内容),请从使用 MVC 项目模板创建的应用中获取它们。 有关详细信息,请参阅 ASP.NET Core MVC 入门。
有关在页面或服务器应用视图中使用Razor客户端应用中的组件的详细信息,请参阅在托管Blazor WebAssembly解决方案中将 ASP.NET 核心Razor组件与 MVC 或 Razor Pages 集成。
运行应用
运行 MultipleBlazorApps.Server
项目:
- 在
https://localhost:5001
访问初始客户端应用。 - 在
https://localhost:5002
访问新增的客户端应用。 - 如果服务器应用配置为提供页面或视图,请在
https://localhost:5000
访问Index
页面或视图。
- 在
https://localhost:{DEFAULT PORT}/FirstApp
访问初始客户端应用。 - 在
https://localhost:{DEFAULT PORT}/SecondApp
访问新增的客户端应用。 - 如果服务器应用配置为提供页面或视图,请在
https://localhost:{DEFAULT PORT}
访问Index
页面或视图。
在前面的示例 URL 中,{DEFAULT PORT}
占位符是由 MultipleBlazorApps.Server
项目的 Properties/launchSettings.json
文件在其 applicationUrl
值中定义的默认端口。
重要
使用 dotnet watch
(或 dotnet run
)命令 (.NET CLI) 运行应用时,请确认命令 shell 已在解决方案的 Server
文件夹中打开。
使用 Visual Studio 的“开始”按钮运行应用时,请确认 MultipleBlazorApps.Server
项目设置为启动项目(在解决方案资源管理器中突出显示)。
静态资产
当资产位于客户端应用的 wwwroot
文件夹中时,请在组件中提供静态资产请求路径:
<img alt="..." src="{PATH AND FILE NAME}" />
{PATH AND FILE NAME}
占位符是 wwwroot
下的路径和文件名。
例如,wwwroot
的 vehicle
文件夹中的 Jeep 图像 (jeep-yj.png
) 的来源:
<img alt="Jeep Wrangler YJ" src="vehicle/jeep-yj.png" />
Razor 类库 (RCL) 支持
将 Razor 类库 (RCL) 作为新项目添加到解决方案中:
- 在“解决方案资源管理器”中右键单击此解决方案并选择“添加”>“新建项目”。
- 使用 Razor 类库项目模板创建项目。 本部分中的示例使用项目名称
ComponentLibrary
,它也是 RCL 的程序集名称。 请勿选中“支持页面和视图”复选框。
对于每个托管的 Blazor WebAssembly 客户端应用,通过右键单击“解决方案资源管理器”中的每个客户端项目并选择“添加”>“项目参考”,为 RCL 项目创建项目引用。
通过以下任一方法在客户端应用中使用 RCL 中的组件:
将
@using
指令放置在 RCL 命名空间的组件顶部,并为组件添加 Razor 语法。 以下示例适用于程序集名称为ComponentLibrary
的 RCL:@using ComponentLibrary ... <Component1 />
提供 RCL 的命名空间以及 Razor 组件的语法。 此方法不需要组件文件顶部的
@using
指令。 以下示例适用于程序集名称为ComponentLibrary
的 RCL:<ComponentLibrary.Component1 />
注意
还可以将 @using
指令放入每个客户端应用的 _Import.razor
文件中,这使得 RCL 的命名空间全局可用于该项目中的组件。
当任何其他静态资产位于 RCL 的 wwwroot
文件夹中时,请按照 ASP.NET Core 的类库中的可重用 Razor UI 中的指南在客户端应用中引用静态资产:
<img alt="..." src="_content/{PACKAGE ID}/{PATH AND FILE NAME}" />
{PACKAGE ID}
占位符是 RCL 的包 ID。 如果项目文件中没有指定 <PackageId>
,则包 ID 默认为项目的程序集名称。 {PATH AND FILE NAME}
占位符是 wwwroot
下的路径和文件名。
以下示例显示了 RCL wwwroot
文件夹的 vehicle
文件夹中 Jeep 图像 (jeep-yj.png
) 的标记。 以下示例适用于程序集名称为 ComponentLibrary
的 RCL:
<img alt="Jeep Wrangler YJ" src="_content/ComponentLibrary/vehicle/jeep-yj.png" />