ASP.NET Core でのアセンブリの遅延読み込みBlazor WebAssembly
注意
これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。
警告
このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、「.NET および .NET Core サポート ポリシー」を参照してください。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。
重要
この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。
現在のリリースについては、この記事の .NET 8 バージョンを参照してください。
Blazor WebAssembly アプリケーションの起動パフォーマンスは、アセンブリが必要になるまで開発者が作成したアプリケーション アセンブリの読み込みを待機すると改善できます。これは "遅延読み込み" と呼ばれます。
この記事の初めのセクションでは、アプリの構成について説明します。 実際の動作のデモンストレーションについては、この記事の最後にある「コード例全体」のセクションを参照してください。
この記事は Blazor WebAssembly アプリにのみ該当します。 server-side アプリでは、アセンブリの遅延読み込みを行っても効果がありません。これは、server-rendered アプリでは、アセンブリがクライアントにダウンロードされないためです。
遅延読み込みはコアのランタイム アセンブリには使用しないでください。これは、発行時にトリミングされ、アプリの読み込み時にクライアントで使用できなくなるおそれがあります。
アセンブリ ファイルのファイル拡張子プレースホルダー ({FILE EXTENSION}
)
アセンブリ ファイルには、ファイル拡張子 .wasm
を持つ .NET アセンブリの Webcil パッケージ形式を使用します。
この記事全体を通して、プレースホルダー {FILE EXTENSION}
は "wasm
" を表します。
アセンブリ ファイルは、ファイル拡張子 .dll
を持つダイナミック リンク ライブラリ (DLL) に基づいています。
この記事全体を通して、プレースホルダー {FILE EXTENSION}
は "dll
" を表します。
プロジェクト ファイルの構成
アプリのプロジェクト ファイル (.csproj
) 内で、BlazorWebAssemblyLazyLoad
項目を使用して、遅延読み込みのマークをアセンブリに付けます。 ファイル拡張子が含まれるアセンブリ名を使用します。 Blazor フレームワークを使用すると、アプリの起動時にアセンブリは読み込まれません。
<ItemGroup>
<BlazorWebAssemblyLazyLoad Include="{ASSEMBLY NAME}.{FILE EXTENSION}" />
</ItemGroup>
プレースホルダー {ASSEMBLY NAME}
はアセンブリの名前、プレースホルダー {FILE EXTENSION}
はファイル拡張子です。 ファイル拡張子が必要です。
アセンブリごとに 1 個の BlazorWebAssemblyLazyLoad
項目を含めます。 アセンブリに依存関係がある場合は、依存関係ごとに BlazorWebAssemblyLazyLoad
エントリを含めます。
Router
コンポーネントの構成
Blazor フレームワークを使用すると、クライアント側の Blazor WebAssembly アプリである LazyAssemblyLoader で、アセンブリの遅延読み込みを行うシングルトン サービスが自動的に登録されます。 LazyAssemblyLoader.LoadAssembliesAsync メソッド:
- JS 相互運用を使用して、ネットワーク呼び出しを介してアセンブリをフェッチします。
- ブラウザー内の WebAssembly で実行されているランタイムにアセンブリを読み込みます。
Note
"ホストされた" Blazor WebAssembly ソリューションに関するガイダンスについては、「ホストされた Blazor WebAssembly ソリューションでのアセンブリの遅延読み込み」のセクションを参照してください。
Blazor の Router は、Blazor がルーティング可能なコンポーネントを求めて探索するアセンブリを指定し、ユーザーが移動するルートのコンポーネントをレンダリングする役割も担うコンポーネントです。 Router コンポーネントの OnNavigateAsync
メソッドは、ユーザーが要求するエンドポイント用の正しいアセンブリを読み込むために、遅延読み込みと組み合わせて使用されます。
LazyAssemblyLoader を使用して読み込むアセンブリを決定するロジックは、OnNavigateAsync 内に実装されています。 ロジックを構成する方法のオプションは次のとおりです。
- OnNavigateAsync メソッド内での条件チェック。
- ルートをアセンブリ名にマップするルックアップ テーブル (コンポーネントに挿入されるか、
@code
ブロック内に実装される)。
次に例を示します。
- Microsoft.AspNetCore.Components.WebAssembly.Services の名前空間が指定されています。
- LazyAssemblyLoader サービスが挿入されます (
AssemblyLoader
)。 {PATH}
プレースホルダーは、アセンブリのリストを読み込む場所のパスです。 この例では、1 つのアセンブリ セットを読み込む 1 つのパスの条件付きチェックを使用しています。- プレースホルダー
{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 マークアップ (...
) の内容が示されていません。 完全なコードのデモンストレーションについては、この記事の「コード例全体」のセクションを参照してください。
Note
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 型にアクセスするために、System.Reflection の名前空間がApp.razor
ファイルの先頭に含まれています。{PATH}
プレースホルダーは、アセンブリのリストを読み込む場所のパスです。 この例では、1 つのアセンブリ セットを読み込む 1 つのパスの条件付きチェックを使用しています。- プレースホルダー
{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 マークアップ (...
) の内容が示されていません。 完全なコードのデモンストレーションについては、この記事の「コード例全体」のセクションを参照してください。
Note
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>();
コード例全体
このセクションのデモンストレーションの内容は次のとおりです。
Robot
コンポーネント (/robot
のルート テンプレートを使用したRobot.razor
) を含む Razor クラス ライブラリ (RCL) としてロボット コントロール アセンブリ (GrantImaharaRobotControls.{FILE EXTENSION}
) を作成します。- ユーザーから
Robot
の URL が要求されたときに/robot
コンポーネントをレンダリングするために、RCL のアセンブリを遅延読み込みします。
Razor クラス ライブラリのアセンブリの遅延読み込みを示す、スタンドアロン Blazor WebAssembly アプリを作成します。 プロジェクトに 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
パッケージを追加します。
Note
.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 プロジェクトへのパスです。
LazyLoadTest
アプリのプロジェクト ファイル (.csproj
) で、遅延読み込みの対象である RCL のアセンブリを指定します。
<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);
}
}
}
アプリケーションをビルドし、実行します。
RCL の Robot
コンポーネントが /robot
で要求されると、GrantImaharaRobotControls.{FILE EXTENSION}
アセンブリが読み込まれ、Robot
コンポーネントがレンダリングされます。 アセンブリの読み込みは、ブラウザーの開発者ツールの [ネットワーク] タブで確認できます。
トラブルシューティング
- レンダリングが予期しないものになった (たとえば、前のナビゲーションのコンポーネントがレンダリングされた) 場合は、キャンセル トークンが設定されている場合にコードがスローされることをご確認ください。
- 遅延読み込み用に構成したアセンブリがアプリの開始時に予期せず読み込まれた場合は、プロジェクト ファイルでアセンブリが遅延読み込み対象としてマークされていることを確認します。
Note
遅延読み込みされたアセンブリから型を読み込む場合、既知の問題が存在します。 詳細については、「Blazor WebAssembly lazy loading assemblies not working when using @ref attribute in the component (dotnet/aspnetcore #29342)」を参照してください。
その他のリソース
ASP.NET Core