コンテンツにスキップ

入力値検査⚓︎

ここでは、アプリケーションアーキテクチャで定義している入力値検査の実装方法を説明します。 入力値検査方針については こちら を参照してください。

前提:本章で説明に使用する画面の概要⚓︎

本章では、例として以下のような画面を作成します。 なお、以下のイメージは Blazor Web アプリの「サンプルページを含める」にチェックを入れた状態でプロジェクトを作成した場合の例です。

  1. 作成する画面のイメージ:エラーがない状態

    作成する画面のイメージ:エラーがない状態

  2. 作成する画面のイメージ:入力エラーがある状態

    入力チェックの結果(エラー)は、入力項目の直下の他、「登録」ボタン下部にまとめて表示します( ValidationSummary を使用)。

    作成する画面のイメージ:入力エラーがある状態

本章で示す手順に従うと、以下のファイルが作成されます。これらは動作を確認後、削除してください。

[Blazor Web App プロジェクト]
├ Components
│ └ Pages
│   └ RegisterStudent.razor
├ Resources
│ └ Messages.resx
└ ViewModels
  ├ Enrollment.cs
  └ Student.cs

単項目チェックの実装⚓︎

Blazor Web アプリでは、 EditForm と属性( Attribute )ベースの検証を組み合わせることで、単項目チェックを実現します。 詳細は フォーム検証 および データ注釈検証コンポーネントとカスタム検証 を参照してください。

大まかな実装の流れを以下に示します。

  1. 検証結果として表示するエラーメッセージを定義します。

    メッセージリソースの追加 の手順に従ってメッセージを作成します。

    メッセージリソースの作成例

    メッセージリソース

  2. ビューモデルを作成します。

    プロパティを持つビューモデルのクラスを作成します。プロパティは入力フォームの各項目となります。

    ビューモデルの実装例
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    public class Student
    {
        public Guid Id { get; set; } = Guid.Empty;
    
        public string StudentNumber { get; set; } = string.Empty;
    
        public string FirstName { get; set; } = string.Empty;
    
        public string LastName { get; set; } = string.Empty;
    
        public int Age { get; set; }
    
        public int EnrollmentYear { get; set; }
    
        public int? GraduationYear { get; set; }
    }
    
  3. ビューモデルの各プロパティに検証属性を付与することで、検証内容や表示するエラーメッセージを定義します。

    ビューモデルに検証属性を付与した例
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public class Student
    {
        public Guid Id { get; set; } = Guid.Empty;
    
        [Display(Name = "学生番号")]
        [Required(ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.RequiredToInput))]
        [StringLength(10, ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.OverMaxStringLength))]
        public string StudentNumber { get; set; } = string.Empty;
    
        [Display(Name = "姓")]
        [Required(ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.RequiredToInput))]
        [StringLength(20, ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.OverMaxStringLength))]
        public string FirstName { get; set; } = string.Empty;
    
        [Display(Name = "名")]
        [Required(ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.RequiredToInput))]
        [StringLength(20, ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.OverMaxStringLength))]
        public string LastName { get; set; } = string.Empty;
    
        [Display(Name = "年齢")]
        [Required(ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.RequiredToInput))]
        [Range(0, 150, ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.InvalidRange))]
        public int Age { get; set; }
    
        [Display(Name = "入学年")]
        [Required(ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.RequiredToInput))]
        [Range(1980, 2100, ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.InvalidRange))]
        public int EnrollmentYear { get; set; }
    
        [Display(Name = "卒業年")]
        [Range(1980, 2100, ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.InvalidRange))]
        public int? GraduationYear { get; set; }
    }
    
  4. Razor コンポーネントに EditForm を用いて入力フォームを作成します。

  5. EditForm にビューモデルをバインドします。

    Razor コンポーネントに EditForm を追加した例
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    @page "/students/register"
    @using [Blazor Web App].ViewModels
    
    <PageTitle>学生登録</PageTitle>
    
    <h1>学生登録</h1>
    
    <EditForm FormName="RegisterStudentForm" EditContext="@editContext" OnValidSubmit="HandleValidSubmit">
        <DataAnnotationsValidator />
    
        <div class="mb-3">
            <label class="form-label">学生番号</label>
            <InputText class="form-control" @bind-Value="model.StudentNumber" />
            <ValidationMessage For="() => model.StudentNumber" />
        </div>
    
        <div class="mb-3">
            <label class="form-label"></label>
            <InputText class="form-control" @bind-Value="model.FirstName" />
            <ValidationMessage For="() => model.FirstName" />
        </div>
    
        <div class="mb-3">
            <label class="form-label"></label>
            <InputText class="form-control" @bind-Value="model.LastName" />
            <ValidationMessage For="() => model.LastName" />
        </div>
    
        <div class="mb-3">
            <label class="form-label">年齢</label>
            <InputNumber class="form-control" @bind-Value="model.Age" />
            <ValidationMessage For="() => model.Age" />
        </div>
    
        <div class="mb-3">
            <label class="form-label">入学年</label>
            <InputNumber class="form-control" @bind-Value="model.EnrollmentYear" />
            <ValidationMessage For="() => model.EnrollmentYear" />
        </div>
    
        <div class="mb-3">
            <label class="form-label">卒業年</label>
            <InputNumber class="form-control" @bind-Value="model.GraduationYear" />
            <ValidationMessage For="() => model.GraduationYear" />
        </div>
    
        <button type="submit" class="btn btn-primary">登録</button>
    
        <div class="mt-3">
            <ValidationSummary />
        </div>
    </EditForm>
    
    @code {
        private Student model = new();
        private EditContext editContext = default!;
    
        protected override void OnInitialized()
        {
            editContext = new EditContext(model);
            editContext.OnValidationRequested += HandleValidationRequest;
        }
    
        private void HandleValidationRequest(object? sender, ValidationRequestedEventArgs e)
        {
            // 項目間チェックの実施
        }
    
        private void HandleValidSubmit()
        {
            // 業務ロジックの実行、複合チェック結果の処理など
        }
    }
    

