ObservableValidator
ObservableValidator
は、INotifyDataErrorInfo
インターフェイスを実装する基底クラスであり、他のアプリケーション モジュールに公開されるプロパティの検証をサポートします。 また、ObservableObject
から継承されるため、INotifyPropertyChanged
と INotifyPropertyChanging
も実装されます。 これは、プロパティ変更通知とプロパティ検証の両方をサポートする必要があるあらゆる種類のオブジェクトの開始点として使用できます。
プラットフォーム API: ObservableValidator、ObservableObject
しくみ
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 つの数値プロパティがあります。それらは特定の範囲内にあり、相互に特定の関係を持っている必要があります (A
は B
より大きい必要があります)。 最初のプロパティに新しい [GreaterThanAttribute]
を追加し、B
のセッターに ValidateProperty
への呼び出しも追加しました。これにより、B
が変更されるたびに (検証の状態はそれに依存しているため)、A
が再び検証されます。 このカスタム検証を有効にするには、viewmodel にこれら 2 つのコード行が必要です。また、アプリケーションの他の viewmodel でも役立つ可能性がある再利用可能なカスタム検証属性を持つという利点も得られます。 このアプローチは、検証ロジックが viewmodel 定義自体から完全に切り離されているため、コードのモジュール化にも役立ちます。
例
MVVM Toolkit