ObservableValidator

ObservableValidator は、INotifyDataErrorInfo インターフェイスを実装する基底クラスであり、他のアプリケーション モジュールに公開されるプロパティの検証をサポートします。 また、ObservableObject から継承されるため、INotifyPropertyChangedINotifyPropertyChanging も実装されます。 これは、プロパティ変更通知とプロパティ検証の両方をサポートする必要があるあらゆる種類のオブジェクトの開始点として使用できます。

プラットフォーム API: ObservableValidatorObservableObject

しくみ

ObservableValidator には、次の主な機能があります。

  • INotifyDataErrorInfo の基本実装を提供し、ErrorsChanged イベントとその他の必要な API を公開します。
  • (基底クラス ObservableObject によって提供されるオーバーロードに加えて) 一連の追加の SetProperty オーバーロードを提供します。これにより、プロパティを自動的に検証し、値を更新する前に必要なイベントを発生させる機能が提供されます。
  • 多数の TrySetProperty オーバーロードを公開します。これらは SetProperty に似ていますが、検証が成功した場合にのみターゲット プロパティを更新し、生成されたエラー (存在する場合) をさらなる検査のために返す機能を備えています。
  • ValidateProperty メソッドを公開します。これは、特定のプロパティの値が更新されていないが、代わりに更新された別のプロパティの値に依存して検証が行われる場合に、そのプロパティの検証を手動でトリガーするのに役立ちます。
  • ValidateAllProperties メソッドを公開します。これは、現在のインスタンス内のすべてのパブリック インスタンス プロパティの検証を自動的に実行します。ただし、実行されるのは、少なくとも1つの[ValidationAttribute] がそれらに適用されている場合に限ります。
  • ユーザーがもう一度入力する必要があるフォームにバインドされたモデルをリセットするときに役立つ ClearAllErrors メソッドを公開します。
  • さまざまなパラメータを渡して、プロパティの検証に使用される ValidationContext インスタンスを初期化できるコンストラクターが多数用意されています。 これは、正しく機能するために追加のサービスやオプションが必要になる可能性があるカスタム検証属性を使用する場合に特に役立ちます。

単純なプロパティ

変更通知と検証の両方をサポートするプロパティを実装する方法の例を次に示します。

public class RegistrationForm : ObservableValidator
{
    private string name;

    [Required]
    [MinLength(2)]
    [MaxLength(100)]
    public string Name
    {
        get => name;
        set => SetProperty(ref name, value, true);
    }
}

ここでは、ObservableValidator によって公開される SetProperty<T>(ref T, T, bool, string) メソッドを呼び出しています。追加の bool パラメータが true に設定されているということは、プロパティの値が更新されたときにプロパティも検証することを示しています。 ObservableValidator は、そのプロパティに適用された属性で指定されたすべてのチェックを使用して、新しい値ごとに検証を自動的に実行します。 その後、他のコンポーネント (UI コントロールなど) は、viewmodel と対話し、viewmodel に現在存在するエラーを反映するように状態を変更できます。そのためには、ErrorsChanged に登録し、GetErrors(string) メソッドを使用して、変更された各プロパティのエラー リストを取得します。

カスタム検証メソッド

プロパティの検証には、viewmodel に追加のサービス、データ、またはその他の API へのアクセス権が必要な場合があります。 カスタム検証をプロパティに追加する方法は、シナリオと必要な柔軟性のレベルによって異なります。 プロパティの追加検証を実行するために特定のメソッドを呼び出す必要があることを示すために、[CustomValidationAttribute] 型の使用例を次に示します。

public class RegistrationForm : ObservableValidator
{
    private readonly IFancyService service;

    public RegistrationForm(IFancyService service)
    {
        this.service = service;
    }

    private string name;

    [Required]
    [MinLength(2)]
    [MaxLength(100)]
    [CustomValidation(typeof(RegistrationForm), nameof(ValidateName))]
    public string Name
    {
        get => this.name;
        set => SetProperty(ref this.name, value, true);
    }

    public static ValidationResult ValidateName(string name, ValidationContext context)
    {
        RegistrationForm instance = (RegistrationForm)context.ObjectInstance;
        bool isValid = instance.service.Validate(name);

        if (isValid)
        {
            return ValidationResult.Success;
        }

        return new("The name was not validated by the fancy service");
    }
}

この場合、viewmodel に挿入されるサービスを介して Name プロパティの検証を実行する静的な ValidateName メソッドがあります。 このメソッドは、name プロパティ値と使用中の ValidationContext インスタンスを受け取ります。これには、viewmodel インスタンス、検証されるプロパティの名前、必要に応じてサービス プロバイダーや使用または設定できるカスタム フラグなどが含まれます。 この場合、検証コンテキストから RegistrationForm インスタンスを取得し、そこから挿入されたサービスを使用してプロパティを検証します。 この検証は他の属性で指定された検証の次に実行されるため、カスタム検証メソッドと既存の検証属性を自由に組み合わせることができることに注意してください。

カスタム検証属性

カスタム検証を行うもう 1 つの方法は、カスタム [ValidationAttribute] を実装し、オーバーライドされた IsValid メソッドに検証ロジックを挿入することです。 これにより、複数の場所で同じ属性を再利用することが非常に簡単になるため、上記のアプローチと比較して柔軟性がさらに高まります。

同じ viewmodel 内の別のプロパティに対する相対値に基づいてプロパティを検証したいとします。 最初の手順は、次のようにカスタム [GreaterThanAttribute] を定義することです。

public sealed class GreaterThanAttribute : ValidationAttribute
{
    public GreaterThanAttribute(string propertyName)
    {
        PropertyName = propertyName;
    }

    public string PropertyName { get; }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        object
            instance = validationContext.ObjectInstance,
            otherValue = instance.GetType().GetProperty(PropertyName).GetValue(instance);

        if (((IComparable)value).CompareTo(otherValue) > 0)
        {
            return ValidationResult.Success;
        }

        return new("The current value is smaller than the other one");
    }
}

次に、この属性を viewmodel に追加できます。

public class ComparableModel : ObservableValidator
{
    private int a;

    [Range(10, 100)]
    [GreaterThan(nameof(B))]
    public int A
    {
        get => this.a;
        set => SetProperty(ref this.a, value, true);
    }

    private int b;

    [Range(20, 80)]
    public int B
    {
        get => this.b;
        set
        {
            SetProperty(ref this.b, value, true);
            ValidateProperty(A, nameof(A));
        }
    }
}

この場合、2 つの数値プロパティがあります。それらは特定の範囲内にあり、相互に特定の関係を持っている必要があります (AB より大きい必要があります)。 最初のプロパティに新しい [GreaterThanAttribute] を追加し、B のセッターに ValidateProperty への呼び出しも追加しました。これにより、B が変更されるたびに (検証の状態はそれに依存しているため)、A が再び検証されます。 このカスタム検証を有効にするには、viewmodel にこれら 2 つのコード行が必要です。また、アプリケーションの他の viewmodel でも役立つ可能性がある再利用可能なカスタム検証属性を持つという利点も得られます。 このアプローチは、検証ロジックが viewmodel 定義自体から完全に切り離されているため、コードのモジュール化にも役立ちます。

  • サンプル アプリ (複数の UI フレームワーク向け) を確認して、MVVM Toolkit の実際の動作を確認してください。
  • 単体テストで、その他の例を確認することもできます。