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>
}

@injectが不要となり(後述)、@codeの記載は全て部分クラスに移行する。


public partial class TestComponentPartial

コンポーネントと同名のクラスを作成し、部分クラスのためpartial キーワードをつける。
learn.microsoft.com


// JSランタイムのインスタンス
[Inject]
private IJSRuntime JS { get; set; }

@injectではなく、 [Inject]属性を利用したプロパティインジェクションにする。

基本クラス

@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>
}

@usingで基本クラスの名前空間を、@inheritsで基本クラスを指定する。
@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 等

各コントローラーのコンストラクタ引数よりオブジェクトを参照する。