ここまでの手順で 前提 で示した画面が作成されます。 「登録」ボタンをクリックし、入力チェックが実行されることを確認してください。

入れ子になった ViewModel での単項目チェック⚓︎

業務の複雑度によっては、ビューモデルが子アイテムのリストを持つことがあります。このような入れ子になったビューモデルで単項目チェックを実装する方法を説明します( 参照 )。

  1. Blazor Web アプリの Program.cs で、入れ子になったオブジェクトのバリデーションをサポートするためのサービスを登録します。

    Program.cs
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    var builder = WebApplication.CreateBuilder(args);
    
    // その他のサービス登録は省略
    
    builder.Services.AddValidation();
    
    var app = builder.Build();
    
    // その他のサービス有効化は省略
    
    app.Run();
    
  2. 子アイテムのビューモデルを定義します。

    子アイテムのビューモデルの例
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public class Enrollment
    {
        public Guid Id { get; set; } = Guid.Empty;
    
        [Display(Name = "科目番号")]
        [Required(ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.RequiredToInput))]
        [StringLength(12, ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.OverMaxStringLength))]
        public string CourseNumber { get; set; } = string.Empty;
    
        [Display(Name = "科目名")]
        [Required(ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.RequiredToInput))]
        [StringLength(100, ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.OverMaxStringLength))]
        public string CourseName { get; set; } = string.Empty;
    
        [Display(Name = "年度")]
        [Required(ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.RequiredToInput))]
        [Range(1980, 2100, ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.InvalidRange))]
        public int Year { get; set; }
    
        [Display(Name = "学期")]
        [Required(ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.RequiredToInput))]
        public int Semester { get; set; }
    
        [Display(Name = "成績")]
        [StringLength(1, ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.OverMaxStringLength))]
        public string? Grade { get; set; }
    }
    
  3. 子アイテムのリストを持つビューモデルを定義します。

  4. 上のビューモデルのクラスに [ValidatableType] 属性を付与します。

    子アイテムのリストを持つビューモデルの例
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    [ValidatableType]
    public class Student
    {
        public Guid Id { get; set; } = Guid.Empty;
    
        [Display(Name = "学生番号")]
        [Required(ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.RequiredToInput))]
        [StringLength(10, ErrorMessageResourceType = typeof(Messages), ErrorMessageResourceName = nameof(Messages.OverMaxStringLength))]
        public string StudentNumber { get; set; } = string.Empty;
    
        // 省略
    
        public List<Enrollment> Enrollments { get; set; } = new();
    }
    
  5. ビューの @code に以下のメソッドを追加します。

    子アイテムを model に追加するメソッド
    1
    2
    3
    4
    private void AddEnrollment()
    {
        model.Enrollments.Add(new Enrollment());
    }
    
  6. EditForm に子アイテムの入力項目を追加します。

    子アイテムを追加した EditForm の例
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    <EditForm FormName="RegisterStudentForm" EditContext="@editContext" OnValidSubmit="HandleValidSubmit">
        <DataAnnotationsValidator />
    
        <div class="mb-3">
            <label class="form-label">学生番号</label>
            <InputText class="form-control" @bind-Value="model.StudentNumber" />
            <ValidationMessage For="() => model.StudentNumber" />
        </div>
    
        <!-- 省略 -->
    
        <h2 class="h5 mt-4">履修科目</h2>
    
        <div class="mb-3">
            <button type="button" class="btn btn-outline-primary" @onclick="AddEnrollment">科目を追加</button>
        </div>
    
        @foreach (var enrollment in model.Enrollments)
        {
            <div class="card mb-3" @key="enrollment">
                <div class="card-body">
                    <div class="row g-3">
                        <div class="col-md-4">
                            <label class="form-label">科目番号</label>
                            <InputText class="form-control" @bind-Value="enrollment.CourseNumber" />
                            <ValidationMessage For="() => enrollment.CourseNumber" />
                        </div>
    
                        <div class="col-md-8">
                            <label class="form-label">科目名</label>
                            <InputText class="form-control" @bind-Value="enrollment.CourseName" />
                            <ValidationMessage For="() => enrollment.CourseName" />
                        </div>
    
                        <div class="col-md-4">
                            <label class="form-label">年度</label>
                            <InputNumber class="form-control" @bind-Value="enrollment.Year" />
                            <ValidationMessage For="() => enrollment.Year" />
                        </div>
    
                        <div class="col-md-4">
                            <label class="form-label">学期</label>
                            <InputNumber class="form-control" @bind-Value="enrollment.Semester" />
                            <ValidationMessage For="() => enrollment.Semester" />
                        </div>
    
                        <div class="col-md-4">
                            <label class="form-label">成績</label>
                            <InputText class="form-control" @bind-Value="enrollment.Grade" />
                            <ValidationMessage For="() => enrollment.Grade" />
                        </div>
                    </div>
                </div>
            </div>
        }
    
        <button type="submit" class="btn btn-primary">登録</button>
    
        <div class="mt-3">
            <ValidationSummary />
        </div>
    </EditForm>
    

