本家動画はこちら
EP01~05はこちら
番外×2 + EP10~はこちら
#EP06:JavaScriptとの相互運用
- razorページでJavaScriptを使うには、まずは
@inject IJSRuntime JSRuntime;
を追加する必要があるよ。 - jsフォルダ内の「site.js」を追加して、同ファイルに以下の関数を追加したのでそれを呼んでみよう。
function saveMessage(){
alert('レコードの保存が完了しました。');
}
- 「_Host.cshtml」の
blazor.server.js
の下に<script src="~/js/site.js"></script>
を追加しよう。 - Saveボタン押下時の処理(SaveAuthorメソッド)に追加したいので、同メソッドに
await JSRuntime.InvokeVoidAsync("saveMessage");
を追加しよう。 - あとこの関数は非同期なので予備元のSaveAuthorメソッドも
private async Task SaveAuthor()
としておこう。 - この手のテストは検証があると面倒なので
@*<DataAnnotationsValidator />*@
と@* *@
で括って検証を無効にしておこう。 - 動かしてみるとちゃんとダイアログが表示されたね。
- それじゃ次にJavaScriptに引数を渡してみよう。まずはJavaScriptに引数を足すよ。
function saveMessage(firstName, lastName) {
alert(firstName + " " + lastName + 'のレコードの保存が完了しました。');
}
- 次に
InvokeVoidAsync
にも引数を足すよ。InvokeVoidAsync
は可変個数の引数を指定できるよ。
await JSRuntime.InvokeVoidAsync("saveMessage", author.FirstName, author.LastName);
(動画じゃauthor.FirstName, author.LastNameを渡さずに、直接一回変数に入れてるけどなんか意味あるんかな?)
- 続いてJavaScriptでDOMを操作してみよう。まずはJavaScriptを修正するよ。
function saveMessage(firstName, lastName) {
document.getElementById('divServiceValidations').innerText = firstName + " " + lastName + 'のレコードの保存が完了しました。';
}
(ローカルで動かしてたら「innerTest」ってタイポしてて動かんかった・・・VisualStudioならTypeScript簡単に使えるし(必要なものはNugetで勝手にとってきてくれる)トランスパイルの時にエラー出してくれるTypeScript使おう。型もあるしほぼ書き方変わらんし)
function saveMessage(firstName: string, lastName: string) {
document.getElementById('divServiceValidations').innerText = firstName + " " + lastName + 'のレコードの保存が完了しました。';
}
- 操作対象の
<div id="divServiceValidations" class="col-10 alert alert-info"></div>
を「Authors.razor」の<EditForm>
の前に追加するよ。 - 動かしてみるとメッセージが追加した
<div id="divServiceValidations">
に表示されるね。 - 次は「Save」を押すと「first name」にフォーカスが映るようにしてみよう。
- まずは
@ref
属性を「first name」の<InputText>
に追加するよ。
<InputText @ref="firstnameText" class="form-control col-3" @bind-Value="author.FirstName" placeholder="first name" />
- 続いてコード部に
ElementReference firstnameText;
を追加するよ。 - ここで一旦コンパイルしてみよう。コンポーネントを変換できない、みたいなエラーが出るね。
-
@ref
はRazorコンポーネントには指定できないので、<InputText>
を<input>
に変えてやる必要があるよ。 -
@bind-Value
も@bind
に変える必要があるので以下のようになるよ。
<input @ref="firstnameText" class="form-control col-3" @bind="author.FirstName" placeholder="first name" />
(ElementReference firstnameText;
だとエラーになるけどInputText firstnameText;
だとエラーにならんな。この辺は型チェックしてるってことか)
- それじゃフォーカスをセットするスクリプトを書いて、
function setFocusOnElement(element: HTMLElement) {
element.focus();
}
(動画ではJavaScriptだからもちろん「: HTMLElement」は無いよ)
- あとはメッセージ表示の時と同じように関数を呼ぶだけだよ。動かしてみるとちゃんとフォーカスが移動してるね。
await JSRuntime.InvokeVoidAsync("setFocusOnElement", firstnameText);
(一々JS使わんといかんの面倒だな。WebForm時代みたいにSetFocus関数呼ぶだけ、とかにならんかなぁ・・・。以下みたいな感じ)
public InputText lastnameText;
private async Task SaveAuthor() {
lastnameText.SetFocus();//今のところそんなメソッドはない。
}
(ID指定すればInputTextのままでもいけるな)
<InputText id="firstnameText" @ref="firstnameText" class="form-control col-3" @bind-Value="author.FirstName" placeholder="first name" />
@code {
public InputText lastnameText;
private async Task SaveAuthor()
{
await JSRuntime.InvokeVoidAsync("setFocusOnID", lastnameText.AdditionalAttributes["id"]);
}
}
function setFocusOnID(element: string) {
document.getElementById(element).focus();
}
- 次はJavaScriptからとった配列でリストボックスの一覧を初期化してみよう。JavaScriptは以下のようなのだよ。
function getCities(): string[] {
var cities: string[] = ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix', 'Philadelphia', 'San Antonio',
'San Diego', 'Dallas', 'San Jose', 'Austin', 'Jacksonville', 'Fort Worth', 'Columbus', 'San Francisco',
'Charlotte', 'Indianapolis', 'Seattle', 'Denver', 'Washington'];
return cities;
}
(動画では(ry)
- あとは
string[] Cities
プロパティを追加して、描画後に実行される「OnAfterRenderAsync」に初回かつCities
がnullの時にJavaScriptから値を取ってきてStateHasChanged();
で反映させるよ。 - コード見ればわかるけど、戻り値なしのときは
InvokeVoidAsync
、戻り値ありの時はInvokeAsync<TValue>
でTValueに型を指定するんだね。
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && Cities == null)
{
Cities = await JSRuntime.InvokeAsync<string[]>("getCities");
StateHasChanged();
}
}
- あとはforeachでoption追加を回せば追加されるね。
<select class="form-control col-3">
@if (@Cities != null){
<option value="">select city..</option>
@foreach (var city in Cities){
<option value="@city">@city</option>
}
}
</select>
(実アプリじゃあんまり使わないパターンかなぁ。選択とかEnumとか固定配列とかDBからとるパターンしか使わなそう)
#EP07:Razorコンポーネントの再利用
- 今回はRazorコンポーネントの再利用について話すよ。
- Razorコンポーネントは2種類あって、一つ目は「ページ」。
- 今までにも出てきた頭に
@page
って書いてあるやつだね。ブラウザからこれらのコンポーネントに移動してWebページとして利用できるよ。 - もう一つは共有可能なコンポーネントでページに組み込んで利用できるよ。今回はこの共有可能なコンポーネントの話だよ。
- さて、今まで「著者(Author)の名前とかの情報を入力して保存する」っていうページを作ってきたけど、これと似たようなので「出版社(Publisher)の名前とかの情報を入力して保存する」というのを作ったよ。
- ここでコードを見てみるとどっちのページにも
<div id="divServerValidations">
っていうタグがあるね。 - いわゆる重複コードだけど、Razorコンポーネントの再利用をすると、こういうのことが起きないようにできるよ。
- では早速ソリューションエクスプローラ右クリックからRazorコンポーネントを追加してみよう。名前は「ServerValidations.razor」にしよう。
- とりあえずここには
<div id="divServerValidations">
とメッセージだけを追加してみよう。
<div id="divServiceValidations" class="col-10 alert alert-info">
保存完了
</div>
@code {
}
- 呼び出すのは簡単で
<ServerValidations></ServerValidations>
って書くだけだよ。実行してみるとちゃんとページに組み込まれてるのがわかるね。 - それじゃ次はパラメーターでメッセージの表示/非表示を切り変えてみよう。コンポーネント側(子)はこんな感じでパラメーターを追加できるよ。
@if (IsVisible) {
<div id="divServiceValidations" class="col-10 alert alert-info">
保存完了
</div>
}
@code {
[Parameter]
public bool IsVisible { get; set; }
}
- 呼び出し側(親)でも変数を宣言して、コンポーネントのタグにプロパティとの対応を書いてあげるよ。
@page "/authors"
<ServerValidations IsVisible="IsVisible"></ServerValidations>
@code {
public bool IsVisible { get; set; }
private async Task SaveAuthor() {
IsVisible = true;
(以下略)
- 動かしてみると、ちゃんと「Save」ボタンを押したら
<div id="divServiceValidations">
の内容が表示されるね。 - 今度は「親の方で書いたHTMLを子の方に表示させる」というのをやってみよう。
- まず「ServerValidations.razor」の方は
RenderFragment
型のプロパティを追加してHTML部に表示させるよ。
@if (IsVisible)
{
<div id="divServiceValidations" class="col-10 alert alert-info">
@ChildContent : 保存完了
</div>
}
@code {
[Parameter]
public bool IsVisible { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
}
- 呼び出し側(親)の方は
<ServerValidations>
のタグの中に「ServerValidations.razor」の@ClildContent
で表示したいものを書くよ。HTMLとかも書けるよ。
(なんかわからんけど↓のコードだと実行エラーになる・・・)
→「ServerValidations.razor」のChildContent
をClildContent
って打ってたのが原因だった。このプロパティ名って固定なのな。
@page "/authors"
<ServerValidations IsVisible="IsVisible">
<span>著者:</span>@RecordName
</ServerValidations>
@code {
public bool IsVisible { get; set; }
public string RecordName { get; set; }
private async Task SaveAuthor() {
IsVisible = true;
RecordName = author.FirstName + " " + author.LastName;
(以下略)
- 動かしてみると呼び出し側(親)の方で
<ServerValidations>
のタグの中に書いた内容がコンポーネント側(子)の方に表示されテルルがわかるね。 - パラメータに
bool Result
とかを足してやればエラー表示もできるね。
(動画では styleとか指定してるけど省略)
@if (IsVisible)
{
@if (Result){
<div id="divServiceValidations" class="col-10 alert alert-info">
@ChildContent 保存完了
</div>
}else{
<div id="divServiceValidations" class="col-10 alert alert-danger">
サーバーエラーです。管理者に問い合わせてください。
</div>
}
}
@code {
[Parameter]
public bool IsVisible { get; set; }
[Parameter]
public bool Result { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
}
- あとこのメッセージを×で閉じれるようにしてみよう。ページと同じようにイベントを記述できるよ。
@if (IsVisible){
@if (Result){
<div id="divValidationMessage" class="col-10 alert alert-danger row">
<div class="col-11" style="text-align:left">
@ChildContent 保存完了
</div>
<div class="col-1" style="text-align:right">
<a style="text-underline-position:below; cursor:pointer" @onclick="CloseValidation">x</a>
</div>
</div>
}else{
<div id="divValidationMessage" class="col-10 alert alert-danger row">
<div class="col-11" style="text-align:left">
サーバーエラーです。管理者に問い合わせてください。
</div>
<div class="col-1" style="text-align:right">
<a style="text-underline-position:below; cursor:pointer" @onclick="CloseValidation">x</a>
</div>
</div>
}
}
@code {
[Parameter]
public bool IsVisible { get; set; }
[Parameter]
public bool Result { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
public void CloseValidation(){
IsVisible = false;
}
}
(そういやこれ、タグに全部プロパティを一個一個書くより@ref
使ったほうがいいな)
(IsVisible
とかの[Parameter]
は消さないと「パラメータ指定してねーぞ」って警告が出た)
(略)
<ServerValidations @ref="ServerValidationsCtrl">
<span>著者:@RecordName</span>
</ServerValidations>
(中略)
@code {
public ServerValidations ServerValidationsCtrl;
public void SaveAuthor(){
RecordName = author.FirstName + " " + author.LastName;
ServerValidationsCtrl.IsVisible = true;
ServerValidationsCtrl.Result = false;
}
(略)
- 実際に再利用してみよう。簡単だね。
#EP08:Razorコンポーネントのライフサイクル
-
今回はRazorコンポーネントのライフサイクルについて話すよ。
(この章、自動翻訳が微妙過ぎて何言ってんのかわからん・・・、他のWebサイトも含めてまとめていこ)
BlazorのLifecycleとShouldRenderの挙動について
AspNetCore.Docs.ja-jp - lifecycle.md/GitHub -
初期化イベントだけど次の順で発生するよ
-
OnInitialized
OnInitializedAsync
:アプリの設定とかタイトルとか設定するといいね。OnInitializedAsync
は非同期だからDB読んだりしてもいいね。 -
SetParametersAsync
(動画では言ってないけど↑にあったので追加)Razorコンポーネントにパラメーターがセットされる前に呼ばれるよ。渡されたパラメーターのチェックができるね。セットされたプロパティの値はparameters.GetValueOrDefault<プロパティの型>("プロパティ名")
で取れるよ。await base.SetParametersAsync(parameters);
を呼ばないようにすれば値は反映されないよ。他と違って非同期しかないっぽいよ。 -
OnParametersSet
OnParametersSetAsync
:Razorコンポーネントにパラメーターがセットされたあとに呼ばれるよ。渡されたパラメーターを元に何か処理するならここだね。 -
OnAfterRender
OnAfterRenderAsync
:画面がレンダリングされた後に呼ばれるよ。DOMも作られてるからJavaScript呼ぶならここだね。初期化の流れで何かするならここが最後のチャンスだね。例えばDBから値を取れなかった時のリトライ処理とか。 -
言わずもがなだけど、Asyncってついてる奴は非同期メソッドだね。
-
あと一回表示したあとは画面更新前に
ShouldRender
っていうイベントが起きて、ここでfalseを返すと画面更新をやめるよ。 -
デフォルトではないけど
IDisposable
を継承してDispose
を実装すればページ遷移時の後処理を実装できるよ。
(RazorコンポーネントにIDisposable
継承させるってどうやるんやろ?)
→Component lifecycles - Blazor Universityにあった。
@implements IDisposable
<h1>This is MyComponent</h1>
@code {
void IDisposable.Dispose(){
// Code here
}
}
- それじゃ実際に動かしてみよう。コードはこんな感じだよ。
@page "/authors"
@implements IDisposable
<h3>Authors</h3>
<ServerValidations @ref="ServerValidationsCtrl" IsVisible="IsVisible">
<span>著者:@RecordName</span>
</ServerValidations>
<EditForm Model="@author" OnValidSubmit="@SaveAuthor">
<div class="col-12 row">
<label class="col-2 font-weight-bold">first name :</label>
<input @ref="firstnameText" class="form-control col-3" @bind="author.FirstName" placeholder="first name" />
<ValidationMessage For="@(()=>author.FirstName)" />
</div>
<br />
<div class="col-12 row">
<label class="col-2 font-weight-bold">last name :</label>
<InputText id="lastnameText" @ref="lastnameText" class="form-control col-3" @bind-Value="author.LastName" placeholder="last name" />
<ValidationMessage For="@(()=>author.LastName)" />
</div>
<br />
<div class="col-12 row">
<label class="col-2 font-weight-bold">city :</label>
<InputSelect @ref="CitiesSelect" @bind-Value="author.City">
<option value="none">選んでね</option>
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</InputSelect>
<ValidationMessage For="@(()=>author.City)" />
</div>
<br />
<div class="col-12 row">
<label class="col-2 font-weight-bold">email address :</label>
<InputText class="form-control col-3" @bind-Value="author.EmailAddress" placeholder="email address" />
<ValidationMessage For="@(()=>author.EmailAddress)" />
</div>
<br />
<div class="col-12 row">
<label class="col-2 font-weight-bold">email address :</label>
<InputNumber class="form-control col-3" @bind-Value="author.Salary" placeholder="email address" />
<ValidationMessage For="@(()=>author.Salary)" />
</div>
<br />
<div class="col-12 row">
<span class="col-2"></span>
<input type="submit" name="btnSave" class="form-control col-1 btn btn-primary" value="Save" />
<span> </span>
<input type="reset" name="btnClear" class="form-control col-1 btn btn-primary" @onclick="Clear" value="Clear" />
</div>
</EditForm>
}
@implements IDisposable
@if (IsVisible){
@if (Result){
<div id="divValidationMessage" class="col-10 alert alert-danger row">
<div class="col-11" style="text-align:left">
@ChildContent 保存完了
</div>
<div class="col-1" style="text-align:right">
<a style="text-underline-position:below; cursor:pointer" @onclick="CloseValidation">x</a>
</div>
</div>
}else{
<div id="divValidationMessage" class="col-10 alert alert-danger row">
<div class="col-11" style="text-align:left">
サーバーエラーです。管理者に問い合わせてください。
</div>
<div class="col-1" style="text-align:right">
<a style="text-underline-position:below; cursor:pointer" @onclick="CloseValidation">x</a>
</div>
</div>
}
}
@code {
[Parameter]
public bool IsVisible { get; set; }
public bool Result { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
protected override void OnInitialized(){
Console.WriteLine("ServerValidations - OnInitialized");
base.OnInitialized();
}
protected override async Task OnInitializedAsync(){
Console.WriteLine("ServerValidations - OnInitializedAsync");
await base.OnInitializedAsync();
}
public override async Task SetParametersAsync(ParameterView parameters){
Console.WriteLine("ServerValidations - SetParametersAsync / IsVisible=" + IsVisible.ToString() + " / parameters.GetValueOrDefault<bool>(\"IsVisible\")=" + parameters.GetValueOrDefault<bool>("IsVisible").ToString());
await base.SetParametersAsync(parameters);
}
protected override void OnParametersSet(){
Console.WriteLine("ServerValidations - OnParametersSet / IsVisible=" + IsVisible.ToString());
base.OnParametersSet();
}
protected override async Task OnParametersSetAsync(){
Console.WriteLine("ServerValidations - OnParametersSetAsync / IsVisible=" + IsVisible.ToString());
await base.OnParametersSetAsync();
}
protected override bool ShouldRender(){
base.ShouldRender();
Console.WriteLine("ServerValidations - ShouldRender");
return true;
}
protected override void OnAfterRender(bool firstRender){
Console.WriteLine("ServerValidations - OnAfterRender - firstRender = " + firstRender);
base.OnAfterRender(firstRender);
}
protected override async Task OnAfterRenderAsync(bool firstRender){
Console.WriteLine("ServerValidations - OnAfterRenderAsync - firstRender = " + firstRender);
await base.OnAfterRenderAsync(firstRender);
}
void IDisposable.Dispose(){
Console.WriteLine("ServerValidations - Dispose");
}
public void CloseValidation(){
IsVisible = false;
}
}
- アプリを立ち上げただけでは初期化処理が呼ばれてないのがわかるね。
- ページを「Author」遷移してみると
Authors - OnInitialized
Authors - OnInitializedAsync
Authors - OnParametersSet
Authors - OnParametersSetAsync
ServerValidations - SetParametersAsync / IsVisible=False / parameters.GetValueOrDefault("IsVisible")=False
ServerValidations - OnInitialized
ServerValidations - OnInitializedAsync
ServerValidations - OnParametersSet / IsVisible=False
ServerValidations - OnParametersSetAsync / IsVisible=False
Authors - OnAfterRender - firstRender = True
Authors - OnAfterRenderAsync - firstRender = True
ServerValidations - OnAfterRender - firstRender = True
ServerValidations - OnAfterRenderAsync - firstRender = True
- とコンソールに出力されて一気に初期化されていることがわかるね。
- 次に「Save」ボタンをクリックしてみよう。
Authors - ShouldRender
ServerValidations - SetParametersAsync / IsVisible=False / parameters.GetValueOrDefault("IsVisible")=True
ServerValidations - OnParametersSet / IsVisible=True
ServerValidations - OnParametersSetAsync / IsVisible=True
ServerValidations - ShouldRender
Authors - OnAfterRender - firstRender = False
Authors - OnAfterRenderAsync - firstRender = False
ServerValidations - OnAfterRender - firstRender = False
ServerValidations - OnAfterRenderAsync - firstRender = False
-
ShouldRender
から始まって描画関係の処理化が呼ばれてるのがわかるね。 - あと2回目の描画だから
firstRender
はfalseになってるね。 - (動画ではやってないけど)別のページに切り替えるとDisposeが呼ばれてるのがわかるね。
- ちなみに今はクリック時にエラーを表示してるけど、サーバー接続エラーみたいなのは画面表示時にチェックしたいよね。
- それじゃ
OnParametersSetAsync
にDB接続エラーチェックを追加してみよう。といっても今回はテストだから接続は失敗したものとしてResult
にはfalseを設定するよ。 - 動かしてみるとページ遷移したタイミングでエラーが出てるのがわかるね。こうやってあげればユーザーに無駄な手間を取らせなくて良いよね。
- 前の章でやった
OnAfterRenderAsync
にJavaScriptを入れた時の動きを見てみよう。 -
StateHasChanged()
を呼んでるせいでページ全体の再描画が走ってるね。 - こういうのを効率化するにもRazorコンポーネントが有効だよ。
- 「SelectCity.razor」を作ってさっきのJavaScriptで一覧を取ってくる処理とかを入れてやってコンポーネント化して動きを見てみよう。
- さっきと違って全体の再描画が起きてないのがわかるね。再利用するだけでなく、こんな風にRazorコンポーネントは処理の軽量化にも有用だよ。
(Dispose
の話、ないと思ったら最後にあった)
#EP09:Razorコンポーネントライブラリ
-
今回はRazorコンポーネントライブラリの話だよ
-
ライブラリ化すると再利用がはかどるよ
-
MatBlazorとかフリーのコンポーネントも紹介するよ
-
Telerikとか有償コンポーネントもあるよ
-
それじゃ前回作成したメッセージとドロップダウンボックスのRazorコンポーネントをライブラリ化してみよう。
-
まずは新しいプロジェクトとして「Razorクラスライブラリ」を選択するよ。名前は「CuriousDriveRazorLibrary」にしようか。
-
とりあえずリリースモードでビルドして、呼び出し方を見てみよう。
-
呼び出す方のBlazor Webアプリのプロジェクトを開いてソリューションエクスプローラのプロジェクトを右クリック→「追加」→「プロジェクト参照」を選択して、表示されたダイアログの「参照」ボタンからさっきビルドした「CuriousDriveRazorLibrary」のDLL(プロジェクトフォルダの
bin\Release\net5.0
。net5.0
は.NET Coreを選択してたらnetstandard2.0
とかになるよ)を追加するよ。 -
「Index.razor」にコンポーネントを追加してみよう。頭に
@using CuriousDriveRazorLibrary
を追加して<Component1></Component1>
と入れるだけだよ。 -
実行してみるとちゃんとライブラリのコンポーネントが表示されてるのがわかるね。
-
それじゃ今度は前章で作った「SelectCity.razor」と「ServerValidations.razor」をライブラリに移して呼んでみよう。
-
エクスプローラーで「SelectCity.razor」と「ServerValidations.razor」を切り取って「CuriousDriveRazorLibrary」のプロジェクトフォルダに張り付けるよ。そしたらソリューションエクスプローラーを見てみると追加されてるのがわかるね。
(昔のVisualStudioと違ってプロジェクトにファイルの追加とかせんでええんや) -
ファイルを変更したらもちろんリビルドが必要だよ。
-
呼び出す側だけど、一々全部のページに
@using
書いていくは面倒だよね。 -
そこで「_Imports.razor」に
@using CuriousDriveRazorLibrary
を追加するよ。こうすることで全てのページで@using
を書いたのと同じになるよ。 -
ソースで
<ServerValidations>
とかにカーソルを合わせてみるとCuriousDriveRazorLibrary.ServerValidations
となってライブラリから呼ばれているようになっているのがわかるね。 -
動かしてみるとちゃんと移す前と同じように動いてるのがわかるね。
-
OSSのコンポーネントとかもあるよ。MatBlazorについて紹介しよう。
-
これはマテリアルデザインUIのコンポーネントでOSSだよ。ググってプロジェクトのページを見てみるとたくさんのコンポーネントがあるのがわかるね。
-
追加して使ってみよう。プロジェクトを右クリックして「Nugetパッケージの管理」を選択してMatBlazorを検索して「インストール」をクリックするだけだよ。
-
実は既にこのプロジェクトにはMtBlazorは組み込んでいて、Publishersのテーブルで使ってるよ、
-
コードを見てみよう。この例では
MatTable
っていうコンポーネントを使ってるね。HTMLのテーブルタグと似たような感じで書けるようになってるよ。 -
有償コンポーネントならTelerikとかがあるよ。Bootstrapとかも使えてグラフなんかのコンポーネントもあるよ。
-
ちなみに$1,300ほどかかるよ。かっこいいし選択肢としては十分ありだね。