WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

IValidatableObject を継承したカスタムモデル

by WebSurfer 28. February 2020 15:31

IValidatableObject インターフェイスを継承したカスタムモデルを使って検証を行う方法を備忘録として書いておきます。

IValidatableObject を継承したクラスで検証

先の記事「CustomValidation 属性」で書いたモデルレベルの検証と同等な機能を実装してみます。

マイクロソフト公式解説書「プログラミング Microsoft ASP.NET MVC」によると "IValidatableObject インターフェイスを実装すると、クラスレベルで CustomValidation 属性を使用するのと機能的には同じになります"、"クラスレベルの検証を実装するにあたって、クラスレベルで IValidatableObject インターフェイスと CustomValidation 属性のどちらを利用するかは、完全にあなた次第です" とのことです。

ただし、"モデルにデータアノテーションも追加されている場合、有効な状態でないプロパティが存在すると、Validate メソッドが呼び出されなくなります。IValidatableObject インターフェイスを利用する場合は、問題が起きないよう、データアノテーションを完全に削除することをお勧めします" との注記があります。

ということは、データアノテーションによるクライアント側での検証ができなくなるということで、あまり使い道はなさそうな気がしますが。

コードは以下の通りです。.NET Framework MVC5 と ASP.NET Core 3.1 MVC で同じコードが使えます。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
using System.Globalization;

namespace MvcCoreApp.Models
{
  // IValidatableObject インターフェイス利用
  public class CustomValidationModel : IValidatableObject
  {
    public int PersonId { set; get; }

    [Display(Name = "名前")]
    public string Name { set; get; }

    [Display(Name = "メールアドレス")]
    public string Mail { set; get; }

    // int? にしないと未入力では Validate メソッドが動かない。
    // int, int? どちらも "1.0" とか入力された場合は Validate
    // メソッドが動かない。モデルバインドされる際に例外がスロ
    // ーされ Validate メソッドに制御が飛ばないのだと思われる
    [Display(Name = "年齢")]
    public int? Age { set; get; }

    // 検証を行う Validate メソッド
    public IEnumerable<ValidationResult> Validate(
                                ValidationContext context)
    {
      var model = 
        context.ObjectInstance as CustomValidationModel;

      if (model == null)
      {
        throw new NullReferenceException();
      }

      if (string.IsNullOrEmpty(model.Name))
      {
        yield return new ValidationResult(
            "名前は必須", new string[] { "Name" });
      }
      else if (model.Name.Length < 2 ||
               model.Name.Length > 20)
      {
        yield return new ValidationResult(
            "名前は 2 ~ 20 文字の範囲",
            new string[] { "Name" });
      }

      if (string.IsNullOrEmpty(model.Mail))
      {
        yield return new ValidationResult(
            "メールアドレスは必須",
            new string[] { "Mail" });
      }
      else
      {
        bool isValidEmai = Regex.IsMatch(model.Mail,
            @"・・・正規表現(省略)・・・",
            RegexOptions.IgnoreCase,
            TimeSpan.FromMilliseconds(250));

        if (!isValidEmai)
        {
          yield return new ValidationResult(
              "有効な Email 形式ではありません",
              new string[] { "Mail" });
        }
      }

      if (model.Age == null)
      {
        yield return new ValidationResult(
            "年齢は必須", new string[] { "Age" });
      }
      else if (model.Age < 0 || model.Age > 200)
      {
        yield return new ValidationResult(
            "年齢は 0 ~ 200 の範囲",
            new string[] { "Age" });
      }
      else if (!string.IsNullOrEmpty(model.Name) && 
               model.Name.StartsWith("佐藤") && 
               model.Age < 20)
      {
        yield return new ValidationResult(
            "佐藤さんは二十歳以上でなければなりません",
            new string[] { "" });
      }
    }
  }
}

.NET Framework MVC5 と ASP.NET Core 3.1 MVC でコードは同じものが使えますが、動作は若干異なります。主な違いは以下の通りです。

  1. Age プロパティを int 型にすると、MVC5 の場合 input 要素に data-val="true" data-val-number="フィールド 年齢 には数字を指定してください。 " data-val-required="年齢 フィールドが必要です。" という検証属性が付与される。Core には data-val-number 属性の設定はない。
  2. Age プロパティを int? 型 (null 許容) にすると、MVC5 の場合 input 要素に検証属性 data-val="true" data-val-number="フィールド 年齢 には数字を指定してください。" が付与される。Core の場合は検証属性は何も付与されない。
  3. さらに、type 属性には "number" と設定されるが、IE, Chrome, Firefox いずれも "1.0" というような整数としては不正な文字列を入力できる。 その場合、クライアント側の検証はパスしてしまう。 POST すると、MVC5 の場合は Validate メソッドに制御が飛ぶが、Core の場合はモデルバインドしようとする際に例外がスローされるようで Validate メソッドには制御が飛ばない。

    ただし、Validate メソッドに制御が飛んでも、Validate メソッドで設定したエラーメッセージはどこかで上書きされ "値 '1.0' は 年齢 に対して無効です。" となる。理由不明。

Tags: , ,

CORE

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  February 2020  >>
MoTuWeThFrSaSu
272829303112
3456789
10111213141516
17181920212223
2425262728291
2345678

View posts in large calendar