ObservableValidator
ObservableValidator
是實作 介面的INotifyDataErrorInfo
基類,可支援驗證公開給其他應用程式模組的屬性。 它也會繼承自 ObservableObject
,因此也會實作 INotifyPropertyChanged
和 INotifyPropertyChanging
。 它可以當做需要支援屬性變更通知和屬性驗證之各種物件的起點。
運作方式
ObservableValidator
具有下列主要功能:
- 它提供的
INotifyDataErrorInfo
基底實作,以ErrorsChanged
公開事件和其他必要的 API。 - 它提供一系列額外的
SetProperty
多載(在基ObservableObject
類所提供的多載之上),可提供自動驗證屬性並引發必要事件的能力,再更新其值。 - 它會公開許多
TrySetProperty
多載,這與 類似SetProperty
,但只有在驗證成功時,才能夠更新目標屬性,並傳回產生的錯誤(如果有的話),以進行進一步檢查。 - 它會公開
ValidateProperty
方法,如果尚未更新特定屬性的值,但驗證會相依於已更新的另一個屬性值,則手動觸發特定屬性的驗證會很有用。 - 它會公開
ValidateAllProperties
方法,這個方法會自動執行目前實例中所有公用實例屬性的驗證,前提是它們已套用至少一個[ValidationAttribute]
。 - 它會公開
ClearAllErrors
方法,在重設系結至使用者可能想要再次填入之表單的模型時很有用。 - 它提供一些建構函式,允許傳遞不同的參數來初始化將用來驗證屬性的
ValidationContext
實例。 使用可能需要其他服務或選項才能正常運作的自定義驗證屬性時,這特別有用。
Simple 屬性
以下範例說明如何實作支援變更通知和驗證的屬性:
public class RegistrationForm : ObservableValidator
{
private string name;
[Required]
[MinLength(2)]
[MaxLength(100)]
public string Name
{
get => name;
set => SetProperty(ref name, value, true);
}
}
我們在這裡呼叫 SetProperty<T>(ref T, T, bool, string)
所 ObservableValidator
公開的方法,而且設定為 的其他 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");
}
}
在此情況下,我們有一個靜態 ValidateName
方法,會透過插入至 viewmodel 的服務,對 Name
屬性執行驗證。 這個方法會 name
接收使用中的屬性值和 ValidationContext
實例,其中包含 viewmodel 實例、正在驗證的屬性名稱,以及選擇性地使用或設定服務提供者和一些自定義旗標。 在此情況下,我們會從驗證內容擷取 RegistrationForm
實例,而從該處,我們會使用插入的服務來驗證 屬性。 請注意,此驗證會在其他屬性中指定的驗證旁邊執行,因此我們可以隨意結合自定義驗證方法和現有的驗證屬性,但我們想要。
自訂驗證屬性
執行自定義驗證的另一種方式是實作自定義 [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));
}
}
}
在此情況下,我們有兩個數值屬性必須位於特定範圍中,而且彼此之間具有特定關聯性(A
必須大於 B
)。 我們在第一個屬性上新增了新的 [GreaterThanAttribute]
,我們也在 setter B
中新增了 ValidateProperty
對 的呼叫,以便在A
每當B
變更時再次進行驗證(因為其驗證狀態相依於它)。 我們只需要 ViewModel 中的這兩行程式代碼來啟用此自定義驗證,我們也會獲得可重複使用的自定義驗證屬性,在應用程式中的其他 ViewModel 中也很有用。 此方法也有助於程式代碼模組化,因為驗證邏輯現在與 viewModel 定義本身完全分離。