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 peristiwa ErrorsChanged serta API lain yang diperlukan.
  • Ini menyediakan serangkaian overload tambahan SetProperty (selain yang disediakan oleh kelas dasar ObservableObject), yang memungkinkan validasi properti secara otomatis dan memicu event yang diperlukan sebelum nilainya diperbarui.
  • Metode ini menyediakan sejumlah overload TrySetProperty, yang mirip dengan SetProperty tetapi hanya dapat memperbarui properti target jika validasi berhasil, serta mengembalikan galat 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 menyediakan metode ValidateAllProperties, yang secara otomatis menjalankan validasi terhadap semua properti instans publik pada instans saat ini, asalkan masing-masing telah diterapkan setidaknya satu [ValidationAttribute].
  • Ini menyediakan metode ClearAllErrors yang dapat berguna saat mengatur ulang model yang terikat ke suatu formulir yang mungkin ingin diisi ulang oleh pengguna.
  • Menyediakan sejumlah konstruktor yang memungkinkan Anda meneruskan berbagai parameter untuk menginisialisasi instans ValidationContext 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 metode SetProperty<T>(ref T, T, bool, string) yang disediakan oleh ObservableValidator, dan parameter tambahan bool yang disetel ke true menandakan bahwa kita juga ingin memvalidasi properti saat 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 terdapat dalam viewmodel, dengan mendaftarkan diri ke ErrorsChanged dan menggunakan metode GetErrors(string) untuk mengambil daftar kesalahan pada 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 name dan instans ValidationContext yang sedang digunakan, yang berisi hal-hal seperti instans viewmodel, nama properti yang sedang divalidasi, dan secara opsional penyedia layanan serta beberapa flag kustom yang dapat kita gunakan atau atur. Dalam hal ini, instance RegistrationForm diperoleh dari konteks validasi, lalu dari sana layanan terinjeksi digunakan 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 mengimplementasikan [ValidationAttribute] kustom, lalu menyisipkan logika validasi ke dalam metode IsValid yang dioverride. 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

  • Lihat contoh aplikasi (untuk beberapa kerangka kerja UI) untuk melihat Toolkit MVVM beraksi.
  • Anda juga dapat menemukan lebih banyak contoh dalam uji unit.