😺
【Blazor Serverアプリ】ページからレイアウトへのイベントコールバック ~ @Bodyを越えて
はじめに
- この記事では、以下のような読者像を想定しています。
- Blazor Serverアプリのチュートリアルを済ませた
- この記事ではツール類の使用方法には言及しません。
この記事で解ること
- カスケーディングパラメータを使ってイベントコールバックを行う方法について言及します。
環境
- Windows 11
- VisualStudio 2022
- 「ASP.NETとWeb開発」導入済み
- .NET 7.0
やりたいこと
- Blazor Serverアプリの新規作成を行ったときに導入されるテンプレでは、ページの右上にリンク付きの「About」が表示されます。
- これは、ページ全体のレイアウトを担う
MainLayout.razor
に記述されています。
- これは、ページ全体のレイアウトを担う
MainLayout.razor
@inherits LayoutComponentBase
<PageTitle>BlazorApp</PageTitle>
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>
<article class="content px-4">
@Body
</article>
</main>
</div>
- この部分に「現在のページの見出し」を表示することを考えました。
MainLayout.razor
@inherits LayoutComponentBase
<PageTitle>BlazorApp</PageTitle>
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<h1>_sectionTitle</h1>
</div>
<article class="content px-4">
@Body
</article>
</main>
</div>
@code {
protected string _sectionTitle = string.Empty;
}
- この見出しは、
@Body
の中身である@page
コンポーネントから更新される必要があります。 - これが
@Body
でなく、通常の子要素コンポーネントであれば、以下のようにしてイベントコールバックを使えば済む話です。
MainLayout.razor
<body OnClickCallback="@((string title) => _sectionTitle = title)" />
-
@Body
を越えてパラメータを渡すためには、以下のようにカスケーディングパラメータを使うことになります。
MainLayout.razor
<CascadingValue Value="@((string title) => _sectionTitle = title)" Name="Section">
@Body
</CascadingValue>
- しかし、カスケーディングパラメータだと、デリゲートを渡すだけでは、コールバックできません。
解決方法
-
EventCallbackFactory
クラスでEventCallback
構造体を生成して渡します。
MainLayout.razor
@inherits LayoutComponentBase
<PageTitle>BlazorApp</PageTitle>
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<h1>@_sectionTitle</h1>
</div>
<article class="content px-4">
<CascadingValue Value="@(EventCallback.Factory.Create<string> (this, (string title) => _sectionTitle = title))" Name="Section">
@Body
</CascadingValue>
</article>
</main>
</div>
@code {
protected string _sectionTitle = string.Empty;
}
-
EventCallback.Factory
は、EventCallbackFactory
のインスタンスを保持している静的な読み出し専用フィールドです。-
Create<TValue>(Object, Action)
によって、レシーバとコールバックが生成されます。
-
- ここでは、双方とも
CascadingValue
のパラメータValue
に埋め込みましたが、コールバックを@code {~}
に書くことも、レシーバをラムダ式でなくメソッドにすることも、もちろん可能です。
ページ側のコード
- ページ側では、以下のようにしてパラメータを受け取ってコールバックを行います。
Index.razor
@page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
@code {
[CascadingParameter(Name = "Section")] protected EventCallback<string> SetSectionTitle { get; set; }
protected override async Task OnInitializedAsync () => await SetSectionTitle.InvokeAsync ("Home");
}
- なお、
async
、await
は必須ではなく、この例のように待機が不要であればOnInitialized ()
を使うことも可能です。
おわりに
- この結論に至るまでに、
StateHasChanged ()
を使って強制的に再描画させたり、DIコンテナにScoped
なサービスを登録したりなどと、試行錯誤を重ねました。- 同様の課題を抱えた方のお役に立てば幸いです。
- 執筆者は、Blazor、ASP.NETともに初学者ですので、誤りもあるかと思います。
- お気づきの際は、是非コメントや編集リクエストにてご指摘ください。
- あるいは、「それでも解らない」、「自分はこう捉えている」などといった、ご意見、ご感想も歓迎いたします。
Discussion