ASP.NET Core Blazor WebAssembly 中的延後載入組件
注意
這不是這篇文章的最新版本。 如需目前版本,請參閱本文的 .NET 8 版本。
警告
不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支援原則。 如需目前版本,請參閱本文的 .NET 8 版本。
在需要組件前,可以藉由等候載入開發人員建立之應用程式組件來改善 Blazor WebAssembly 應用程式啟動效能,此運作稱為延後載入。
本文的初始章節涵蓋此應用程式設定。 如需工作示範,請參閱本文結尾的完成範例一節。
本文僅適用於 Blazor WebAssembly 應用程式。 組件延遲載入不會使伺服器端應用程式受益,因為伺服器呈現的應用程式不會將組件下載到用戶端。
延後載入不應該用於核心執行階段組件,因為這些組件可能會在發佈遭到修剪,並於應用程式載入時無法在用戶端提供使用。
組件檔的檔案副檔名預留位置 ({FILE EXTENSION}
)
組件檔會為副檔名為 .wasm
的 .NET 組件,使用 Webcil 封裝格式。
在整篇文章中,{FILE EXTENSION}
預留位置代表「wasm
」。
組件檔是以副檔名為 .dll
的動態連結程式庫 (DLL) 為基礎。
在整篇文章中,{FILE EXTENSION}
預留位置代表「dll
」。
專案檔設定
使用 BlazorWebAssemblyLazyLoad
項目在應用程式專案檔 (.csproj
) 中,標記延後載入的組件。 使用帶有副檔名的組件名稱。 Blazor 架構可防止組件在應用程式啟動時載入。
<ItemGroup>
<BlazorWebAssemblyLazyLoad Include="{ASSEMBLY NAME}.{FILE EXTENSION}" />
</ItemGroup>
{ASSEMBLY NAME}
預留位置是組件的名稱,而 {FILE EXTENSION}
預留位置是副檔名。 需要 副檔名。
為每個組件包含一個 BlazorWebAssemblyLazyLoad
項目。 如果組件具有相依性,請為每個相依性包含 BlazorWebAssemblyLazyLoad
輸入。
Router
元件設定
Blazor 架構會自動註冊單一服務,以在用戶端 Blazor WebAssembly 應用程式 (LazyAssemblyLoader) 中延後載入組件。 LazyAssemblyLoader.LoadAssembliesAsync 方法:
- 使用 JS Interop,透過網路呼叫來擷取組件。
- 將組件載入瀏覽器中在 WebAssembly 上執行的執行階段。
注意
在託管 Blazor WebAssembly 解決方案中延後載入組件一節中會提供的託管Blazor WebAssembly解決方案的指導。
Blazor 的 Router 元件會指定 Blazor 搜尋可路由元件的組件,該組件也負責為使用者瀏覽所在的路由轉譯元件。 Router 元件的 OnNavigateAsync
方法會與延後載入搭配使用,以針對使用者要求的端點載入正確的組件。
會在 OnNavigateAsync 內實作邏輯,以判斷要使用 LazyAssemblyLoader 載入的組件。 如何建構邏輯的選項包括:
- OnNavigateAsync 方法內部有條件的檢查。
- 將路由對應至組件名稱的查閱表格,會插入至元件或在
@code
區塊內實作。
在以下範例中:
- 已指定 Microsoft.AspNetCore.Components.WebAssembly.Services 的命名空間。
- 會插入 LazyAssemblyLoader 服務 (
AssemblyLoader
)。 {PATH}
預留位置是組件清單載入所在的路徑。 此範例會針對載入單一組件集的單一路徑使用有條件的檢查。{LIST OF ASSEMBLIES}
預留位置是組件檔案名稱字串清單 (以逗號分隔),包括其副檔名 (例如,"Assembly1.{FILE EXTENSION}", "Assembly2.{FILE EXTENSION}"
)。
App.razor
:
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.WebAssembly.Services
@using Microsoft.Extensions.Logging
@inject LazyAssemblyLoader AssemblyLoader
@inject ILogger<App> Logger
<Router AppAssembly="typeof(App).Assembly"
OnNavigateAsync="OnNavigateAsync">
...
</Router>
@code {
private async Task OnNavigateAsync(NavigationContext args)
{
try
{
if (args.Path == "{PATH}")
{
var assemblies = await AssemblyLoader.LoadAssembliesAsync(
new[] { {LIST OF ASSEMBLIES} });
}
}
catch (Exception ex)
{
Logger.LogError("Error: {Message}", ex.Message);
}
}
}
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.WebAssembly.Services
@using Microsoft.Extensions.Logging
@inject LazyAssemblyLoader AssemblyLoader
@inject ILogger<App> Logger
<Router AppAssembly="typeof(Program).Assembly"
OnNavigateAsync="OnNavigateAsync">
...
</Router>
@code {
private async Task OnNavigateAsync(NavigationContext args)
{
try
{
if (args.Path == "{PATH}")
{
var assemblies = await AssemblyLoader.LoadAssembliesAsync(
new[] { {LIST OF ASSEMBLIES} });
}
}
catch (Exception ex)
{
Logger.LogError("Error: {Message}", ex.Message);
}
}
}
注意
隨著 ASP.NET Core 5.0.1 的發行以及在任何其他 5.x 版中,Router
元件會包含設定為 @true
的 PreferExactMatches
參數。 如需詳細資訊,請參閱從 ASP.NET Core 3.1 移轉至 5.0。
包含可路由元件的組件
當組件清單包含可路由的元件時,便會將指定路徑的組件清單傳遞至 Router 元件的 AdditionalAssemblies 集合。
在以下範例中:
lazyLoadedAssemblies
中的 List<Assembly> 會將組件清單傳遞至 AdditionalAssemblies。 如果找到新的路由,該架構會搜尋組件中的路由,並更新路由集合。 若要存取 Assembly 類型,App.razor
檔案頂端會包含 System.Reflection 的命名空間。{PATH}
預留位置是組件清單載入所在的路徑。 此範例會針對載入單一組件集的單一路徑使用有條件的檢查。{LIST OF ASSEMBLIES}
預留位置是組件檔案名稱字串清單 (以逗號分隔),包括其副檔名 (例如,"Assembly1.{FILE EXTENSION}", "Assembly2.{FILE EXTENSION}"
)。
App.razor
:
@using System.Reflection
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.WebAssembly.Services
@using Microsoft.Extensions.Logging
@inject ILogger<App> Logger
@inject LazyAssemblyLoader AssemblyLoader
<Router AppAssembly="typeof(App).Assembly"
AdditionalAssemblies="lazyLoadedAssemblies"
OnNavigateAsync="OnNavigateAsync">
...
</Router>
@code {
private List<Assembly> lazyLoadedAssemblies = new();
private async Task OnNavigateAsync(NavigationContext args)
{
try
{
if (args.Path == "{PATH}")
{
var assemblies = await AssemblyLoader.LoadAssembliesAsync(
new[] { {LIST OF ASSEMBLIES} });
lazyLoadedAssemblies.AddRange(assemblies);
}
}
catch (Exception ex)
{
Logger.LogError("Error: {Message}", ex.Message);
}
}
}
@using System.Reflection
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.WebAssembly.Services
@using Microsoft.Extensions.Logging
@inject ILogger<App> Logger
@inject LazyAssemblyLoader AssemblyLoader
<Router AppAssembly="typeof(Program).Assembly"
AdditionalAssemblies="lazyLoadedAssemblies"
OnNavigateAsync="OnNavigateAsync">
...
</Router>
@code {
private List<Assembly> lazyLoadedAssemblies = new List<Assembly>();
private async Task OnNavigateAsync(NavigationContext args)
{
try
{
if (args.Path == "{PATH}")
{
var assemblies = await AssemblyLoader.LoadAssembliesAsync(
new[] { {LIST OF ASSEMBLIES} });
lazyLoadedAssemblies.AddRange(assemblies);
}
}
catch (Exception ex)
{
Logger.LogError("Error: {Message}", ex.Message);
}
}
}
注意
隨著 ASP.NET Core 5.0.1 的發行以及在任何其他 5.x 版中,Router
元件會包含設定為 @true
的 PreferExactMatches
參數。 如需詳細資訊,請參閱從 ASP.NET Core 3.1 移轉至 5.0。
如需詳細資訊,請參閱 ASP.NET Core Blazor 路由和導覽。
使用 <Navigating>
內容的使用者互動
載入組件可能需要幾秒鐘的時間,Router 元件可以透過路由器的 Navigating 屬性,向使用者指出頁面轉換正在發生。
如需詳細資訊,請參閱 ASP.NET Core Blazor 路由和導覽。
在 OnNavigateAsync
中處理取消
傳遞至 OnNavigateAsync 回撥的 NavigationContext 物件會包含發生新導覽事件時設定的 CancellationToken。 設定取消權杖時,OnNavigateAsync 回撥必須擲回,以避免在過期的導覽上繼續執行 OnNavigateAsync 回撥。
如需詳細資訊,請參閱 ASP.NET Core Blazor 路由和導覽。
OnNavigateAsync
事件和重新命名的組件檔
資源載入器會依賴 blazor.boot.json
檔案中定義的組件名稱。 如果組件已重新命名,則 OnNavigateAsync 回撥中使用的組件名稱與 blazor.boot.json
檔案中的組件名稱會不同步。
若要修正此問題:
- 判斷要使用的組件名稱時,檢查應用程式是否在
Production
環境中執行。 - 將重新命名的組件名稱儲存在個別的檔案中,並從該檔案讀取,以判斷要與 LazyAssemblyLoader 服務和 OnNavigateAsync 回撥搭配使用的組件名稱。
託管 Blazor WebAssembly 解決方案中的延後載入組件
架構的延後載入實作支援在託管 Blazor WebAssembly解決方案中預先轉譯的延後載入。 在預先轉譯期間,假設會載入所有組件,包括標示為延後載入的組件。 在 Server 專案中手動註冊 LazyAssemblyLoader 服務。
在 Server 專案的 Program.cs
檔案頂端,新增 Microsoft.AspNetCore.Components.WebAssembly.Services 的命名空間:
using Microsoft.AspNetCore.Components.WebAssembly.Services;
在 Server 專案的 Program.cs
中,註冊此服務:
builder.Services.AddScoped<LazyAssemblyLoader>();
在 Server 專案的 Startup.cs
檔案頂端,新增 Microsoft.AspNetCore.Components.WebAssembly.Services 的命名空間:
using Microsoft.AspNetCore.Components.WebAssembly.Services;
在 Server 專案的 Startup.ConfigureServices
(Startup.cs
) 中,註冊此服務:
services.AddScoped<LazyAssemblyLoader>();
完整範例
本節中的示範:
- 建立機器人控制群組件 (
GrantImaharaRobotControls.{FILE EXTENSION}
) 作為 Razor 類別庫 (RCL),其中包含Robot
元件 (含/robot
路由範本的Robot.razor
)。 - 延後載入 RCL 的組件,以在使用者要求
/robot
URL 時轉譯其Robot
元件。
建立獨立 Blazor WebAssembly 應用程式,以示範 Razor 類別庫組件的延後載入。 將專案命名為 LazyLoadTest
。
將 ASP.NET Core 類別庫專案新增至解決方案:
- Visual Studio:以滑鼠右鍵按一下 [方案總管] 中的解決方案檔案,然後選取 [新增]>[新增專案]。 從新專案類型的對話方塊中,選取 [Razor 類別庫]。 將專案命名為
GrantImaharaRobotControls
。 請勿選取 [支援頁面和檢視] 核取方塊。 - Visual Studio Code/.NET CLI:從命令提示字元執行
dotnet new razorclasslib -o GrantImaharaRobotControls
。-o|--output
選項會建立資料夾,並將專案命名為GrantImaharaRobotControls
。
本節稍後顯示的範例元件會使用 Blazor 表單。 在 RCL 專案中,將 Microsoft.AspNetCore.Components.Forms
封裝新增至此專案。
注意
如需將套件新增至 .NET 應用程式的指引,請參閱在套件取用工作流程 (NuGet 文件) 的安裝及管理套件底下的文章。 在 NuGet.org 確認正確的套件版本。
使用假設讓機器人執行拇指手勢的 ThumbUp
方法,在 RCL 中建立 HandGesture
類別。 此方法接受將軸的引數 (Left
或 Right
) 作為 enum
。 此方法會在成功時傳回 true
。
HandGesture.cs
:
using Microsoft.Extensions.Logging;
namespace GrantImaharaRobotControls;
public static class HandGesture
{
public static bool ThumbUp(Axis axis, ILogger logger)
{
logger.LogInformation("Thumb up gesture. Axis: {Axis}", axis);
// Code to make robot perform gesture
return true;
}
}
public enum Axis { Left, Right }
using Microsoft.Extensions.Logging;
namespace GrantImaharaRobotControls
{
public static class HandGesture
{
public static bool ThumbUp(Axis axis, ILogger logger)
{
logger.LogInformation("Thumb up gesture. Axis: {Axis}", axis);
// Code to make robot perform gesture
return true;
}
}
public enum Axis { Left, Right }
}
將下列元件新增至 RCL 專案的根目錄。 此元件可讓使用者提交左手或右手拇指手勢要求。
Robot.razor
:
@page "/robot"
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.Extensions.Logging
@inject ILogger<Robot> Logger
<h1>Robot</h1>
<EditForm FormName="RobotForm" Model="robotModel" OnValidSubmit="HandleValidSubmit">
<InputRadioGroup @bind-Value="robotModel.AxisSelection">
@foreach (var entry in Enum.GetValues<Axis>())
{
<InputRadio Value="entry" />
<text> </text>@entry<br>
}
</InputRadioGroup>
<button type="submit">Submit</button>
</EditForm>
<p>
@message
</p>
@code {
private RobotModel robotModel = new() { AxisSelection = Axis.Left };
private string? message;
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
var result = HandGesture.ThumbUp(robotModel.AxisSelection, Logger);
message = $"ThumbUp returned {result} at {DateTime.Now}.";
}
public class RobotModel
{
public Axis AxisSelection { get; set; }
}
}
@page "/robot"
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.Extensions.Logging
@inject ILogger<Robot> Logger
<h1>Robot</h1>
<EditForm Model="robotModel" OnValidSubmit="HandleValidSubmit">
<InputRadioGroup @bind-Value="robotModel.AxisSelection">
@foreach (var entry in Enum.GetValues<Axis>())
{
<InputRadio Value="entry" />
<text> </text>@entry<br>
}
</InputRadioGroup>
<button type="submit">Submit</button>
</EditForm>
<p>
@message
</p>
@code {
private RobotModel robotModel = new() { AxisSelection = Axis.Left };
private string? message;
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
var result = HandGesture.ThumbUp(robotModel.AxisSelection, Logger);
message = $"ThumbUp returned {result} at {DateTime.Now}.";
}
public class RobotModel
{
public Axis AxisSelection { get; set; }
}
}
@page "/robot"
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.Extensions.Logging
@inject ILogger<Robot> Logger
<h1>Robot</h1>
<EditForm Model="robotModel" OnValidSubmit="HandleValidSubmit">
<InputRadioGroup @bind-Value="robotModel.AxisSelection">
@foreach (var entry in (Axis[])Enum
.GetValues(typeof(Axis)))
{
<InputRadio Value="entry" />
<text> </text>@entry<br>
}
</InputRadioGroup>
<button type="submit">Submit</button>
</EditForm>
<p>
@message
</p>
@code {
private RobotModel robotModel = new RobotModel() { AxisSelection = Axis.Left };
private string message;
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
var result = HandGesture.ThumbUp(robotModel.AxisSelection, Logger);
message = $"ThumbUp returned {result} at {DateTime.Now}.";
}
public class RobotModel
{
public Axis AxisSelection { get; set; }
}
}
在 LazyLoadTest
專案中,建立 GrantImaharaRobotControls
RCL 的專案參考:
- Visual Studio:以滑鼠右鍵按一下
LazyLoadTest
專案,然後選取 [新增]>[專案參考] 以新增GrantImaharaRobotControls
RCL 的專案參考。 - Visual Studio Code/.NET CLI:從專案的資料夾,在命令殼層中執行
dotnet add reference {PATH}
。{PATH}
預留位置是 RCL 專案的路徑。
指定 RCL 的組件,以在 LazyLoadTest
應用程式的專案檔 (.csproj
) 進行延後載入:
<ItemGroup>
<BlazorWebAssemblyLazyLoad Include="GrantImaharaRobotControls.{FILE EXTENSION}" />
</ItemGroup>
下列 Router 元件示範當使用者瀏覽至 /robot
時載入 GrantImaharaRobotControls.{FILE EXTENSION}
組件。 以下列 App
元件取代應用程式的預設 App
元件。
在頁面轉換期間,會向使用者顯示具有 <Navigating>
元素的已設定樣式訊息。 如需詳細資訊,請參閱使用者與 <Navigating>
內容互動一節。
組件會指派給 AdditionalAssemblies,這會導致路由器搜尋組件中的可路由元件,並在其中尋找 Robot
元件。 Robot
元件的路由會新增至應用程式的路由集合。 如需詳細資訊,請參閱 ASP.NET Core Blazor 路由和導覽一文和本文中包含可路由元件之組件一節。
App.razor
:
@using System.Reflection
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.WebAssembly.Services
@using Microsoft.Extensions.Logging
@inject ILogger<App> Logger
@inject LazyAssemblyLoader AssemblyLoader
<Router AppAssembly="typeof(App).Assembly"
AdditionalAssemblies="lazyLoadedAssemblies"
OnNavigateAsync="OnNavigateAsync">
<Navigating>
<div style="padding:20px;background-color:blue;color:white">
<p>Loading the requested page…</p>
</div>
</Navigating>
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
@code {
private List<Assembly> lazyLoadedAssemblies = new();
private async Task OnNavigateAsync(NavigationContext args)
{
try
{
if (args.Path == "robot")
{
var assemblies = await AssemblyLoader.LoadAssembliesAsync(
new[] { "GrantImaharaRobotControls.{FILE EXTENSION}" });
lazyLoadedAssemblies.AddRange(assemblies);
}
}
catch (Exception ex)
{
Logger.LogError("Error: {Message}", ex.Message);
}
}
}
@using System.Reflection
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.WebAssembly.Services
@using Microsoft.Extensions.Logging
@inject ILogger<App> Logger
@inject LazyAssemblyLoader AssemblyLoader
<Router AppAssembly="typeof(Program).Assembly"
AdditionalAssemblies="lazyLoadedAssemblies"
OnNavigateAsync="OnNavigateAsync">
<Navigating>
<div style="padding:20px;background-color:blue;color:white">
<p>Loading the requested page…</p>
</div>
</Navigating>
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
@code {
private List<Assembly> lazyLoadedAssemblies = new List<Assembly>();
private async Task OnNavigateAsync(NavigationContext args)
{
try
{
if (args.Path == "robot")
{
var assemblies = await AssemblyLoader.LoadAssembliesAsync(
new[] { "GrantImaharaRobotControls.{FILE EXTENSION}" });
lazyLoadedAssemblies.AddRange(assemblies);
}
}
catch (Exception ex)
{
Logger.LogError("Error: {Message}", ex.Message);
}
}
}
建置並執行應用程式。
在 /robot
要求 RCL 中的 Robot
元件時,會載入 GrantImaharaRobotControls.{FILE EXTENSION}
組件並轉譯 Robot
元件。 您可以在瀏覽器開發人員工具的 [網路] 索引標籤中檢查載入的組件。
疑難排解
- 如果發生非預期的轉譯,例如從上一個導覽轉譯元件,請確認程式碼會在設定取消權杖時擲回。
- 如果設定為延後載入的組件在應用程式啟動時意外載入,請檢查是否在該專案檔中將該組件標示為延後載入。
注意
從延後載入的組件載入類型時,存在已知問題。 如需詳細資訊,請參閱Blazor WebAssembly lazy loading assemblies not working when using @ref attribute in the component (dotnet/aspnetcore #29342)。