多个托管 ASP.NET 核心 Blazor WebAssembly 应用

注释

此版本不是本文的最新版本。 有关本文的最新版本,请参阅 .NET 7 版本

本文介绍如何配置托管 Blazor WebAssembly 应用以托管多个 Blazor WebAssembly 应用。

配置

选择符合托管要求的本文版本,即端口/域托管(例如,:5001/:5002firstapp.comsecondapp.com/)或路由子路径托管(例如,/FirstApp和)。/SecondApp

使用当前托管选择时,本文介绍端口/域托管(例如,:5001/:5002或)。firstapp.com/secondapp.com

在以下示例中:

  • 托管应用的项目名称位于名为 <a0/> 的文件夹中。
  • 在添加第二个客户端应用之前,解决方案中的三个项目位于MultipleBlazorApps.ClientClient文件夹、 MultipleBlazorApps.ServerServer文件夹和MultipleBlazorApps.SharedShared文件夹中。
  • 初始客户端应用是从 Blazor WebAssembly 项目模板创建的解决方案的默认客户端项目。
  • 另一个客户端应用将添加到解决方案,位于名为 <a0/a0> 的文件夹中。
  • (可选)服务器项目(MultipleBlazorApps.Server)可以将页面或视图用作 Razor 页面或 MVC 应用。
  • 第一个客户端应用可在端口 5001 或主机 firstapp.com的浏览器中访问。 第二个客户端应用可在端口 5002 或主机 secondapp.com的浏览器中访问。

通过当前选择,本文介绍路由子路径托管(例如 /FirstApp ,和 /SecondApp)。

在以下示例中:

  • 托管应用的项目名称位于名为 <a0/> 的文件夹中。
  • 在添加第二个客户端应用之前,解决方案中的三个项目位于MultipleBlazorApps.ClientClient文件夹、 MultipleBlazorApps.ServerServer文件夹和MultipleBlazorApps.SharedShared文件夹中。
  • 初始客户端应用是从 Blazor WebAssembly 项目模板创建的解决方案的默认客户端项目。
  • 另一个客户端应用将添加到解决方案,位于名为 <a0/a0> 的文件夹中。
  • (可选)服务器项目(MultipleBlazorApps.Server)可以将页面或视图作为正式 Razor 页面或 MVC 应用提供服务。
  • 这两个客户端应用都 MultipleBlazorApps.Server 使用项目 Properties/launchSettings.json 文件在其值中 applicationUrl 定义的默认端口。 第一个客户端应用可在子路径的 /FirstApp 浏览器中访问。 第二个客户端应用可在子路径的 /SecondApp 浏览器中访问。

本文中所示的示例需要以下附加配置:

  • 直接在示例主机域上访问应用, firstapp.com 以及 secondapp.com
  • 用于启用 TLS/HTTPS 安全性的客户端应用的证书。
  • 将服务器应用配置为 Razor Pages 应用,以便实现以下功能:
    • 将 Razor 组件集成到页面或视图中。
    • 预呈现 Razor 组件。

上述配置超出了本文的范围。 有关详细信息,请参阅以下资源:

使用现有托管Blazor WebAssembly解决方案或通过传递-ho|--hosted选项从Blazor WebAssembly项目模板创建新的托管Blazor WebAssembly解决方案(如果使用 .NET CLI 或在 IDE 中创建项目时在 Visual Studio 中选择“ASP.NET 核心托管”复选框)。

对命名 MultipleBlazorApps 和命名项目 MultipleBlazorApps的解决方案使用文件夹。

在名为 <a0/&a0> 的解决方案中创建一个新文件夹。 在新文件夹中,添加名为 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 项目名称。

物理文件夹 项目名称 Description
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 页面或 MVC 应用提供页面或视图。 本文稍后将介绍启用服务页面或视图的步骤。

注释

本文中的演示使用项目和SecondApp项目的静态 Web 资产路径名称FirstAppMultipleBlazorApps.ClientMultipleBlazorApps.SecondClient 名称“FirstApp”和“”SecondApp仅用于演示目的。 可以接受其他名称来区分客户端应用,例如App1App2/,/Client1Client21/2或类似的命名方案。

当通过端口或域将请求路由到客户端应用时,“FirstApp”和“”SecondApp内部 用于路由请求并为静态资产提供响应,并且不会在浏览器的地址栏中看到。

注释