ここまでの手順を実行し、学生番号~入学年に適切な値を入力した状態で「科目を追加」ボタンをクリックします。 「登録ボタン」を押して、ビューモデルの子アイテムである「科目」についても単項目チェックが働くことを確認してください。

子アイテムに対する単項目チェックエラーのイメージ

項目間チェックの実装⚓︎

項目間チェックは OnValidationRequested イベントハンドラー内で実装します。

  1. @codeValidationMessageStore の変数を宣言します。
  2. OnInitialized または OnInitializedAsync メソッド内で上の変数を初期化します。
  3. OnValidationRequested イベントハンドラーで以下の処理を追加します。
    1. ValidationMessageStore をクリアします。
    2. 項目間チェック結果のエラーメッセージを ValidationMessageStore に追加します。
    3. EditContext.NotifyValidationStateChanged メソッドを呼び出し、入力値検査の状態が変わったことを伝えます。
項目間チェックの結果を表示する例
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@code {
    private Student model = new();
    private EditContext editContext = default!;
    private ValidationMessageStore validationMessageStore = default!;

    protected override void OnInitialized()
    {
        editContext = new EditContext(model);
        editContext.OnValidationRequested += HandleValidationRequest;
        validationMessageStore = new ValidationMessageStore(editContext);
    }

    private void HandleValidationRequest(object? sender, ValidationRequestedEventArgs e)
    {
        validationMessageStore.Clear();

        if (model.GraduationYear < model.EnrollmentYear) // 項目間チェックの実施
        {
            validationMessageStore.Add(new FieldIdentifier(model, nameof(model.GraduationYear)), "卒業年は入学年以降の年を入力してください。");
            editContext.NotifyValidationStateChanged();
            return;
        }
    }
}

