これまでのレクチャーでは、Home.razorのコード内で「MessageBoxAlert」「MessageBoxConfirm」「MessageBoxPrompt」といった文字列を直接指定してJavaScriptの関数を呼び出す形でメッセージを表示していました。しかし、この方法には次のような問題があります。
・文字列でメソッド(JavaScript関数)名を指定するため、タイプミスに気づきにくくIntelliSenseの補完も効かない ・テストコードの観点から見て、実際にブラウザのメッセージボックスがポップアップしてしまう実装をそのままテストで実行するのは現実的ではない
そこで今回は、これらのメッセージ表示処理を「MessageService」というクラスにまとめ、インターフェース(IMessageService)を介して呼び出せるようにします。インターフェースを挟むことで、本番実行時は実際にブラウザのメッセージボックスを表示する実装を使い、テスト実行時は文字列を受け取って記録するだけの差し替え可能な実装を使う、といった切り替えができるようになります(テストコードやDIの詳細については、ドメイン駆動設計のコースやテストコードのコースを参照してください)。
図1 Login()メソッド内で文字列を直接指定してMessageBoxPromptやMessageBoxAlertを呼び出している既存のコード
Servicesフォルダーとクラスの作成
まず、Componentsフォルダーの下に「Services」という名前のフォルダーを新規作成します。サービスを置く場所に厳密な決まりはありませんが、今回はこのフォルダーにまとめることにします。
図2 ソリューションエクスプローラーでComponentsフォルダー配下にServicesフォルダーを作成する
作成したServicesフォルダーを右クリックして新しい項目を追加し、クラス名を「MessageService」としてファイルを作成します。
図3 「新しい項目の追加」ダイアログでMessageServiceクラスファイルを作成する
図4 作成直後のIMessageServiceインターフェースとMessageServiceクラスの雛形
インターフェースにメソッドを定義する
テストの観点からは、インターフェース越しに呼び出せるようにしておいた方が都合が良いため、まず public interface IMessageService を用意します。この中に、外部から呼び出したいメソッドを3つ定義します。
1つ目は、Home.razorで使っていたMessageBoxAlertに対応するメソッドです。voidなので戻り値はなく、非同期メソッドとして定義するためTask型、引数はstring型のmessageを受け取ります。
図5 IMessageServiceインターフェースにTask MessageBoxAlert(string message)を定義した状態
ただし、クラス名がすでに「MessageService」なので、メソッド名に重ねて「MessageBox」と付けるのは冗長になります。そこでメソッド名は「Alert」に短縮しました。続けて、Confirm(戻り値bool、非同期なのでTask<bool>)とPrompt(戻り値string、非同期なのでTask<string>)も同様の考え方で定義します。
public interface IMessageService
{
Task Alert(string message);
Task<bool> Confirm(string message);
Task<string> Prompt(string message);
}
図6 AlertとConfirmのメソッド定義。MessageBoxAlertからAlertへ名称を短縮している
図7 Alert・Confirm・Promptの3メソッドを定義し終えた状態(IntelliSenseでTask<TResult>の説明が表示されている)
MessageServiceクラスへの実装
インターフェースに定義を書いたら、実際の処理はMessageServiceクラス側に実装します。MessageServiceクラス宣言に「: IMessageService」を付けてインターフェースを実装する形にし、Visual Studioの「Ctrl + .」(クイックアクション)を使うと、未実装のメンバーを自動的にスタブとして生成してくれます。
図8 MessageServiceクラスにIMessageServiceを実装する宣言を追加した状態
図9 Ctrl+.(クイックアクション)でAlert・Confirm・Promptの3メソッドが自動生成された状態(中身はNotImplementedException)
なお、今回はインターフェースと実装クラスを同じファイルに記述していますが、これは説明の都合上まとめているだけで、後で別々のファイルに分割する想定です。
Alertメソッドを実装する
実際の処理内容は、これまでHome.razorに書いていたコードをそのまま持ってくれば作れます。Home.razorの警告メッセージ(例えば「認証失敗!!」を表示している箇所)を確認します。
図10 Home.razorでMessageBoxAlertを使って「認証失敗!!」を表示している箇所
この呼び出し部分をAlertメソッドの中にそのまま貼り付け、固定文字列だった「認証失敗!!」の部分を引数のmessageに置き換えます。ただし、Home.razorでは@inject IJSRuntime JSのようにJSがすでに注入されていましたが、MessageServiceクラスにはその仕組みがありません。そこで、コンストラクターでIJSRuntimeを受け取るようにします。
図11 Alertメソッドの中にJS.InvokeVoidAsyncの呼び出しを貼り付けた直後(JSが未定義のためエラーになっている状態)
IJSRuntime型のフィールドを小文字始まりの _js としてprivateで保持するように宣言します。
図12 private IJSRuntime _js; フィールドを追加する
このフィールドをコンストラクターの引数として受け取るようにしておくと、Razorコンポーネントで@injectを使ったときと同じように、DI(依存性注入)の仕組みによって自動的にインスタンスが渡されるようになります。
図13 コンストラクターMessageService(IJSRuntime js)でIJSRuntimeを受け取り、_jsフィールドに代入する実装
あとは、JS.InvokeVoidAsyncとしていた箇所を _js.InvokeVoidAsync に書き換え、Alertメソッドをasyncにしてawaitできるようにすれば完成です。
図14 JSを_jsに置き換え、_js.InvokeVoidAsyncでメッセージを表示するAlertメソッドの完成形
public async Task Alert(string message)
{
await _js.InvokeVoidAsync("MessageBoxAlert", message);
}
Confirmメソッドを実装する
同じ要領で、Confirmも実装します。Home.razorのSave()メソッド内で「保存しますか?」という確認メッセージを表示している箇所を確認します。
図15 Home.razorのSave()メソッド内でMessageBoxConfirmを使って「保存しますか?」を表示している箇所
この呼び出し部分をConfirmメソッドにコピーし、固定文字列だった「保存しますか?」を引数のmessageに置き換えます。戻り値があるため、JS.InvokeVoidAsyncではなくJS.InvokeAsync<bool>を使い、その戻り値をそのままreturnします。JSは同様に_jsに置き換え、非同期メソッドとしてasyncを付けます。
図16 Confirmメソッドの実装。return await _js.InvokeAsync<bool>(“MessageBoxConfirm”, message)で結果をそのまま返す
public async Task<bool> Confirm(string message)
{
return await _js.InvokeAsync<bool>("MessageBoxConfirm", message);
}
Promptメソッドを実装する
最後にPromptです。考え方はConfirmと同じで、Home.razorでMessageBoxPromptを呼び出している箇所をコピーしてきます。戻り値がstring型になるため、JS.InvokeAsync<string>を使い、その結果をそのままreturnします。
図17 Home.razorのPrompt呼び出し箇所を確認し、Promptメソッドの実装に取り掛かる
public async Task<string> Prompt(string message)
{
return await _js.InvokeAsync<string>("MessageBoxPrompt", message);
}
完成したIMessageService / MessageService
以上で、IMessageServiceインターフェースと、それを実装するMessageServiceクラスが完成しました。
図18 完成したIMessageServiceインターフェースとMessageServiceクラス(コンストラクターでIJSRuntimeを受け取る)
using Microsoft.JSInterop;
namespace BlazorApp1.Components.Services
{
public interface IMessageService
{
Task Alert(string message);
Task<bool> Confirm(string message);
Task<string> Prompt(string message);
}
public class MessageService : IMessageService
{
private IJSRuntime _js;
public MessageService(IJSRuntime js)
{
_js = js;
}
public async Task Alert(string message)
{
await _js.InvokeVoidAsync("MessageBoxAlert", message);
}
public async Task<bool> Confirm(string message)
{
return await _js.InvokeAsync<bool>("MessageBoxConfirm", message);
}
public async Task<string> Prompt(string message)
{
return await _js.InvokeAsync<string>("MessageBoxPrompt", message);
}
}
}
なお、インターフェースと実装クラスを同じファイルにまとめて記述しているのは説明の都合上の暫定措置です。どのように画面(Home.razor)へ適用し、DIコンテナへ登録するかについては、次回のレクチャーで扱います。
■非公開コース「C#14新機能」プレゼント:
非公開コース「C#14新機能」(80分)をご覧になりたい方は
こちらからURLとパスワードを発行していますので、ご覧になってみてください。
非公開コース「C#14新機能」を観る
A01_はじめに
B01_メッセージを出すレイアウトの作成
B02_pタグを使ったメッセージ表示
B03_ポップアップメッセージを表示する方法
B04_問い合わせメッセージを表示する方法
B05_ユーザーの入力を受けるメッセージ
B06_インラインJavaScript非推奨
B07_Javascriptを分離してアクセスする方法
B08_MessageServiceを作成する
B09_自作Serviceの依存性注入をする方法
C01_EditFormを使った入力検証
C02_ValidationSummary
C03_ValidationMessage
C04_入力コンポーネント_InputText
C05_InputNumber
C06_未入力を許容する方法
C07_InputNumberで対応している型
C08_InputDate
C09_InputSelect
C10_入力コンポーネント_InputCheckbox
C11_InputRadioとInputRadioGroup
C12_InputTextArea
C13_InputFile
C14_ファイルの上限を設定する
C15_フォーカスをあてる方法_標準編_ElementReference
C16_フォーカスをあてる方法_Razorコンポーネント編
D01_StringLength
D02_RegularExpression_正規表現
D03_EmailAddress
D04_Compare
D05_ViewModelに分ける方法
D06_依存性注入を利用したViewModelの生成
D07_CustomValidation1
D08_CustomValidation2
D09_プロパティレベルのCustomValidation
D10_ValidationContext
D11_処理をViewModelに移す書き方
D12_ViewModelに移せないロジック
D14_ShouldRender
D15_ViewModelに書かないほうがいいもの
D16_エラー箇所にフォーカスを当てる方法
E01_Azureにデプロイするための事前作業
E02_Azureにアップするアプリの作成
E03_Azureへの公開準備
E04_Azureへの公開
E05_データベースの作成
E06_データベースのデータを取得する
E07_データベースの値を画面に表示する
E08_Azureのデータベースを作成する
E09_Azureのデータベースにポータルサイトからログインする
E10_AzureのDBにテーブルを作成する
E11_BlazorアプリからAzureDBに接続する
E12_SQLデータベース料金の注意
E13_AzureSQLデータベースの無料オファーを受ける
F01_依存性注入のライフサイクル
F02_クラウドでのAddSingletonの動き
■非公開コース「C#14新機能」プレゼント:
非公開コース「C#14新機能」(80分)をご覧になりたい方は
こちらからURLとパスワードを発行していますので、ご覧になってみてください。
非公開コース「C#14新機能」を観る