Razorコンポーネントのビュー・ロジックの分割
はじめに
Razorコンポーネントでは、@code ブロック内で定義したメンバーとHTML要素をバインドして処理を記述できる。
しかし可読性等の観点より、Razorコンポーネントに処理をまとめるのではなく、ビュー・ロジックは分割させたい場合がある。その記法を試していく。
以下プロパティ・メソッドを持つRazorコンポーネントの、ビュー・ロジックを分割する。
1. コンポーネントのパラメーター([Parameter] 属性)
2. ライフサイクル用関数
3. ユーザー関数(Javascript呼び出し)
@inject IJSRuntime JS @foreach (var exe in Executed) { <p>@exe</p> } @code { // 実行済リスト(ページに一覧表示) private List<string> Executed { get; set; } = new(); // パラメーター private string _param = string.Empty; [Parameter] public string Param { get { return _param; } set { Executed.Add(value); _param = value; } } // ▽ライフサイクル // 初期化 protected override async Task OnInitializedAsync() { Executed.Add("OnInitializedAsync"); } // レンダリング後 protected override async Task OnAfterRenderAsync(bool firstRendeer) { if (firstRendeer) { ExceRazor(); await ExceJSAsync(); // 再レンダリング await InvokeAsync(() => StateHasChanged()); return; } } // △ライフサイクル // ユーザー関数 private void ExceRazor() { Executed.Add("ExceRazor"); } // ユーザー関数(Javascript呼び出し) private async Task ExceJSAsync() { Executed.Add(await JS.InvokeAsync<string>("ExceJS")); } }
@page "/" <PageTitle>Index</PageTitle> <TestComponent Param="Param" />
Index.razorに上記のコンポーネントを含めると、1~3のプロパティ・メソッドが呼ばれている事が確認できる。
ビュー・ロジックを分割後に同様の動作が再現できれば、成功とする。
部分クラス
Blazorでは部分クラスがサポートされているため、
コンポーネント (.razor) と紐づく分離コード ファイル (.cs) に部分クラスを定義できる。
@foreach (var exe in Executed) { <p>@exe</p> }
using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; namespace Blazor_CodeBehind.Pages { public partial class TestComponentPartial { // 実行済リスト(ページに一覧表示) private List<string> Executed { get; set; } = new(); // JSランタイムのインスタンス [Inject] private IJSRuntime JS { get; set; } // パラメーター private string _param = string.Empty; [Parameter] public string Param { get { return _param; } set { Executed.Add(value); _param = value; } } // ▽ライフサイクル // 初期化 protected override async Task OnInitializedAsync() { Executed.Add("OnInitializedAsync"); } // レンダリング後 protected override async Task OnAfterRenderAsync(bool firstRendeer) { if (firstRendeer) { ExceRazor(); await ExceJSAsync(); // 再レンダリング await InvokeAsync(() => StateHasChanged()); return; } } // △ライフサイクル // ユーザー関数 private void ExceRazor() { Executed.Add("ExceRazor"); } // ユーザー関数(Javascript呼び出し) private async Task ExceJSAsync() { Executed.Add(await JS.InvokeAsync<string>("ExceJS")); } } }
1~3のプロパティ・メソッドが呼ばれている事が確認でき、ビュー・ロジックが分割できている。
部分クラス移行時の変更箇所
@foreach (var exe in Executed) { <p>@exe</p> }
public partial class TestComponentPartial
learn.microsoft.com
// JSランタイムのインスタンス [Inject] private IJSRuntime JS { get; set; }
基本クラス
@inherits ディレクティブでコンポーネントの基本クラスを指定し、メソッド・プロパティを利用できる。
@using Blazor_CodeBehind.Data; @inherits TestComponentBaseClass @foreach (var exe in Executed) { <p>@exe</p> }
using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; namespace Blazor_CodeBehind.Data { public partial class TestComponentBaseClass : ComponentBase { // アクセス権を変更 // 実行済リスト(ページに一覧表示) protected List<string> Executed { get; set; } = new(); // JSランタイムのインスタンス [Inject] public IJSRuntime JS { get; set; } // パラメーター private string _param = string.Empty; [Parameter] public string Param { get { return _param; } set { Executed.Add(value); _param = value; } } // ▽ライフサイクル // 初期化 protected override async Task OnInitializedAsync() { Executed.Add("OnInitializedAsync"); } // レンダリング後 protected override async Task OnAfterRenderAsync(bool firstRendeer) { if (firstRendeer) { ExceRazor(); await ExceJSAsync(); // 再レンダリング await InvokeAsync(() => StateHasChanged()); return; } } // △ライフサイクル // ユーザー関数 private void ExceRazor() { Executed.Add("ExceRazor"); } // ユーザー関数(Javascript呼び出し) private async Task ExceJSAsync() { Executed.Add(await JS.InvokeAsync<string>("ExceJS")); } } }
1~3のプロパティ・メソッドが呼ばれている事が確認でき、ビュー・ロジックが分割できている。
基本クラス移行時の変更箇所
@using Blazor_CodeBehind.Data; @inherits TestComponentBaseClass @foreach (var exe in Executed) { <p>@exe</p> }
@codeの記載は全て基本クラスに移行する。
public class TestComponentBaseClass : ComponentBase
ライフサイクル関数はComponentBaseで定義されているため、基本クラスでComponentBaseを継承する。
// アクセス権を変更 // 実行済リスト(ページに一覧表示) protected List<string> Executed { get; set; } = new();
部分クラスとは異なり、コンポーネントより呼び出すプロパティがprivateではいけない。
備考
基本クラスの複数指定は不可能だった。
まとめ
Razorコンポーネントの以下プロパティ・メソッドも、部分クラス・基本クラスに分割することが可能だった。
1. コンポーネントのパラメーター([Parameter] 属性)
2. ライフサイクル用関数
3. ユーザー関数(Javascript呼び出し)
.NET 依存関係の挿入 事始め
はじめに
.NETのWebフレームワークは、起動時に他オブジェクト が依存するオブジェクトを挿入することができる。これを依存関係の挿入と呼ぶ。試しにWebフレームワークから実際に触ってみる。
以下を、挿入クラスとする。
オブジェクトの挿入場所
Program.cs内でWebApplicationBuilder.Servicesプロパティに挿入する。この時、クラスよりオブジェクトを生成してDIコンテナに登録する。オブジェクト利用時はDI(Dependency injection)コンテナを参照することになる。
クラスに引数が必須だった場合は生成できないと思われるが、どんな動作となるのだろうか。コンストラクタに引数を追加し、動作確認してみた。
結果、やはり実行時に以下エラーが発生した。(Blazor)
System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: TestDependency Lifetime: Scoped ImplementationType: TestDependency': Unable to resolve service for type 'System.String' while attempting to activate 'TestDependency'.)'
(Google翻訳)
System.AggregateException: '一部のサービスを構築できません (サービス記述子の検証中にエラーが発生しました'ServiceType: TestDependency Lifetime: Scoped ImplementationType: TestDependency': Unable to resolve service for type 'System.String' while attempts to activate 'TestDependency' .)'
オブジェクトの利用場所
Webフレームワークにより以下に分かれる。
1. Blazor Server等
各Razorコンポーネントより、@inject ディレクティブ・Inject属性を使用してオブジェクトを参照する
2. ASP.NET Core Webアプリ・ASP.NET Core WebAPI 等
各コントローラーのコンストラクタ引数よりオブジェクトを参照する。