ここまでの手順を実行し、「卒業年」に「入学年」より過去の年を入力して「登録」ボタンをクリックすると、以下のようにエラーになることを確認してください。

項目間チェックエラーのイメージ

複合チェックの実装⚓︎

複合チェックは、アプリケーションコア層の業務ロジック内で実装します。

  1. @codeValidationMessageStore の変数を宣言します(項目間チェックと同様)。
  2. OnInitialized または OnInitializedAsync メソッド内で上の変数を初期化します(項目間チェックと同様)。
  3. @code にビジネスロジックを実行するメソッドを作成します。ここではメソッド名を HandleValidSubmit とします。

    手順 1 ~ 3 を実行した状態の @code セクション
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    private Student model = new();
    private EditContext editContext = default!;
    private ValidationMessageStore validationMessageStore = default!;
    
    protected override void OnInitialized()
    {
        editContext = new EditContext(model);
        validationMessageStore = new ValidationMessageStore(editContext);
    }
    
    private void HandleValidSubmit()
    {
        // 業務ロジックの実行
    }
    
  4. HandleValidSubmit メソッド内で以下の処理を追加します。

    1. ValidationMessageStore をクリアします。
    2. 相関チェック結果のエラーメッセージを ValidationMessageStore に追加します。 ここでは、ビジネスロジックが複合チェックの結果を業務例外としてスローしたものとします。
    3. EditContext.NotifyValidationStateChanged メソッドを呼び出し、入力値検査の状態が変わったことを伝えます。
    HandleValidSubmit メソッドのコード例
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    private void HandleValidSubmit()
    {
        validationMessageStore.Clear();
    
        try
        {
            // 業務ロジックの実行
        }
        catch (Exception) // ←ここではサンプルのため汎用例外としていますが、実際のコードでは適宜業務例外クラスを定義してください。
        {
            validationMessageStore.Add(new FieldIdentifier(model, string.Empty), "同じ学生番号の学生がすでに存在します。");
            editContext.NotifyValidationStateChanged();
            return;
        }
    
        // 以降の処理は省略
    }
    
  5. EditFormOnValidSubmit イベントハンドラーに HandleValidSubmit メソッドを関連付けます。

    OnValidSubmit イベントハンドラーに HandleValidSubmit メソッドを関連付けた EditForm
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <EditForm FormName="RegisterStudentForm" EditContext="@editContext" OnValidSubmit="HandleValidSubmit">
        <DataAnnotationsValidator />
    
        <!-- 入力項目は省略 -->
    
        <button type="submit" class="btn btn-primary">登録</button>
    
        <div class="mt-3">
            <ValidationSummary />
        </div>
    </EditForm>
    

ここまでの手順を実行し、複合チェックエラーとなるような値を入力して「登録」ボタンをクリックすると、以下のようなエラーが表示されます。

複合チェックエラーのイメージ