I.はじめに
長い間 WPF を使用していませんでしたが、最近デスクトップ プロジェクトができたので、再び使用するようになりました。
この一連の MVVM ツールキットでは、以前にセクション 1、2、3、4、6、7 を作成しましたが、セクション 5 の内容が省略されていたため、今回追加しました。
セクション5の内容は検証に関するものですが、前回見たときはまだ初心者だったので内容がよくわからず、公式ドキュメントをざっと読んだのですが、ナツメを丸呑みするような感じでした。
今回勉強しに来たのは、機能を実装する必要があったのと、この部分の検証に関する内容をチラッと見て、やはりちょっと感動したので、直接ブログを書いて勉強することにしました。体系的に。
私が実現したい機能は非常に一般的なもので、フロントエンドのエラー入力をフィルタリングして除去し、フィードバックを与えることです。
早速、本題に入りましょう。
二、ObservableValidator
このセクションのタイトル - ObservableValidator (監視可能なバリデータ)、この単語は一目でわかります。ObservableObject に関連しており、プロパティ値の更新を監視でき、検証に関連しています。
ObservableValidator は、 INotifyDataErrorInfoインターフェイスを実装する基本クラスで、他のプログラム モジュールに公開されるプロパティの検証のサポートを提供します。また、 ObservableObjectから継承するため、 INotifyPropertyChangedとINotifyPropertyChangingも実装します。これは、プロパティ変更通知とプロパティ検証の両方をサポートする必要があるすべてのタイプの開始点として使用できます。
これは ObservableObject を継承しており、これは自動通知クラスであることを意味します。INotifyDataErrorInfo
インターフェイスを実装しました。ここでのインターフェイスはコード レベルのインターフェイスであり、上位レベルのモジュール間のインターフェイスではありません。コード上のインターフェイスは、それを継承するクラスがこのインターフェイスを実装する必要があることを意味します。つまり、これらのクラスがこの機能を持つ必要があることを意味します (ただし、実装は異なります)。このインターフェースは名前からもわかるように、データのエラー情報を通知するために使用されており、フロントエンド入力の検証との整合性が高いことがわかります。つまり、クラスがObservableValidatorを継承する場合、そのクラスには検証する必要があるプロパティがいくつかあることを意味します。
2.1. 仕組みは?
ObservableValidatorには次の主な機能があります。
- INotifyDataErrorInfoの基本実装を提供し、ErrorsChangedイベントおよびその他の必要な API を公開します。
- 一連の追加のSetPropertyオーバーロードが (基本のObservableObjectクラスによって提供されるオーバーロードに加えて)提供され、プロパティを自動的に検証し、値を更新する前に必要なイベントを発生させる機能を提供します。
- SetPropertyに似た多数のTrySetPropertyオーバーロードを公開しますが、検証が成功した場合にのみターゲット プロパティを更新し、生成されたエラー (存在する場合) をさらなる検査のために返します。
- また、ValidatePropertyメソッドも公開します。これは、特定のプロパティの値が更新されていない場合にその検証を手動でトリガーするのに役立ちますが、その検証は更新される別のプロパティの値に依存します。
- ValidateAllPropertiesメソッドを公開します。このメソッドは、少なくとも 1 つの [ValidationAttribute] がある場合に、現在のインスタンス内のすべてのパブリック インスタンス プロパティの検証を自動的に実行します。
- ユーザーが再度入力する必要があるフォームにバインドされたモデルをリセットするときに役立つClearAllErrorsメソッドを公開します。
- プロパティの検証に使用されるValidationContextインスタンスを初期化するためにさまざまなパラメーターを渡すことを可能にする多数のコンストラクターも提供されています。これは、適切に動作するために追加のサービスやオプションが必要なカスタム検証属性を使用する場合に特に便利です。
2.2. 単純な属性
単純なプロパティは単一のプロパティであり、複合ではありません。
変更通知と検証の両方をサポートするプロパティを実装する方法の例を次に示します。
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 コントロールなど) はビューモデルと対話し、ErrorsChanged に登録し、GetErrors(string) メソッドを使用して、以前に作成された各プロパティのエラーを取得することで、ビューモデルに現在存在するエラーを反映するように状態を変更できます。変更されたリスト。
2.3. カスタム認証方法
プロパティを検証するには、ビューモデルが他のサービス、データ、または 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");
}
}
この例には、ビューモデルに挿入されたサービス (「依存関係の挿入」の章で説明) を介してNameプロパティの検証を実行する静的なValidateNameメソッドがあります。このメソッドは、name属性値と使用中のValidationContextインスタンスをパラメーターとして受け取ります。これには、ビューモデル インスタンス、検証されるプロパティの名前、オプションのサービス プロバイダー、および使用または設定するいくつかのカスタム フラグが含まれます。この例では、検証コンテキストからRegistrationFormインスタンスを取得し、そこから挿入されたサービスを使用してプロパティを検証します。この検証は他の属性で指定された検証の後に実行されるため、カスタム検証メソッドと既存の検証属性を自由に組み合わせることができます。
上記のコード例では、Name 属性に [] 属性の文字列があり、1 つずつ検証できます。
2.4. カスタム検証属性
検証をカスタマイズするもう 1 つの方法は、カスタム[ValidationAttribute]を実装し、書き換えられたIsValidメソッドに検証ロジックを挿入することです。これにより、複数の場所で同じ属性を簡単に再利用できるため、上記のアプローチと比較してさらなる柔軟性が得られます。
同じビューモデル内の別のプロパティに対する相対値に基づいてプロパティを検証したいとします。まず、次のようにカスタム[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");
}
}
これで、この属性をビューモデルに追加できます。
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 の Setter にValidatePropertyへの呼び出しを追加しました。これにより、B が変更されるたびに a が再度検証されるようになります (検証は b に依存するため)。このカスタム検証を開始するには、ビューモデル内のこれら 2 行のコードのみが必要です。また、アプリケーションの他のビューモデルでも役立つ再利用可能なカスタム検証属性の利点も得られます。このアプローチは、検証ロジックがビューモデル定義自体から完全に切り離されるため、コードのモジュール化にも役立ちます。
3.終了
バリデータのコードは複雑ではなく、アプリケーション的な内容となっており、上記の例をそのままコピーして改変することが可能であり、 ObservableObject の拡張に相当します。