ObservableValidator

ObservableValidator adalah kelas dasar yang mengimplementasikan INotifyDataErrorInfo antarmuka, memberikan dukungan untuk memvalidasi properti yang terekspos ke modul aplikasi lain. Ini juga mewarisi dari ObservableObject, sehingga mengimplementasikan INotifyPropertyChanged dan INotifyPropertyChanging juga. Ini dapat digunakan sebagai titik awal untuk semua jenis objek yang perlu mendukung pemberitahuan perubahan properti dan validasi properti.

API Platform:ObservableValidator, ObservableObject

Cara kerjanya

ObservableValidator memiliki fitur utama berikut:

  • Ini menyediakan implementasi dasar untuk INotifyDataErrorInfo, mengekspos ErrorsChanged peristiwa dan API lain yang diperlukan.
  • Ini menyediakan serangkaian kelebihan beban tambahan SetProperty (di atas yang disediakan oleh kelas dasar ObservableObject ), yang menawarkan kemampuan memvalidasi properti secara otomatis dan meningkatkan peristiwa yang diperlukan sebelum memperbarui nilainya.
  • Ini mengekspos sejumlah TrySetProperty kelebihan beban, yang mirip SetProperty dengan tetapi dengan kemampuan hanya memperbarui properti target jika validasi berhasil, dan untuk mengembalikan kesalahan yang dihasilkan (jika ada) untuk pemeriksaan lebih lanjut.
  • Ini mengekspos ValidateProperty metode , yang dapat berguna untuk memicu validasi properti tertentu secara manual jika nilainya belum diperbarui tetapi validasinya tergantung pada nilai properti lain yang telah diperbarui.
  • Ini mengekspos metode , yang secara otomatis menjalankan validasi semua properti instans publik dalam instans ValidateAllProperties saat ini, asalkan mereka memiliki setidaknya satu [ValidationAttribute] yang diterapkan padanya.
  • Ini mengekspos ClearAllErrors metode yang dapat berguna saat mengatur ulang model yang terikat ke beberapa formulir yang mungkin ingin diisi pengguna lagi.
  • Ini menawarkan sejumlah konstruktor yang memungkinkan melewati parameter yang berbeda untuk menginisialisasi ValidationContext instans yang akan digunakan untuk memvalidasi properti. Ini dapat sangat berguna saat menggunakan atribut validasi kustom yang mungkin memerlukan layanan atau opsi tambahan untuk bekerja dengan benar.

Properti sederhana

Berikut adalah contoh cara mengimplementasikan properti yang mendukung pemberitahuan perubahan serta validasi:

public class RegistrationForm : ObservableValidator
{
    private string name;

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

Di sini kita memanggil SetProperty<T>(ref T, T, bool, string) metode yang diekspos oleh ObservableValidator, dan parameter tambahan bool yang diatur untuk true menunjukkan bahwa kita juga ingin memvalidasi properti ketika nilainya diperbarui. ObservableValidator akan secara otomatis menjalankan validasi pada setiap nilai baru menggunakan semua pemeriksaan yang ditentukan dengan atribut yang diterapkan ke properti . Komponen lain (seperti kontrol UI) kemudian dapat berinteraksi dengan viewmodel dan memodifikasi statusnya untuk mencerminkan kesalahan yang saat ini ada di viewmodel, dengan mendaftar ErrorsChanged dan menggunakan GetErrors(string) metode untuk mengambil daftar kesalahan untuk setiap properti yang telah dimodifikasi.

Metode validasi kustom

Terkadang memvalidasi properti memerlukan viewmodel untuk memiliki akses ke layanan tambahan, data, atau API lainnya. Ada berbagai cara untuk menambahkan validasi kustom ke properti, tergantung pada skenario dan tingkat fleksibilitas yang diperlukan. Berikut adalah contoh bagaimana [CustomValidationAttribute] jenis dapat digunakan untuk menunjukkan bahwa metode tertentu perlu dipanggil untuk melakukan validasi tambahan properti:

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");
    }
}

Dalam hal ini kami memiliki metode statis ValidateName yang akan melakukan validasi pada Name properti melalui layanan yang disuntikkan ke viewmodel kami. Metode ini menerima nilai properti dan ValidationContext instans yang digunakan, yang berisi hal-hal seperti instans name viewmodel, nama properti yang sedang divalidasi, dan secara opsional penyedia layanan dan beberapa bendera kustom yang dapat kita gunakan atau atur. Dalam hal ini, kami mengambil RegistrationForm instans dari konteks validasi, dan dari sana kami menggunakan layanan yang disuntikkan untuk memvalidasi properti. Perhatikan bahwa validasi ini akan dijalankan di samping yang ditentukan dalam atribut lain, jadi kami bebas untuk menggabungkan metode validasi kustom dan atribut validasi yang ada sesuka kami.

Atribut validasi kustom

Cara lain untuk melakukan validasi kustom adalah dengan menerapkan kustom [ValidationAttribute] dan kemudian memasukkan logika validasi ke dalam metode yang ditimpa IsValid . Ini memungkinkan fleksibilitas ekstra dibandingkan dengan pendekatan yang dijelaskan di atas, karena membuatnya sangat mudah untuk hanya menggunakan kembali atribut yang sama di beberapa tempat.

Misalkan kami ingin memvalidasi properti berdasarkan nilai relatifnya sehubungan dengan properti lain dalam viewmodel yang sama. Langkah pertama adalah menentukan kustom [GreaterThanAttribute], seperti:

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");
    }
}

Selanjutnya kita dapat menambahkan atribut ini ke dalam viewmodel kita:

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));
        }
    }
}

Dalam hal ini, kita memiliki dua properti numerik yang harus dalam rentang tertentu dan dengan hubungan tertentu antara satu sama lain (A harus lebih besar dari B). Kami telah menambahkan yang baru [GreaterThanAttribute] melalui properti pertama, dan kami juga menambahkan panggilan ke ValidateProperty di setter untuk B, sehingga A divalidasi lagi setiap kali B berubah (karena status validasinya tergantung padanya). Kami hanya membutuhkan dua baris kode ini di viewmodel kami untuk mengaktifkan validasi kustom ini, dan kami juga mendapatkan manfaat dari memiliki atribut validasi kustom yang dapat digunakan kembali yang dapat berguna di viewmodel lain dalam aplikasi kami juga. Pendekatan ini juga membantu modularisasi kode, karena logika validasi sekarang sepenuhnya dipisahkan dari definisi viewmodel itu sendiri.

Contoh