Razor コンポーネントは C# クラスなので派生できる
既存の Razor コンポーネントに対して、あとちょっと機能を足したいという場合がままあります。そのような場合は、C# の拡張メソッドを実装するとか、その既存の Razor コンポーネントを内包・ラップした新たな Razor コンポーネントを作る、といった方法があります。しかし、そう滅多にあることではないのですが、希に、既存の Razor コンポーネントから "派生" した新たな Razor コンポーネントを作りたい、という場合があります。
幸い、Razor コンポーネントは、つまるところは、単なる C# のクラスに過ぎません。例えば <Foo />
という Razor コンポーネントは、つまりは Foo
クラスです。これから派生した新たな Razor コンポーネント、<Bar />
を作りたい、という場合は、プロジェクトに Bar.cs
という C# ソースファイルを追加し、そこで、普通に C# の構文で「Foo
クラスから派生した Bar
クラス」を実装すれば実現できます (下記コード例)。
public class Bar : Foo {
// ここに追加の実装を記述
}
.cs で実装すると *.razor.js が error BLAZOR106
を引き起こす
しかし、さらにここで、その派生して作成した <Bar />
に固有の JavaScript モジュールファイル、Bar.razor.js
を併設したいとしましょう。C# 側のソースコードは以下のようなイメージです。
public class Bar : Foo {
[Inject]
public IJSRuntime JSRuntime { get; init; } = null!;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
// 👇 JavaScript モジュールファイル、Bar.razor.js を読み込む
await using var module = await this.JSRuntime.InvokeAsync<IJSObjectReference>(
"import", "./Bar.razor.js");
// ここで、Bar.razor.js 内で export された関数を呼び出したりする
// 例) await module.InvokeAsync<...>(...);
}
}
}
ということで、プロジェクトに Bar.razor.js
を追加してビルドしてみたところ、残念ながら以下のビルドエラーが発生してしまいました。
error BLAZOR106:
The JS module file '...\Bar.razor.js' was defined but no associated razor component or
view was found for it.
どうやら、Blazor アプリケーションとしてのビルド処理において、*.razor.js のような、Razor コンポーネントに抱き合わせの併設ファイルを配置するには、その親 (?) となる *.razor ファイルが実在していないとダメらしいのです。
では、ということで、C# ソースファイル、すなわち、.cs ファイルではなく、Razor コンポーネント形式、すなわち、.razor ファイルで、Bar
コンポーネントを実装してみることにします。Razor コンポーネント (.razor ファイル) においてクラスの派生を用いるには、@inherits
ディレクティブを使えばよいです。ということで、実装は以下のようになります。
@inherits Foo
@code {
// 以下略
}
これで無事ビルドがとおるようになり、Bar.razor.js
を import して使えるようになります。
.cs ではなく .razor で派生すると、派生元コンポーネントのレンダリングが現れなくなる
しかし今度は、せっかく <Foo />
コンポーネントから派生したのに、<Foo />
コンポーネントとしてのレンダリング結果が出力されません。というのも、Razor コンポーネント、すなわち、.razor ファイルを Razor ソースジェネレーターが C# ソースコードに変換する際は、BuildRenderTree
メソッドもオーバーライドして、.razor ファイル内に記述されたマークアップをレンダリングするようにコード生成されるからなのですね。つまり、Bar.razor
内のマークアップをレンダリングするよう BuildRenderTree
メソッドが上書きされるため、<Foo />
コンポーネントで実装されていた BuildRenderTree
メソッドの処理内容は一切呼び出されず使用されないわけです。
そこで、<Foo />
コンポーネントで実装されていた BuildRenderTree
の処理内容を、派生先の <Bar />
コンポーネントでも今一度明示的にレンダリングするように、実装を追加します。
まず、派生元のレンダリング処理を保持する RenderFragment
型のフィールド変数を用意し、
@inherits Foo
@code {
// 👇 RenderFragment フィールド変数を用意
private RenderFragment _baseRenderer;
...
}
続けて、コンストラクタを追加して、そこで派生元のレンダリング処理、すなわち、派生元の BuildRenderTree
メソッド呼び出しを、用意しておいたフィールド変数に格納します。
@inherits Foo
@code {
private RenderFragment _baseRenderer;
// 👇コンストラクタを追加し...
public Bar() {
// 👇派生元の BuildRenderTree を呼び出す RenderFragment (ラムダ式) を作って
// 先に用意したフィールド変数に格納
_baseRenderer = (builder) => base.BuildRenderTree(builder);
}
...
}
そうしてフィールド変数に格納された RenderFragment
を、派生先 .razor 内のマークアップにてレンダリングします。
@inherits Foo
<!--
// 👇フィールド変数に格納された、派生元の BuildRenderTree を呼び出す RenderFragment を、
// マークアップ内でレンダリング -->
@_baseRenderer
@code {
private RenderFragment _baseRenderer;
public Bar() {
_baseRenderer = (builder) => base.BuildRenderTree(builder);
}
...
}
おわりに
以上の実装にて、既存の Razor コンポーネントから "派生" した新たな Razor コンポーネントを実装することができました。
そうそう出番のある技法とも思いませんが、バイナリでしか提供されていない既存の Razor コンポーネントにモンキーパッチを当てたい、などのケースで使えたりします。ご参考までに。