かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

System.ComponentModel.DataAnnotationsの検証が実行される順番

さて、超便利と噂のDataAnnotationですが、こいつの検証の実行順番って意外と知られてないけど、こんな動きするんだぜ!っていうのを見てみたいと思います。

検証用クラスの作成

[CustomValidation(typeof(Sample), "ValidateObject")]
public class Sample : IValidatableObject
{
    [CustomValidation(typeof(Sample), "ValidateProperty")]
    public string Value { get; set; }

    // オブジェクトの検証(第二引数のValidationContextは無くてもいい
    public static ValidationResult ValidateObject(Sample s, ValidationContext c)
    {
        Console.WriteLine("ValidateObject");
        return ValidationResult.Success;
    }

    // プロパティの検証(第二引数のValidationContextは無くてもいい
    public static ValidationResult ValidateProperty(string s, ValidationContext c)
    {
        Console.WriteLine("ValidateProperty");
        return ValidationResult.Success;
    }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        Console.WriteLine("IValidatableObject.Validate");
        return new[] { ValidationResult.Success };
    }
}

とりあえず、プロパティの検証。オブジェクト自体の検証。あとはIValidatableObjectによる検証と3パターン用意してみました。

どういう順番で呼ばれるか見てみよう

さっそくどういう順番で呼ばれるのかプログラムを書いてみました。TryValidateObjectを呼んでるだけです。

var s = new Sample();

List<ValidationResult> r = new List<ValidationResult>();
// 全プロパティ検証する
Validator.TryValidateObject(s,
    new ValidationContext(s, null, null),
    r,
    true);

これを、実行すると、以下のように表示されます。

ValidateProperty
ValidateObject
IValidatableObject.Validate

ということで以下の順番で呼ばれることがわかりました。

  1. プロパティのバリデーション
  2. オブジェクトのバリデーション
  3. IValidatableObjectのバリデーション

ここまでは、とりあえずOKです。ちょっとめんどくさいのはエラーがあるときの挙動です。では、ValidatePropertyの処理を以下のように書き換えて常にエラーが出るようにしてみます。

// プロパティの検証(第二引数のValidationContextは無くてもいい
public static ValidationResult ValidateProperty(string s, ValidationContext c)
{
    Console.WriteLine("ValidateProperty");
    return new ValidationResult("だめぽ");
}

この状態で実行すると、以下のような結果になります。

ValidateProperty

そう、プロパティのバリデーションでエラーがでたらヘタレなことにプロパティのバインドでエラーが出たらそこで諦めてしまいます。次はValidatePropertyをSuccessに直してValidateObjectでエラーを返すように書き換えます。

// オブジェクトの検証(第二引数のValidationContextは無くてもいい
public static ValidationResult ValidateObject(Sample s, ValidationContext c)
{
    Console.WriteLine("ValidateObject");
    return new ValidationResult("だめぽ");
}

// プロパティの検証(第二引数のValidationContextは無くてもいい
public static ValidationResult ValidateProperty(string s, ValidationContext c)
{
    Console.WriteLine("ValidateProperty");
    return ValidationResult.Success;
}

これで実行すると以下のようになります。

ValidateProperty
ValidateObject

今度は、ValidatePropertyはSuccessなのでValidateObjectが続けて呼ばれます。ValidateObjectでエラーが出ると、そこで諦めてしまいます。IValidatableObjectは呼ばれません。

まとめ

ということで簡単にまとめます。

  1. プロパティの検証、オブジェクトの検証、IValidatableObjectの検証の順番で検証は呼ばれる
  2. 検証エラーが起きると、検証は途中で止まる

ということで、検証はValidator.TryValidateObjectを呼んでも必ずしも全部実行されるわけではないということでした。