ASP.NET Core Blazor WebAssembly 中的延後載入組件

注意

這不是這篇文章的最新版本。 如需目前版本,請參閱本文的 .NET 8 版本

重要

這些發行前產品的相關資訊在產品正式發行前可能會有大幅修改。 Microsoft 對此處提供的資訊,不做任何明確或隱含的瑕疵擔保。

如需目前版本,請參閱本文的 .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);
           }
    }
}

注意

上述範例不會顯示 Router 元件 Razor 標記的內容 (...)。 如需完整程式碼的示範,請參閱本文的完整範例一節。

注意

隨著 ASP.NET Core 5.0.1 的發行以及在任何其他 5.x 版中,Router 元件會包含設定為 @truePreferExactMatches 參數。 如需詳細資訊,請參閱從 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);
           }
    }
}

注意

上述範例不會顯示 Router 元件 Razor 標記的內容 (...)。 如需完整程式碼的示範,請參閱本文的完整範例一節。

注意

隨著 ASP.NET Core 5.0.1 的發行以及在任何其他 5.x 版中,Router 元件會包含設定為 @truePreferExactMatches 參數。 如需詳細資訊,請參閱從 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 類別。 此方法接受將軸的引數 (LeftRight) 作為 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>&nbsp;</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>&nbsp;</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>&nbsp;</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&hellip;</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&hellip;</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)

其他資源