本文中的演示使用项目和SecondApp项目的静态 Web 资产路径名称FirstAppMultipleBlazorApps.ClientMultipleBlazorApps.SecondClient 名称“FirstApp”和“”SecondApp仅用于演示目的。 可以接受其他名称来区分客户端应用,例如App1App2/,/Client1Client21/2或类似的命名方案。

FirstApp”和“”SecondApp也出现在浏览器的地址栏中,因为请求会使用这些名称路由到两个客户端应用。 支持其他有效的 URL 路由段,并且路由段不需要严格匹配用于在内部路由静态 Web 资产的名称。 对内部静态资产路由和应用请求路由使用“FirstApp”和“”SecondApp仅用于本文示例中的召集。

在第一个客户端应用的项目文件中(MultipleBlazorApps.Client.csproj)将属性添加到<StaticWebAssetBasePath><PropertyGroup>值为FirstApp设置项目静态资产的基本路径的属性:

<StaticWebAssetBasePath>FirstApp</StaticWebAssetBasePath>

MultipleBlazorApps.SecondClient 应用的项目文件中(MultipleBlazorApps.SecondClient.csproj):

  • <StaticWebAssetBasePath> 属性添加到 <PropertyGroup> 值为 SecondApp

    <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文件中,配置applicationUrlKestrel配置文件(MultipleBlazorApps.Server)以访问端口 5001 和 5002 上的客户端应用。 如果将本地环境配置为使用示例域,则 URL applicationUrl 可用于 firstapp.comsecondapp.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.HostRequireHost)可能会受到客户端的欺骗。

    若要防止主机和端口欺骗,请使用以下方法之一:

  • 添加将请求映射到客户端应用的中间件。 以下示例将中间件配置为在请求子路径用于 /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 核心 Blazor 静态文件

有关详细信息 UseBlazorFrameworkFilesMapFallbackToFile请参阅以下资源:

注释

指向 .NET 引用源的文档链接通常会加载存储库的默认分支,该分支代表正在进行的 .NET 下一版本的开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉菜单。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)

从客户端应用到 /WeatherForecast 服务器 API 中的请求要么是请求 /FirstApp/WeatherForecast ,要么 /SecondApp/WeatherForecast 取决于哪个客户端应用发出请求。 因此,从服务器 API 返回天气数据的控制器路由需要修改以包含路径段。

在服务器应用的天气预报控制器()中,将现有路线(Controllers/WeatherForecastController.cs[Route("[controller]")]WeatherForecastController替换为以下路由,这考虑到了客户端请求路径:

[Route("FirstApp/[controller]")]
[Route("SecondApp/[controller]")]

如果计划从服务器应用提供页面,请将页面 IndexRazor 添加到 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 项目模板创建的应用获取它们。 有关详细信息,请参阅 Razor ASP.NET Core 中的 Pages 体系结构和概念

如果计划从服务器应用提供 MVC 视图,请添加视图 IndexHome 控制器:

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客户端应用中的组件的详细信息,请参阅将 ASP.NET Core Razor 组件与托管Blazor WebAssembly解决方案中的 MVC 或 Razor Pages 集成

运行应用

MultipleBlazorApps.Server运行项目:

  • 在 . 处 https://localhost:5001访问初始客户端应用。
  • 在 . 处 https://localhost:5002访问添加的客户端应用。
  • 如果服务器应用配置为提供页面或视图,则访问Index页面或视图。https://localhost:5000
  • 在 . 处 https://localhost:{DEFAULT PORT}/FirstApp访问初始客户端应用。
  • 在 . 处 https://localhost:{DEFAULT PORT}/SecondApp访问添加的客户端应用。
  • 如果服务器应用配置为提供页面或视图,则访问Index页面或视图。https://localhost:{DEFAULT PORT}

在前面的示例 URL 中,{DEFAULT PORT}占位符是项目Properties/launchSettings.json文件在其值中applicationUrl定义MultipleBlazorApps.Server的默认端口。

重要

使用 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 下的路径和文件名。

例如,吉普图像 (jeep-yj.png) 的源位于vehiclewwwroot

<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 的命名空间全局可用于该项目中的组件。

当任何其他静态资产位于 wwwroot RCL 的文件夹中时,请根据 具有 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 文件夹文件夹中吉普图像 (jeep-yj.pngvehiclewwwroot 标记。 以下示例适用于具有程序集名称 ComponentLibrary的 RCL:

<img alt="Jeep Wrangler YJ" src="_content/ComponentLibrary/vehicle/jeep-yj.png" />

其他资源