ステップ・バイ・ステップで学ぶ
Blazor アプリケーション開発ガイド
Blazorの始め方
Blazorは、現代のWebアプリケーション開発において、.NET アプリケーション開発者の間で圧倒的な支持を得ている.NETウェブフレームワークの一つです。C#やRazor構文、HTMLを使って、クライアントサイドのシングルページアプリケーション(SPA)を簡単に構築できます。Blazorは、コンポーネントベースのアーキテクチャ、ページ間で共有可能なUI要素、JavaScriptの相互運用性など、多彩な特徴を持ち、Web開発における生産性を飛躍的に向上させます。
このBlazorアプリケーション開発ガイドでは、Blazor機能を多数の例やコードスニペットを交えてわかりやすく紹介しています。ぜひ、このガイドを参考にして、Blazorの可能性を最大限に引き出し、次のWebアプリケーション開発にお役立てください。
Blazorの基本
Blazorとは?
Blazor は、C#、Razor 構文、HTML を使用してクライアントサイドアプリケーションを作成することができる .NET ウェブフレームワークです。C# を使ってリッチでモダンな Single Page Application (SPA) を作成し、好きなブラウザ上で実行することができます。
「Blazor」という名前は、このフレームワークがC#とRazorをブラウザー上で実行できることに由来しています。"ブラウザ "と "Razor "を組み合わせることで、"Blazor "という名前になったそうです。
なぜBlazorを使用すべきか?
- Blazorは、モダンで機能豊富な言語であるC#を使用して、リッチでインタラクティブなUIを作成することができます。
- アプリのロジックをクライアントとサーバーの両方で共有することで、再利用することができます。これにより、フルスタックの.NET開発体験が可能になります。
- 既存の.NET APIやツールを利用して、リッチなWebアプリケーションを作成することができます。
- Blazorは、Visual StudioとVisual Studio Codeの両方でサポートされています。これにより、Linux、Windows、Macなど、複数のプラットフォームで優れた.NET開発体験を提供します。
- モバイルブラウザを含むすべてのモダンブラウザでサポートされています。
- オープンソースのフレームワークであり、コミュニティによるサポートも充実しています。
- IIS、Azure App Service、Dockerを使用してアプリを簡単にホストすることができます。
Blazorの特徴
- コンポーネントベースのアーキテクチャ:Blazorは、リッチでコンポーザブルなUIを作成するためのコンポーネントベースのアーキテクチャを提供します。
- レイアウト: レイアウト機能により、ページ間で共通のUI要素(例えばメニューなど)を共有することができます。
- JavaScriptの相互運用:これにより、JavaScriptからC#のメソッドを呼び出したり、C#のコードからJavaScriptの関数やAPIを呼び出したりすることができるようになります。
- ルーティング:ルーティングの助けを借りて、クライアントリクエストをあるコンポーネントから別のコンポーネントにリダイレクトすることができます。
- フォームと検証:ユーザーの入力を処理するための対話型フォームを作成し、フォームのエラーを処理するための検証技術を適用することができます。
- 状態の管理:ユーザーに対するアプリの状態を、ブラウザーのメモリーに永続化させることができます。但し、ユーザーがブラウザーを開き直したり、ページを再読み込みしたりすると、ブラウザーのメモリーに保持されていたユーザーの状態は失われます。
- グローバリゼーションとローカライゼーション:Blazorアプリは、複数の文化や言語のユーザーがアクセスできるようにすることができます。これにより、アプリケーションのリーチを世界中のユーザーに拡大することができます。
- Progressive Web Application:BlazorアプリをProgressive Web Applicationとして作成することができます。これにより、Blazorアプリはオフラインで動作し、ユーザーのネットワーク速度に関係なく瞬時に読み込まれるようになります。
- アセンブリの遅延読み込み:アセンブリの遅延読み込みを使用すると、一部のアプリケーションアセンブリの読み込みを必要な時まで遅らせることができます。これにより、アプリケーションの起動時のパフォーマンスが向上します。
- デバッグ:Blazor WebAssemblyアプリは、Microsoft EdgeやGoogle ChromeなどのChromiumベースのブラウザーでデバッグすることができ、Firefoxでのデバッグサポートもプレビューが開始されています。また、Visual StudioやVisual Studio Code IDEでアプリのデバッグが可能です。
Blazorホスティングモデル
Blazor のコンポーネントモデルは、UI の変更を計算する役割を担っています。しかし、異なるレンダラーを使用して、UI の表示と更新の方法を制御することができます。これらのレンダラーはホスティング モデルとして知られています。
Blazorは2つのホスティングモデルをサポートしています。
- Blazor WebAssembly
- Blazor Server
Blazor Server 実行モデル
Blazor Server vs WebAssembly vs ハイブリッド
Blazor Serverとは何ですか
Blazor Server ホスティングモデルは、Blazorアプリケーションをフルの.NETランタイム上でサーバー上で実行することを可能にします。
Blazor Serverはどのように動作しますか?
Blazor Server の実行モデルを下の画像に示します:
ユーザーがアプリケーションを開くと、ブラウザーにblazor.server.jsという小さなJavaScriptファイルがダウンロードされ、サーバーとの間にリアルタイムで双方向のコミュニケーションが可能になるSignalR接続が確立されます。
アプリケーションとユーザーのやり取りは、SignalR接続を経由して規定ではWebSocketプロトコルを使用してサーバーに送信されます。サーバーは、ユーザーの要求を処理します。サーバーの処理が完了すると、UIの更新情報があればクライアントに送信され、ブラウザー上のDOMに適用されます。
サーバーは、接続された各ユーザーの状態を保持します。この状態はサーキットと呼ばれます。
ブラウザーでアプリケーションを起動すると、サーキットが作成されます。ブラウザー上のアプリケーションのインスタンスごとに、サーバー上に新しいサーキットが作成されます。これは、同じブラウザーの2つの異なるタブでアプリケーションを開いた場合、サーバー上に2つのサーキットが作成されることを意味します。
ブラウザーを閉じるか、外部のURLに移動してアプリを閉じると、サーキットと関連リソースはすぐに解放されます。
Blazor Serverを利用するメリット
Blazor Serverの利用は、以下のような利点があります。
- Blazor WebAssembly アプリと比較して、アプリのダウンロードサイズが大幅に小さくなっています。これは、アプリの読み込みを高速化します。
- Blazor Server アプリは、.NET互換のAPIなど、サーバーの機能をフル活用します。
- アプリケーションがサーバー上で実行されるため、デバッグなど既存の.NETツールをフル活用することができます。
- アプリのコードベースは、クライアントと共有されません。
Blazor Serverをどのような場合に採用しますか?
Blazor Server アプリは、以下のような場合に使用することが望ましいです。
- アプリの読み込みを速やかに行いたいとき。
- アプリからサーバーやネットワークリソースにアクセスさせたい場合。
- ユーザーとのやり取りはすべてネットワークを経由するため、Blazor Server アプリでは通信遅延がアプリの性能に影響します。したがって、通信遅延時間が長くても問題ではない場合にBlazor Serverを使用します。
- 重い処理はすべてServerが行うので、クライアントのリソースが限られている場合は、Blazor Serverが好まれます。
- Blazor Serverは、WebAssemblyをサポートしていないブラウザに適しています。
Blazor WebAssembly
Blazor WebAssemblyとは何ですか?
Blazor WebAssembly(WASM) ホスティングモデルは、WebAssembly ベースの .NET ランタイム上で Blazor アプリをブラウザ上でクライアントサイドに実行できるようにするものです。これは Blazor の主要なホスティングモデルです。
Blazor WebAssemblyはどのように機能するのですか?
以下の画像には、Blazor WebAssembly 実行モデルが示されています。:
ユーザーがアプリケーションを読み込むと、小さなJavaScriptファイル(blazor.webassembly.js)がブラウザーにダウンロードされます。
このファイルでは、次の2つの操作を行います。
- ブラウザーには、Blazorアプリとその依存関係とともに.NETランタイムがダウンロードされます。
- アプリを実行するための.NETランタイムを初期化します。
アプリの実行は、ブラウザーのUIスレッドで直接行われます。UIの更新やイベント処理も、同じプロセス内で行われます。
静的なコンテンツをクライアントに提供することができるWebサーバーやその他のサービスを使用して、アプリのアセットを静的なファイルとして展開することができます。
Blazor WebAssemblyアプリの種類
Blazor WebAsseblyのアプリは2種類あります。
- スタンドアロン:バックエンドのASP.NET Coreアプリなしに、Blazor WebAssemblyアプリをデプロイメント用に作成した場合、そのアプリは「スタンドアロンのBlazor WebAssemblyアプリ」と呼ばれます。
- ホスト 型:アプリがそのファイルを提供するバックエンドアプリとともにデプロイメント用に作成された場合、そのアプリは「ホスト型 Blazor WebAssemblyアプリ」と呼ばれます。ホスト型アプリは、.NETによるフルスタックのWeb開発体験を提供します。クライアントアプリとサーバーアプリの間でコードを共有することができ、プリレンダリングや、MVCやRazor Pagesとの統合をサポートします。
Blazor WebAssemblyを使用するメリット
Blazor WebAssemblyを利用することで、以下のような利点があります。
- アプリがクライアントにダウンロードされた後は、サーバーサイドへの依存はありません。このため、サーバーがオフラインになっても、アプリの機能は維持されます。
- クライアントが重い処理を行うので、サーバーの負荷が少なくなります。
- アプリはクライアントのリソースをフルに活用します。
- アプリをホストするサーバーが不要なため、サーバーレスのデプロイメントシナリオがサポートされます。
Blazor WebAssemblyをどのような場合に採用しますか?
Blazor WebAssembly アプリは、以下のような場合に優先的に使用されます。
- アプリにクライアントのリソースを活用させたいとき。
- クライアントがインターネットに接続できない場合、アプリをオフラインで実行する必要がある場合。
- アプリを静的なサイトとしてホストしたい場合。
Blazor Hybrid
Blazor Hybridとは何ですか?
Blazor Hybridでは、.NET、HTML、CSS を使ってネイティブクライアントアプリを作成することができます。.NET MAUI、WPF、Windows Forms などの既存の .NET ネイティブアプリフレームワークを使用して Blazor Hybridアプリを作成することができます。
Blazor Hybridの仕組みは?
Blazor Hybridアプリは、デバイス上で razor コンポーネントをネイティブに実行します。Blazor コンポーネントは、ローカル interop チャンネルを介して、埋め込み Web View コントロールにレンダリングされます。コンポーネントは、ネイティブ アプリにあり、ブラウザにはありません。したがって、WebAssemblyはハイブリッドアプリには関係ありません。
Blazor Hybridアプリは、.NET API を介してネイティブ プラットフォームの機能にアクセスすることができます。ネイティブアプリである Blazor Hybridアプリは、Web プラットフォームだけでは利用できない機能性をサポートすることができます。また、Blazor Serverや Blazor WebAssembly アプリケーションの既存の razor コンポーネントを Blazor Hybridアプリで共有、再利用することができます。
Blazor Hybridを使用するメリット
- Blazor Server&Blazor WebAssemblyアプリケーションの既存コンポーネントを再利用することができます。これにより、モバイル、デスクトップ、Webの各プラットフォームでコードの共有と再利用が可能になります。
- アプリは、デバイスのネイティブ機能を活用することができます。
Blazor Hybridをどのような場合に採用しますか?
Blazor Hybridアプリは、以下のような場合に使用することが望ましいとされています。
- .NETのAPIを使ってネイティブアプリを作りたいとき。
- ネイティブクライアントの機能を活用したいとき。
- アプリをオフラインで動作させる必要があるとき。
- アプリのデータ処理をクライアントにオフロードしたいとき
Blazorコンポーネント
Blazorコンポーネントとは何ですか?
Blazor コンポーネントは、ナビゲーションバー、ボタン、フォームなどの UI の一部として定義されます。 Blazor コンポーネントは、Razor コンポーネントファイルとして作成され、拡張子は ".razor" です。
コンポーネントは再利用可能であり、複数のプロジェクトで共有することができます。Blazorコンポーネントは、Razorコンポーネントとも呼ばれます。
Razorは、HTMLとC#のコードを結合するためのマークアップ構文です。
Blazor コンポーネントの名前は、大文字で始まる必要があります。
例:LoginForm.razor という名前は有効なコンポーネント名です。一方、loginForm.razorという名前は無効なコンポーネント名です。
以下に示すコンポーネントコードの例を見てください。
@page "/counter"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status" >Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
「@page」ディレクティブは、コンポーネントのルートを指定するために使用されます。「@code」ディレクティブは、コンポーネントのC#コードを指定するために使用されます。ボタンクリックイベントで「IncrementCount」メソッドが呼び出されます。
また、1つのコンポーネントに対して、複数の異なる「@page」ディレクティブを使用することができます。2 つの異なるコンポーネントに同じルートを設定することはできません。実行時エラーになります。
コンポーネント基本クラス
HTMLとC#のロジックを分離するために、コンポーネントのベースクラスを作成することができます。ベースクラスはComponentBaseクラスから派生させる必要があります。コンポーネントの基底クラスを継承するには、@inheritsディレクティブを使用します。
以下のようなベースクラスWelcome.razor.csを作成しました。
using Microsoft.AspNetCore.Components;
namespace BlazorTutorial.Client.Pages;
public class WelcomeBase : ComponentBase
{ public string WelcomeMessage { get; set; } = "Welcome to Blazor tutorial."; }
以下に示すように、RazorコンポーネントWelcome.razorを作成しました。
@page "/welcome"
@inherits WelcomeBase
<h1>@WelcomeMessage</h1>
ベースクラスを継承するために@inheritsディレクティブを使用しています。
コンポーネント パラメーター
コンポーネントパラメータは、親コンポーネントから子コンポーネントへのデータの受け渡しに使用されます。
例を挙げます
以下のように、コンポーネントChild.razorを作成します。
<h3>Child Component</h3>
<p>The sum of number is : @(num1 + num2)</p>
@code {
[Parameter]
public int num1 { get; set; }
[Parameter]
public int num2 { get; set; }
}
コンポーネントのパラメーターは[Parameter]属性で注釈されています。このコンポーネントは、整数型のパラメーターを2つ受け取り、両方の数値の合計を表示します。
以下のように、別のコンポーネントParent.razorを作成します。
@page "/parent"
<h3>Parent Component</h3>
<Child num1="5" num2="10"><Child>
親コンポーネントの内部で子コンポーネントを起動し、両方のパラメーターを渡しています。
また、C#のプロパティを「@」記号でパラメーター値に割り当てることもできます。
以下に示す例をご覧ください。
@page "/parent"
<h3>Parent Component</h3>
<Child num1="@number1" num2="@number2"><Child>
@code {
int number1 = 5;
int number2 = 10;
}
パラメーターは、[EditorRequired]属性で必須とすることができます。
以下に示す例をご覧ください。
<h3>Child Component</h3>
<p>The sum of number is : @(num1 + num2)<p>
@code {
[Parameter]
[EditorRequired]
public int num1 { get; set; }
[Parameter]
[EditorRequired]
public int num2 { get; set; }
}
[EditorRequired]属性を使用しながら、[Parameter]属性を使用する必要があります。
コンポーネントを呼び出す際に必要なパラメーターを与えなければ、コンパイル時の警告が表示されます。
また、コンポーネントのパラメーターにデフォルト値を設定することも可能です。
以下に示す例をご覧ください。
@page "/welcome"
<h1>WelcomeMessage</h1>
@code {
[Parameter]
public string WelcomeMessage { get; set; } = "Welcome";
}
Razorコンポーネントのライフサイクル
Razor のコンポーネントは、作成されてから削除されるまで、一連のライフサイクルイベントを経ます。ライフサイクルには、同期と非同期のメソッドがあります。Blazor フレームワークでは、ライフサイクルメソッドをオーバーライドすることで、コンポーネントに対して追加の操作を行うことができます。
SetParametersAsync
このメソッドは、パラメーターが設定される前に呼び出されます。これは、親コンポーネントまたはコンポーネントルートから供給されたパラメーターを設定します。コンポーネントのパラメーターに値を設定するには、base.SetParametersAsync()メソッドを呼び出す必要があります。そうでなければ、パラメーターを操作するための独自のコードを記述する必要があります。
OnInitialize
このメソッドは、パラメーターが設定される前に呼び出されます。親から初期パラメーターを受け取った後、コンポーネントが起動できるようになったときに呼び出されます。
非同期バージョンはOnInitializedAsyncと呼ばれます。非同期処理を行い、その処理が完了した時点でコンポーネントを更新したい場合は、OnInitializedAsyncをオーバーライドしてください。
OnParametersSet
このメソッドは、パラメーターが設定された後に呼び出されます。このメソッドは、コンポーネントが親からパラメーターを受け取り、受け取った値がプロパティに割り当てられたときに呼び出されます。このメソッドは、パラメーターが更新されるたびに実行されます。非同期バージョンはOnParametersSetAsyncと呼ばれます。
OnAfterRender
このメソッドは、コンポーネントのレンダリングが終了した後、つまり HTML がすでに表示された後に呼び出されます。このメソッドは、レンダリングされた DOM 要素を使用するサードパーティの JavaScript ライブラリをアクティブにするなど、コンポーネントの初期化に必要な追加ステップを実行するために使用されます。このメソッドは、コンポーネントがレンダリングされるたびに実行されます。非同期バージョンはOnAfterRenderAsyncとして知られています。
StateHasChanged
このメソッドは、コンポーネントの状態が変更されたことを通知し、コンポーネントを再レンダリングします。EventCallbackの処理においては、このメソッドは自動的に呼び出され、親コンポーネントを再レンダリングします。
このメソッドを呼び出すと、コンポーネントをいつでもレンダリングできるようになります。しかし、StateHasChangedの呼び出しが多すぎると、アプリケーションに不必要なレンダリングコストを追加することになります。
例を挙げます
以下のようにベースクラスLifecycle.razor.csを作成しました。
using Microsoft.AspNetCore.Components;
namespace BlazorTutorial.Client.Pages;
public class LifecycleBase : ComponentBase
{ public override async Task SetParametersAsync(ParameterView parameters) { Console.WriteLine("SetParametersAsync-start"); await base.SetParametersAsync(parameters); Console.WriteLine("SetParametersAsync-end"); } protected override void OnInitialized() { Console.WriteLine("OnInitialized-start"); base.OnInitialized(); Console.WriteLine("OnInitialized-end"); } protected override async Task OnInitializedAsync() { Console.WriteLine("OnInitializedAsync-start"); await base.OnInitializedAsync(); Console.WriteLine("OnInitializedAsync-end"); } protected override void OnParametersSet() { Console.WriteLine("OnParametersSet-start"); base.OnParametersSet(); Console.WriteLine("OnParametersSet-end"); } protected override async Task OnParametersSetAsync() { Console.WriteLine("OnParametersSetAsync-start"); await base.OnParametersSetAsync(); Console.WriteLine("OnParametersSetAsync-end"); } protected override void OnAfterRender(bool firstRender) { Console.WriteLine("OnAfterRender({0})-start", firstRender); base.OnAfterRender(firstRender); Console.WriteLine("OnAfterRender({0})-end", firstRender); } protected override async Task OnAfterRenderAsync(bool firstRender) { Console.WriteLine("OnAfterRenderAsync({0})-start", firstRender); await base.OnAfterRenderAsync(firstRender); Console.WriteLine("OnAfterRenderAsync({0})-end", firstRender); } }
以下のように、RazorコンポーネントLifecycle.razorを作成しました。
@page "/lifecycle"
@inherits WelcomeBase
<h1>Blazor component lifecycle example</h1>
アプリケーションを実行し、ライフサイクル・コンポーネントに移動すると、以下のようにブラウザ・コンソールに出力が表示されます。
この出力は、RazorコンポーネントのLifecycleメソッドの実行順序を示しています。
Blazorカスケード値及びパラメーター
カスケード値とパラメーターは、あるコンポーネントからそのすべての子孫のコンポーネントにデータを渡すことを可能にします。コンポーネントは<CascadingValue>コンポーネントを使用してカスケード接続された値を提供することができます。
親コンポーネントが提供するカスケード値を利用するために、子コンポーネントは [CascadingParameter] 属性を用いてカスケードパラメーターを宣言することができます。
カスケード接続された値は、データ型によってカスケード接続されたパラメーターに束縛されます。同じ型の複数の値をカスケード接続したい場合は、それぞれの[CascadingParameter]属性に一意な名前を指定します。
例を挙げます
以下のように、コンポーネントChild.razorを作成します。
<h3>Child Component</h3>
<p>The sum of number is : @(num1 + num2)</p>
@code {
[CascadingParameter(Name = "FirstNumber")]
public int num1 { get; set; }
[CascadingParameter(Name = "SecondNumber")]
public int num2 { get; set; }
}
以下のように、コンポーネントParent.razorを作成します。
@page "/parent"
<h3>Parent Component</h3>
<CascadingValue Value="@number1" Name="FirstNumber" >
<CascadingValue Value="@number2" Name="SecondNumber" >
<Child></Child>
</CascadingValue>
</CascadingValue>
@code {
int number1 = 5;
int number2 = 10;
}
子コンポーネントを呼び出して、カスケード接続された値を渡しています。子コンポーネントは、Nameプロパティを使ってパラメーター値をバインドします。
コンポーネント階層をまたぐデータの受け渡し
カスケードパラメータを使って、コンポーネント階層を越えてデータを渡すことができます。
例を挙げます
以下のように、コンポーネントGrandChild.razorを作成します。
<h3>Grand Child Component</h3>
<p>The product of number is : @(num1 * num2)</p>
@code {
[CascadingParameter(Name = "FirstNumber")]
public int num1 { get; set; }
[CascadingParameter(Name = "SecondNumber")]
public int num2 { get; set; }
}
以下のように、コンポーネントChild.razorを作成します。
<h3>Child Component</h3>
<p>The sum of number is : @(num1 + num2)</p>
<GrandChild></GrandChild>
@code {
[Parameter]
public int num1 { get; set; }
[Parameter]
public int num2 { get; set; }
}
ParentコンポーネントからGrandChildコンポーネントに、カスケード接続された値を渡しています。カスケード・パラメータの名前は、ChildとGrandChildの両方のコンポーネントで同じであることに注意してください。
実行すると、以下のような出力が表示されます。
Blazorのデータバインディング
単方向データバインディング
単方向データバインディングは、プロパティの値をHTML DOM要素にバインドすることができます。その逆はできません。プロパティやフィールド変数をHTMLタグに結びつけるには、@記号を先頭に付けてプロパティまたはフィールド変数名を渡す必要があります。
以下に示す例をご覧ください。
@page "/databinding"
<h3>One way data binding</h3>
<p>@SampleText</p>
@code {
string SampleText = "This is a sample text depicting one-way data-binding";
}
C#のフィールド変数SampleTextは、@マークを用いてHTML DOMにバインドされています。
双方向データバインディング
双方向データバインディングによって、プロパティやフィールド変数の値をHTML DOM要素にバインドしたり、逆にHTML DOMからの入力プロパティやフィールド変数にバインドしたりすることができます。双方向データバインディングを実現するには、@bind 属性を使用します。
HTML要素の@bind:event="{EVENT}"属性({EVENT}はDOMイベントのプレースホルダー)を使って、DOMイベントにC#プロパティをバインドすることができます。
以下に示す例をご覧ください。
@page "/databinding"
<h3>Two way data binding</h3>
<div>
<span>Enter your Name: </span>
<input type="text" @bind="Name":@bind:event="oninput" />
</div>
<br/>
<p>You name is: @Name</p>
@code {
string Name { get; set; }
}
入力フィールドの値は、Nameプロパティにバインドされます。データバインディングは、入力フィールドの oninput イベントがトリガーされたとき、つまりテキストボックスの値が変更されたときに行われます。
注:属性バインディングは、大文字と小文字を区別します。つまり、@bind, @bind:event は有効ですが、@Bind, @Bind:EVENT などの大文字を使った構文は無効です。
<select>要素で複数の選択肢を束ねる
複数選択から得られる値を、配列型のC#プロパティにバインドすることができます。
以下に示す例をご覧ください。
@page "/databinding"
<p>
<label>
Select one or more days:
<select @onchange="SelectedDaysChanged" multiple>
<option value="monday">Monday</option>
<option value="tuesday">Tuesday</option>
<option value="wednesday">Wednesday</option>
<option value="thursday">Thursday</option>
<option value="friday">Friday</option>
</select>
</label>
</p>
<p>
Selected Days: @string.Join(", ", SelectedDays)
</p>
@code {
public string[] SelectedDays { get; set; } = new string[] { };
void SelectedDaysChanged(ChangeEventArgs e) {
if (e.Value is not null) {
SelectedDays = (string[])e.Value;
}
}
}
選択された値は、<select>要素の@onchangeイベントを使って文字列の配列にバインドされます。
フォーマットされたsringにバインドする
データバインディングは、@bind:format="{FORMAT STRING}"という構文を使って、フォーマットされた文字列で動作します。
フォーマットされたデータバインディングは、以下の.NETタイプでサポートされています。
- System.DateTime
- System.DateTimeOffset
以下に示す例をご覧ください。
@page "/databinding"
<h3>Formatted string</h3>
<div>
<span>The Sample date is: </span>
<input @bind="SampleDate" @bind:format="dd-MM-yyyy" />
</div>
@code {
DateTime SampleDate { get; set; } = new DateTime(2023, 1, 14);
}
日付は、@bind:format属性で指定されたフォーマットで表示されます。
カスタムバインディングフォーマットは、getおよびsetアクセサを使用して指定することができます。
以下に示す例をご覧ください。
@page "/databinding"
<input @bind="SalaryValue" />
<h3>Formatted string</h3>
<p>
<code>decimalValue</code>: @salary
</p>
@code {
decimal salary = 123456;
string SalaryValue {
get => salary.ToString("0.000");
set {
if (Decimal.TryParse(value, out var number)) {
salary = Math.Round(number, 3);
}
}
}
}
文字列プロパティSalaryValueは、入力要素に小数点以下3桁まででバインドされます。
コンポーネントパラメータを用いたバインディング
子コンポーネントのプロパティを、その親コンポーネントのプロパティにバインドすることができます。データバインディングは複数のレベルで行われるため、このシナリオは連鎖バインディングと呼ばれます。
ここで、{PROPERTY}はバインドするプロパティのプレースホルダーです。子コンポーネントから親コンポーネントのプロパティの更新をサポートするために、イベントハンドラと値を提供する必要があります。
例を挙げます。
以下のように、コンポーネントChild.razorを作成します。
<h3>Child Component</h3>
<p>Child Message: @Message</p>
<button @onclick="UpdateMessageFromChild">Update Message from Child</button>
@code {
[Parameter]
public string Message { get; set; }
[Parameter]
public EventCallback <string> MessageChanged { get; set; }
private async Task UpdateMessageFromChild() {
await MessageChanged.InvokeAsync("Message from child component");
}
}
文字列型のコンポーネントパラメータと、コンポーネントパラメータと同じ型のEventCallbackを宣言しています。
EventCallbackの名前は、{PARAMETER NAME}Changedという構文に従わなければなりません。 {PARAMETER NAME}はコンポーネントパラメータ名のプレースホルダです。他の命名形式を使用すると、ランタイムエラーが発生します。
上記の例では、コンポーネントパラメータは Message、EventCallback は MessageChanged という名前になっています。
以下のように、コンポーネントParent.razorを作成します。
@page "/parent"
<h3>Parent Component</h3>
<Child @bind-Message="MesssageFromParent"></Child>
<button @onclick="UpdateMessageFromChild">Update Message from Child</button>
@code {
string MesssageFromParent = "Message from parent component";
}
C#のプロパティMesssageFromParentは、ChildコンポーネントのMessageパラメータにバインドされています。Child コンポーネントの Message パラメータは、Message パラメータと同じタイプの MessageChanged イベントを持つので、バインド可能です。
実行すると、以下のような出力が表示されます。
ボタンをクリックするとすぐに、下の画像のようにメッセージが更新されます。
Blazorのイベントハンドリング
HTML 属性に @on{EVENT} という名前で、delegate 型の値を追加します。この属性の値は、Blazor コンポーネントによってイベントハンドラとして扱われます。
Blazor がサポートするイベントハンドラには、@onclick, @onchange, @onselect, @onfocus, @onkeyup 等があります。
例
<button @onclick="ButtonClicked">Click me</button>
@code {
void ButtonClicked() {
Console.WriteLine("button clicked");
}
}
ボタンをクリックすると、ButtonClickedメソッドが呼び出されます。
非同期イベント処理
以下に示す例をご覧ください。
<button @onclick="ButtonClicked">Click me</button>
@code {
async Task ButtonClicked() {
await Task.Delay(1000);
Console.WriteLine("button clicked");
}
}
ButtonClickedメソッドは、ボタンがクリックされたときに非同期で呼び出されます。
イベント引数の使用
以下に示す例をご覧ください。
<select class="form-control col-md-4" @onchange="SelectGender">
<option value="">-- Select Gender --</option>
<option value="Male">Male</option>
<option value="Famale">Famale</option>
</select>
@code {
protected string Gender { get; set; }
protected void SelectGender(ChangeEventArgs e) {
Gender = e.Value.ToString();
}
}
SelectGender メソッドを select 要素の onchange イベントにバインドしています。
イベントメソッド定義におけるイベント引数の指定は任意です。イベント引数をメソッド内で使用する場合のみ、必須となります。
イベント処理にラムダ式を使用する
ラムダ式を使って、イベント属性に無名関数を割り当てることができます。
以下に示す例をご覧ください。
@for (int i = 1; i < 4; i++) {
int textboxNumber = i;
<p>
<input> @onfocus="@(() => FocusTextbox(textboxNumber))" />
</p>
}
@code {
protected string Gender { get; set; }
protected void FocusTextbox(int textboxNumber) {
Console.WriteLine($"You have selected textbox number {textboxNumber}");
}
}
for ループを使用して 3 つのテキスト ボックス コントロールを作成します。各テキストボックスの onfocus イベントで FocusTextbox メソッドを呼び出すには、ラムダ式を使用しています。
EventCallback
EventCallbackは、複数のコンポーネントにまたがるイベントを公開するために使用することができます。これは、子コンポーネントでイベントが発生したときに、親コンポーネントのメソッドを呼び出すのに役立ちます。
EventCallback<TValue>という構文でイベントパラメータを指定することで、EventCallbackを強く型付けすることができます。
例- EventCallback<MouseEventArgs>
例題をもとにEventCallbackを理解しましょう。
以下のように、コンポーネントChild.razorを作成します。
<h3>Child Component</h3>
<button class="btn btn-primary"
@onclick="ChildClick">
Invoke parent component method
</button>
@code {
[Parameter]
public EventCallback ChildClick { get; set; }
}
以下のように、別のコンポーネントParent.razorを作成します。
<h3>Parent Component</h3>
<Child ChildClick="ChildClickHandler"> </Child>
<p><strong> @message </strong></p>
@code {
string message { get; set; }
void ChildClickHandler() {
message = "Child event has occurred";
}
}
子コンポーネントにEventCallbackパラメーターを作成しました。ボタンのonclickイベントハンドラは、ParentComponentからEventCallbackデリゲートを受信するように設定されています。
ParentComponentは、子コンポーネントのEventCallbackであるChildClickを、そのChildClickHandlerメソッドに設定します。
子コンポーネントのボタンをクリックすると、親コンポーネントのChildClickHandlerメソッドが呼び出されます。文字列プロパティのメッセージが更新され、ParentComponentに表示されます。
既定のアクションを止める
イベントの既定のアクションを阻止するには、@on{DOM EVENT}:preventDefault ディレクティブ属性を使用します。この属性は、ブール値を引数として受け取ります。もし、引数を指定しなければ、デフォルトの値であるtrueが指定されたのと同じになります。
以下に示す例をご覧ください。
<input value="@name" @onkeydown="KeyDownHandler" @onkeydown:preventDefault />
@code {
private string name { get; set; }
private void KeyDownHandler(KeyboardEventArgs e) {
// 何らかの処理
}
}
Boolean型の値はプロパティにバインドすることができるので、ユーザーの要求に基づいてイベントの既定のアクションを制御することができます。
以下に示す例をご覧ください。
<input @onkeydown="KeyDownHandler" @onkeydown:preventDefault="shouldPreventDefault" />
@code {
private bool shouldPreventDefault = true;
}
イベント伝達を停止する
HTML要素がその親要素にイベントを伝達することがあります。
Blazor では、@on{DOM EVENT}:stopPropagation ディレクティブ属性を使って、イベントの伝達を停止させることができます。この属性は、引数としてブール値を受け取ります。引数を指定しない場合は、デフォルトの値である true が指定されたのと同じになります。
以下に示す例をご覧ください。
<button @onclick:stopPropagation>Click</button>
Boolean型の値はプロパティにバインドすることができるので、ユーザーの要求に基づいてイベントの伝達を制御することができます。
<button @onclick:stopPropagation="shouldStopPropagation">Click</button>
@code {
private bool shouldStopPropagation = true;
}
ルーティング&ナビゲーション
App.razor ファイルで使用される Router コンポーネントは、Blazor アプリケーションのコンポーネントへのルーティングを可能にします。
Blazor アプリケーションを作成すると、App.razor ファイルに以下のようなコードが記述されます。
<Router AppAssembly="@typeof(App).Assembly >
<Found Context="routeData" >
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" / >
<FocusOnNavigate RouteData="@routeData" Selector="h1" / >
</Found>
<NotFound>
<PageTitle>NotFound</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert"> Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
Router コンポーネントは、現在のナビゲーション状態に対応するルートデータを供給するために使用されます。FocusOnNavigateコンポーネントは、ユーザーがあるコンポーネントから別のコンポーネントに移動するときに、CSSセレクタに一致する要素にフォーカスを設定するために使用されます。これにより、スクリーンリーダーと互換性のある、アクセシブルなルーティング機構を作成することができます。
NotFound プロパティは、要求されたルートでデータが見つからない場合に、カスタムコンテンツを表示するために使用されます。
ページディレクティブを使用して、Razor コンポーネントのルートを定義します。また、1つのコンポーネントに対して、複数の異なる @page ディレクティブを使用することができます。
以下に示す例をご覧ください。
@page "/route1"
@page "/home/route1"
<h1>This component is accessible via multiple routes.</h1>
2つの異なるコンポーネントに同じルートを設定することはできません。これを行うと、ランタイムエラーが発生します。
ルートパラメーター
ルートパラメーターを定義することで、同じ名前のコンポーネントパラメータに入力することができます。1つのルートは複数のパラメータを持つことができます。
以下に示す例をご覧ください。
@page "/home/{Name}/{Message}"
<h1>Hello @Name</h1>
<h3>@Message</h3>
@code {
[Parameter]
public string Name { get; set; }
[Parameter]
public string Message { get; set; }
}
コンポーネントのルートパラメータプロパティは、public として定義する必要があります。それ以外のアクセス修飾子を使用すると、コンパイル時にエラーになります。
ルートパラメーター名は 大文字と小文字を区別しませんので、以下のように使用することができます。
@page "/home/{name}/{message}"
<h1>Hello @NAME</h1>
<h3>@MESSAGE</h3>
@code {
[Parameter]
public string NAME { get; set; }
[Parameter]
public string MESSAGE { get; set; }
}
Blazor はオプションのルートパラメータもサポートしています。ルートパラメータをオプションとしてマークするには、?記号を付けます。
以下に示す例をご覧ください。
@page "/home/{name}/{message?}"
<h1>Hello @Name</h1>
<h3>@Message</h3>
@code {
[Parameter]
public string Name { get; set; }
[Parameter]
public string Message { get; set; }
}
オプションのパラメーターは複数持つことができます。
以下のルートは 有効です。
@page "/home/{name}/{message?}/{text?}"
オプションでないパラメーターは、オプションのパラメーターの後に出現させることはできません。オプションのパラメーターの後にオプションでないパラメーターを追加すると、ランタイムエラーが発生します。
次のルートは無効です。
@page "/home/{name?}/{message}"
ルート制約
ルート制約を使用して、ルート上で型の一致を強制することができます。
以下に示す例をご覧ください。
@page "/home/{name}/{userID:int}/{isAdmin:bool}"
<h1>Hello @Name</h1>
<h3>User ID: @userID</h3>
<h3>isAdmin: @isAdmin</h3>
@code {
[Parameter]
public string Name { get; set; }
[Parameter]
public int userID { get; set; }
[Parameter]
public bool isAdmin { get; set; }
}
このコンポーネントのルートは、以下の条件を満たした場合にマッチします。
- ルートには、name パラメーターに値を指定する必要があります。
- ルートには userID パラメーターの値を指定する必要があり、int 型であることが必要です。
- ルートには isAdmin パラメーターの値を指定する必要があり、bool 型である必要があります。
このパターンにマッチするルートの例としては、次のようなものがあります。 – “/home/John/1234/true”
オプションのパラメーターでも同様に、ルート制約を使用することができます。
例
@page "/home/{name}/{userID:int}/{isAdmin:bool?}"
NavigationManager
NavigationManagerクラスを使用すると、C#コードでナビゲーションを処理することができます。このクラスはNavigateToメソッドを提供し、コンポーネントのルートをパラメータとして受け取り、ユーザーをあるコンポーネントから別のコンポーネントにリダイレクトさせます。
例を挙げます。
以下のようにベースクラスRouting.razor.csを作成します。
using Microsoft.AspNetCore.Components;
namespace BlazorWasmDemo.Client.Pages;
public class RoutingBase : ComponentBase
{ [Inject] public NavigationManager NavigationManager { get; set; }
[Parameter] public string Name { get; set; }
protected void NavigateAway() { NavigationManager.NavigateTo("parent/1234"); } }
Routing.razorコンポーネント内に以下のコードを追加します。
@page "/home/{name}"
@inherits RoutingBase
<h1>Hello @Name</h1>
<button @onclick="NavigateAway" >Navigate away</button>
ボタンのクリックでNavigateAwayメソッドを呼び出すことにします。これは、ユーザーをルート「/parent/1234」にリダイレクトします。
NavigateToメソッドには、オプションでforceLoadというBooleanパラメータがあります。このパラメータに真の値を渡すと、ブラウザはサーバーから新しいページを再読み込みします。
キャッチオールのルートパラメーター
キャッチオールのルートパラメーターは、ルーティングパラメーターが明示的に定義されていない場合に、様々なルートを処理するために使用することができます。
以下に示すコンポーネントの例をご覧ください。
@page "/home/{*routeParams}"
@code {
[Parameter]
public string? RouteParams { get; set; }
}
このコンポーネントでは、以下のルートが有効です。
- /home/John
- /home/123
- /home/John/123/345/USA/true
キャッチオールのルートパラメーターは,以下の条件を満たす必要があります。
- 同じ名前の対応するコンポーネントパラメータを持つ必要があります。この名前は大文字と小文字を区別しません。
- 文字列型である必要があります。ルート制約をかけることはできません
- URLの末尾に存在する必要があります。
NavLinkコンポーネント
ナビゲーションリンクを作成する際に、HTMLの属性の代わりにNavLinkコンポーネントを使用することができます。これは、現在のURLがhrefプロパティに一致するかどうかに基づいて、アクティブなCSSクラスの有無を切り替えます。これにより、ユーザーは、利用可能なすべてのナビゲーションリンクのうち、どのページがアクティブであるかを理解することができます。
例
<NavLink class="nav-link" href="" Match="NavLinkMatch.All" >
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
NavLinkコンポーネントのMatch属性には、2つの値を指定することができます。
- NavLinkMatch.All: 現在のURL全体に一致する場合のみ、NavLinkをアクティブにします。
- NavLinkMatch.Prefix: 現在のURLの先頭部分に一致すれば、NavLinkをアクティブにします。これはデフォルト値です。
クエリー文字列
[SupplyParameterFromQuery] 属性と [Parameter] 属性を併用することで、ルートのクエリ文字列からコンポーネントパラメータへ入力するように指定することができます。
@page "/movie"
<p>@Name</p>
<p>@Genre</p>
@code {
[Parameter]
[SupplyParameterFromQuery]
public string? Name { get; set; }
[Parameter]
[SupplyParameterFromQuery]
public string? Genre { get; set; }
}
このコンポーネントの有効なルートは “/movie?name=avatar&genre=science%20fiction” とすることができます。[SupplyParameterFromQuery] 属性Name プロパティを使用して、クエリパラメータの名前を指定することもできます。
例:
[Parameter]
[SupplyParameterFromQuery(Name="Category")]
public string? Genre { get; set; }
この場合、有効なルートは /movie?name=avatar&category =science%20fiction” となります。
コンポーネントのクエリパラメーターは、以下のデータ型をサポートしています。
- bool
- DateTime
- decimal
- double
- float
- Guid
- int
- long
- string
NavigationManagerクラスのGetUriWithQueryParameterメソッドを使用すると、現在のURLから1つまたは複数のクエリーパラメーターを追加、更新、削除することができます。
このメソッドの構文は GetUriWithQueryParameter("{NAME}", {VALUE}) で、{NAME} はクエリパラメーターの名前を表すプレースホルダー、{VALUE} はクエリパラメーターの値を表すプレースホルダーです。はクエリパラメーターの値のプレースホルダーです。このメソッドは文字列の値を返します。
クエリパラメーターが既に現在の URI に存在する場合、このメソッドはその値を更新します。クエリパラメーターが現在の URI に存在しない場合、このメソッドは指定された値で新しいパラメーターを追加します。
例:
@page "/movie"
@inject NavigationManager Navigation
<p>@Name</p>
<p>@Genre</p>
<button @onclick="UpdateCurrentURI">Update URI</button>
<p>NewURI: @NewURI</p>
<button @onclick="NavigateToNewURI">Go To New URI</button>
@code {
string NewURI { get; set; }
[Parameter]
[SupplyParameterFromQuery]
public string? Name { get; set; }
[Parameter]
[SupplyParameterFromQuery(Name = "Category")]
public string? Genre { get; set; }
void UpdateCurrentURI() {
NewURI = Navigation.GetUriWithQueryParameter("name", "Top Gun");
}
void NavigateToNewURI() {
Navigation.NavigateTo(NewURI);
}
}
ボタンをクリックすると、クエリパラメーター「name」の値が更新されます。
もし、URIに新しいクエリーパラメーターを追加したい場合は、以下のようにコードを更新すればよいです。
NewURI = Navigation.GetUriWithQueryParameter("language", "English");
URIから既存のクエリパラメーターを削除したい場合は、以下のように値をnullに設定します。
NewURI = Navigation.GetUriWithQueryParameter("name", (string)null);
GetUriWithQueryParametersメソッドを使用すると、URIから複数のパラメータを同時に追加、更新、削除することができます。
以下に示す例をご覧ください。
NewURI = Navigation.GetUriWithQueryParameters(new Dictionary<string, object?>
{
["name"] = "Top Gun",
["category"] = "Action",
["language"] = "English",
});
これにより、既存のクエリーパラメーターである名前とカテゴリーが更新され、新しいクエリーパラメーターである言語が追加され、値が英語に設定されます。
Blazorレイアウト
レイアウトとは、ナビゲーションメニュー、ヘッダー、フッターなど、複数のコンポーネントに共通する UI 機能を含む Blazor コンポーネントのことです。
アプリケーションの既定のレイアウトは、App.razor ファイル内の Router コンポーネントで定義されています。
@pageディレクティブを持つルーディング可能なコンポーネントのレイアウトを指定するには、@layoutディレクティブを使用します。コンポーネントで直接レイアウトを指定すると、Routerコンポーネントで設定されたアプリケーションの既定のレイアウトは上書きされます。
以下に示す例をご覧ください。
@layout CustomLayout
@page "/layout-demo"
<h1>Custom layout demo.</h1>
Blazor はレイアウトの入れ子をサポートしています。コンポーネントはレイアウトを参照することができ、そのレイアウトは別のレイアウトを参照します。これは、多階層のメニュー構造を作るのに役立ちます。
カスタムレイアウトコンポーネントの作成
コンポーネントがレイアウトコンポーネントとして機能するためには、次の2つの条件を満たす必要があります。
- LayoutComponentBase クラスから継承する必要があります。このクラスは、レイアウト内のコンテンツをレンダリングするために使用されるBodyプロパティを定義しています。
- ボディコンテンツがレンダリングされる場所を指定する必要があります。これは、Razor の構文 @Body を使って行います。
以下に示す例をご覧ください。
@inherits LayoutComponentBase
<header>
<h1>Welcome to Blazor tutorial</h1>
</header>
@Body
<footer>
<h1>All rights reserved</h1>
</footer>
依存性注入
依存性注入(DI)は、クラスとその依存関係の間で制御の逆転(IoC)を実現するためのソフトウェア設計パターンです。
Blazorアプリに登録されたサービスを Blazor コンポーネントに直接注入することができます。カスタムサービスは、DI経由で利用できるようにBlazorアプリに登録する必要があります。
サービス寿命
Blazorのサービスは、その登録時に、以下の3種類のライフタイム(サービス寿命)のうちいずれかを指定します。
- Singleton:すべてのコンポーネントで共有されるサービスのインスタンスを1つ作成します
- Transient:コンポーネントがサービスを要求するたびに、サービスの新しいインスタンスを作成します。
- Scoped:ウェブリクエストごとにサービスのインスタンスを一つ作成します。
- Blazor WebAssembly アプリは、Scoped ライフタイムをサポートしていません。Scoped ライフタイムで登録されたサービスは、Blazor WebAssembly アプリ上は Singleton ライフタイムサービスのように動作します。
- Blazor Server アプリは、HTTP リクエスト間の Scoped ライフタイムをサポートしますが、クライアント側に読み込まれたコンポーネントとサーバー間の SignalR 接続におけるメッセージごとのライフタイムには対応しません。
既定のBlazorサービス
Blazorアプリでよく利用されるサービスは、以下の表の通りです。
サービス | Blazor Wasm Lifetime | Blazor Server Lifetime | 説明 |
---|---|---|---|
HttpClient | Scoped | Scoped | リソースURLからのHTTP要求と応答を処理するためのメソッドを提供します。 |
IJSRuntime | Singleton | Scoped | JavaScriptの呼び出しがディスパッチされるJavaScriptランタイムのインスタンスを表します。 |
NavigationManager | Singleton | Scoped | URIナビゲーションのクエリーと管理のためのヘルパークラスを提供します。 |
Blazor WebAssemblyアプリにサービスを追加する
Program.cs ファイルを使用して、Blazor WebAssembly アプリと Blazor Server アプリの両方に対してカスタムサービスを登録することができます。
Blazor WebAssemblyアプリのProgram.csファイルのコードサンプルを見てください。
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
await builder.Build().RunAsync();
Blazor Server アプリの Program.cs ファイルのコードサンプルを見てください。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
var app = builder.Build();
app.UseRouting();
app.Run();
Blazor コンポーネントにサービスを注入する
コンポーネントへのサービスのインジェクションは,以下の2つの方法で行うことができます.
- ベースクラスで[Inject]属性を使用する。
- コンポーネントで @inject 命令を使用する。
1つのコンポーネントに複数のサービスを注入することができます。
以下に示すRazorコンポーネントの例を見てください。
@page "/movie"
@inject NavigationManager Navigation
<button @onclick="NavigateToPage">Go To Page</button>
@code {
void NavigateToPage() {
Navigation.NavigateTo("/home");
}
}
ベースクラスについては、以下に示す例を見てください。
using Microsoft.AspNetCore.Components;
namespace BlazorWasmDemo.Client.Pages;
public class RoutingBase : ComponentBase
{ [Inject] public NavigationManager NavigationManager { get; set; } = default!;
[Inject] HttpClient Http { get; set; } = default!;}
注入されたサービスは、コンポーネントが初期化されるときに利用可能になることが期待されます。したがって、null免除演算子 (default!) を使用して規定の即値を割り当てています。nullではない値で初期化しないと、コンパイラは「Non-nullable property must contain a non-null value when exiting constructor」という警告メッセージを表示します。あるいは .NET SDKバージョン7以降であれば、C#11から追加されたrequired修飾子を用いることで、null免除演算子に頼ることなく、以下のように記述できます。
Blazor サービスでの依存性注入の使用
コンストラクタのインジェクションを利用して、サービスを別のサービスに注入することができます。[Inject]属性や@injectディレクティブはサービスクラスで使用することはできません。
以下に示す例をご覧ください。
public class MyCustomService
{ private readonly HttpClient _httpClient;
private readonly NavigationManager _navigationManager ;
public MyCustomService(HttpClient httpClient, NavigationManager navigationManager) { _httpClient = httpClient; _navigationManager = navigationManager; } }
JavaScript 相互運用
JavaScriptの相互運用性、別名JavaScript interopは、.NETのメソッドからJavaScriptの関数を呼び出したり、JavaScriptの関数から.NETのメソッドを呼び出すことができる機能です。
.NETからJavaScriptの関数を呼び出す
IJSRuntimeインターフェースを使って、.NETからJavaScriptの関数を呼び出すことができます。
IJSRuntime インターフェースは、以下の2つのメソッドを提供します。
- InvokeAsync: Promiseを含む何らかの値やオブジェクトを返すJavaScript関数を呼び出すために使用します。
- InvokeVoidAsync:voidまたはundefinedを返すJavaScriptの関数を呼び出すために使用します。
カスタムJavaScriptコードは、Blazor WebAssembly アプリであれば wwwroot/index.html に、Blazor ServerアプリであればPages/_Host.cshtmlに、追加することができます。
例を挙げます。
<script>
window.getArraySum = (numberArray) => {
return numberArray.reduce((a, b) => a + b, 0);
}
</script>
getArraySum関数は、パラメータとして配列を受け取り、その配列の全要素の合計を返します。
コンポーネント JSDemoBase.razor を作成し、ベースクラス JSDemoBase.razor.cs に次のコードを追加します。
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorWasmDemo.Client.Pages;
public class JSDemoBase : ComponentBase { [Inject] protected IJSRuntime JSRuntime { get; set; } = default!;
protected int sumOfArray = 0;
private int[] arrayItems = new int[] { 2, 4, 6, 8, 10 };
protected async Task GetSumOfArray() { sumOfArray = await JSRuntime.InvokeAsync<int>("getArraySum", arrayItems); } }
InvokeAsyncメソッドを使って、JavaScript関数 "getArraySum" を呼び出します。JavaScript関数"getArraySum"は合計を返し、それがint型のフィールド変数sumOfArrayに代入されます。
このGetSumOfArrayメソッドは、以下のようにボタンクリックで呼び出すことができます。
@page "/calljsmethod"
@inherits JSDemoBase
<button @onclick="GetSumOfArray">Get Array Sum</button>
<p>The sum of array elements is : @sumOfArray</p>
JavaScriptの内蔵メソッドを呼び出すには、次のようなコードを使用します。
protected async Task ShowAlert()
{
await JSRuntime.InvokeVoidAsync("alert", "I am invoked from .NET code");
}
protected async Task ShowArrayItems()
{
await JSRuntime.InvokeVoidAsync("console.table", arrayItems);
}
上記のShowAlertメソッドでは、文字列のパラメーターを渡してalert関数を呼び出しています。ShowArrayItemsメソッドは、JavaScriptのconsole.table関数を呼び出して、表示するarrayItemsを渡しています。
HTML要素への参照をキャプチャする
ElementReference構造体を使用すると、コンポーネント内のHTML要素への参照を取得することができます。これは、レンダリングされた要素への参照を表すために使用されます。
HTML要素への参照をコンポーネントに取り込むには、HTML要素に@ref属性を追加する必要があります。また、@ref属性の値と一致する名前のElementReference型のフィールドを定義します。
このElementReferenceをJavaScriptコードに渡すには、JavaScript 相互運用機能を使用します。JavaScriptコードはDOM操作に使用できるHTMLElementのインスタンスを受け取ることになります。
例を挙げます。
以下のJavaScript コードを追加します。
<script>
window.showValue = (element) => {
alert('Your name is :' + element.value);
}
</script>
この関数は、HTML 要素の参照を受け取り、その値をアラートボックスに表示します。
この関数を呼び出すには、ベースクラス内に次のコードを追加します。
protected ElementReference name;
protected async Task ShowName()
{ await JSRuntime.InvokeVoidAsync("showValue", name); }
および以下のコードのように、HTML要素の@ref属性を使って、ElementReferenceのインスタンスを取得します。
<button class="btn btn-primary" @onclick="ShowName">Show Name</button>
<input type="text" @ref="name" class="form-control" placeholder="Enter your name" />
JavaScriptの関数から.NETの静的メソッドを呼び出す
DotNet.invokeMethodやDotNet.invokeMethodAsync関数を使って、JavaScriptから静的な.NETメソッドを呼び出すことができます。
呼び出す.NETメソッドは、以下の条件を満たす必要があります。
- publicかつstaticに宣言する必要があります。
- [JSInvokable]属性で装飾されている必要があります。
この条件を満たした.NETメソッドを JavaScriptから呼び出すには、その.NET静的メソッドの識別子、C#メソッドを含むアセンブリの名前、および必要な引数を渡します。
関数の構文は次のとおりです - DotNet.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD Name}', {ARGUMENTS});
各部分の説明を以下に記します。
- {ASSEMBLY NAME}は、アプリケーション・アセンブリ名のプレースホルダーです。
- {.NET METHOD Name}は呼び出す.NETメソッドのプレースホルダーです。
- {ARGUMENTS}は、呼び出される.Netメソッドが必要とする引数のプレースホルダーです。これはオプションのパラメータです。
例を挙げます。
JSDemoBase.razor.cs ファイルに次のコードを追加します。
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorTutorial.Client.Pages;
public class JSDemoBase: ComponentBase
{ [Inject] protected IJSRuntime JSRuntime { get; set; } = default!;
protected void InvokeJSMethod(){ JSRuntime.InvokeVoidAsync("printMessageToConsole"); }
[JSInvokable]public static Task<string> PrintMessage(){ return Task.FromResult("I am invoked from JavaScript."); } }
上記のInvokeJSMethodメソッドは、JavaScript関数であるprintMessageToConsoleを呼び出します。PrintMessage メソッドは、[JSInvokable] 属性を持っており、且つ public および static として宣言されています。これによって、JavaScript 関数printMessageToConsoleは、.NET静的メソッドPrintMessageを呼び出します。
JSDemoBase.razor ファイルに、以下のようにボタンを追加します。
<button @onclick="InvokeJSMethod">Invoke static .NET method</button>
以下のJavaScript コードを追加します。
<script>
window.printMessageToConsole = () => {
DotNet.invokeMethodAsync(BlazorTutorial.Client', 'PrintMessage')
.then(data => {
console.log(data);
});
}
</script>
JavaScript 関数、printMessageToConsoleを上記のとおり定義し、DotNet.invokeMethodAsync 関数を使って C# メソッド PrintMessage を呼び出しています。アセンブリの名前は "BlazorTutorial.Client"として渡しています。
コンポーネントのインスタンスメソッドの呼び出し
以下の手順で、Blazorコンポーネントの.NETインスタンスメソッドを呼び出すことができます。
- DotNetObjectReference.Create を使って、JavaScript関数に渡すことのできる.NETオブジェクト参照を作成します。
- .NETオブジェクト参照のinvokeMethodまたはinvokeMethodAsyncメソッドを使って、コンポーネントのインスタンスメソッドの呼び出しを行います。
- コンポーネントが破棄されるタイミングで、.NETオブジェクトへの参照も破棄します。
例を挙げます。
以下のJavaScriptコードを追加します。
function displayMessageCallerJS(objectRef, value) {
objectRef.invokeMethodAsync('DisplayMessage', value);
}
関数 displayMessageCallerJS を追加し、objecRef およびvalue という名前の2つのパラメーターを受け取るようにしました。この関数は、引数に渡されたobjectRefが参照している.NETオブジェクト (コンポーネント) のDisplayMessageインスタンスメソッドを呼び出し、引数としてvalueを渡します。
JSDemoBase.razor.cs ファイルに次のコードを追加します。
public class JSDemoBase : ComponentBase, Idisposable
{
private DotNetObjectReference<JSDemoBase>? objectRef;
protected string message = "Click the button.";
protected override void OnInitialized()
{
objectRef = DotNetObjectReference.Create(this);;
}
protected void InvokeDisplayMessageCallerJS(string value)
{
JSRuntime.InvokeVoidAsync("displayMessageCallerJS", objectRef, value);
}
private void DisplayMessage(string value)
{
message = value;
StateHasChanged();
}
public void Dispose()
{
objectRef?.Dispose();
}
.NETオブジェクトへの参照であるDotNetObjectReference<JSDemoBase>型のフィールド変数objectRefを宣言しています。OnInitializedライフサイクルメソッドの中で、DotNetObjectReference.Create 静的メソッドを呼び出し、このコンポーネント自身を参照する.NETオブジェクト参照を生成してobjectRefに代入しています。InvokeDisplayMessageCallerJS メソッドは、文字列パラメータを受け取ります。そして、このメソッドは、JavaScript関数であるdisplayMessageCallerJSを呼び出し、このコンポーネント自身を参照する.NETオブジェクト参照objectRefと、文字列パラメータを渡します。
[JSInvokable] 属性を持つ DisplayMessageというインスタンスメソッドを作成し、文字列のパラメータを受け取るようにします。このインスタンスメソッドは、JS関数displayMessageCallerJSから呼び出されることになります。DisplayMessageは、パラメータの値を変数 message に代入し、Statehaschanged() を呼び出して、コンポーネントに状態の変化を通知します。
最後に、このコンポーネントが破棄されるタイミングで、このコンポーネント自身を参照する.NETオブジェクト参照も破棄するようIDisposableインターフェースを実装し、このコンポーネントのDispose メソッドの中で、.NETオブジェクト参照のDisposeメソッドを呼び出します。
JSDemoBase.razor ファイルに、以下のようにボタンを追加します。
<button @onclick="@(()=>InvokeDisplayMessageCallerJS("The Button is clicked"))">Call JS Method</button>
<br />
<p >@message</p>
ボタンクリック時に、InvokeDisplayMessageCallerJSメソッドを呼び出し、パラメーターとして文字列の値を渡します。更新されたメッセージの値が画面に表示されます。
続けて読む
下記フォームをご入力の上、続きをお読みください。