Freigeben über


ObservableValidator

Das ObservableValidator ist eine Basisklasse, welche die INotifyDataErrorInfo-Schnittstelle implementiert und Support für die Überprüfung von Eigenschaften bereitstellt, die anderen Anwendungsmodulen zur Verfügung stehen. Sie erbt auch von ObservableObject, also implementiert INotifyPropertyChanged und INotifyPropertyChanging ebenfalls. Es kann als Ausgangspunkt für alle Arten von Objekten verwendet werden, die sowohl Benachrichtigungen über Eigenschaftsänderungen als auch die Validierung von Eigenschaften unterstützen müssen.

Plattform-APIs: ObservableValidator, ObservableObject

Funktionsweise

ObservableValidator hat die folgenden Hauptfunktionen:

  • Es bietet eine Basisimplementierung für INotifyDataErrorInfo, die das ErrorsChanged-Ereignis und die anderen erforderlichen APIs verfügbar macht.
  • Sie bietet eine Reihe zusätzlicher SetProperty-Überladungen (zusätzlich zu den von der ObservableObject-Basisklasse bereitgestellten), welche die Möglichkeit bieten, Eigenschaften automatisch zu validieren und die erforderlichen Ereignisse auszulösen, bevor ihre Werte aktualisiert werden.
  • Es stellt eine Reihe von TrySetProperty-Überladungen zur Verfügung, die SetProperty ähnlich sind, aber mit der Fähigkeit, die Zieleigenschaft nur zu aktualisieren, wenn die Validierung erfolgreich ist, und die generierten Fehler (falls vorhanden) zur weiteren Überprüfung zurückzugeben.
  • Es macht die ValidateProperty-Methode verfügbar, die nützlich sein kann, um die Überprüfung einer bestimmten Eigenschaft manuell auszulösen, falls der Wert nicht aktualisiert wurde, aber seine Überprüfung hängt vom Wert einer anderen Eigenschaft ab, die stattdessen aktualisiert wurde.
  • Es stellt die ValidateAllProperties-Methode zur Verfügung, die automatisch die Validierung aller öffentlichen Instanzeigenschaften in der aktuellen Instanz durchführt, sofern mindestens eine [ValidationAttribute] auf sie angewendet wird.
  • Es macht eine ClearAllErrors-Methode verfügbar, die nützlich sein kann, wenn ein Modell zurückgesetzt wird, das an ein bestimmtes Formular gebunden ist, welches der Benutzer möglicherweise erneut ausfüllen möchte.
  • Es bietet eine Reihe von Konstruktoren, mit denen verschiedene Parameter übergeben werden können, um die ValidationContext-Instanz zu initialisieren, die zum Überprüfen von Eigenschaften verwendet wird. Dies kann besonders hilfreich sein, wenn benutzerdefinierte Überprüfungsattribute verwendet werden, die möglicherweise zusätzliche Dienste oder Optionen benötigen, um ordnungsgemäß zu funktionieren.

Einfache Eigenschaft

Nachfolgend finden Sie ein Beispiel für die Implementierung einer Eigenschaft, die sowohl Änderungsbenachrichtigungen als auch Validierung unterstützt:

public class RegistrationForm : ObservableValidator
{
    private string name;

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

Hier rufen wir die SetProperty<T>(ref T, T, bool, string)-Methode auf, die von ObservableValidator exponiert wird, und der zusätzliche bool-Parameter, der auf true gesetzt ist, zeigt an, dass wir die Eigenschaft auch validieren möchten, wenn ihr Wert aktualisiert wird. ObservableValidator führt die Überprüfung für jeden neuen Wert automatisch mit allen Prüfungen aus, die mit den auf die Eigenschaft angewendeten Attributen angegeben sind. Andere Komponenten (z. B. UI-Steuerelemente) können dann mit dem Ansichtsmodell interagieren und ihren Status ändern, um die aktuell im Ansichtsmodell vorhandenen Fehler widerzuspiegeln, indem sie sich bei der ErrorsChanged-Methode GetErrors(string) registrieren und diese verwenden, um die Liste der Fehler für jede Eigenschaft, die geändert wurde, abzurufen.

Benutzerdefinierte Überprüfungsmethoden

Für die Überprüfung einer Eigenschaft ist manchmal ein Ansichtsmodell erforderlich, um Zugriff auf zusätzliche Dienste, Daten oder andere APIs zu haben. Es gibt verschiedene Möglichkeiten, eine benutzerdefinierte Überprüfung zu einer Eigenschaft hinzuzufügen, je nach Szenario und Maß an Flexibilität, die erforderlich ist. Hier ist ein Beispiel dafür, wie der [CustomValidationAttribute]-Typ verwendet werden kann, um anzugeben, dass eine bestimmte Methode aufgerufen werden muss, um eine zusätzliche Überprüfung einer Eigenschaft durchzuführen:

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

In diesem Fall haben wir eine statische ValidateName-Methode, die eine Überprüfung der Name-Eigenschaft über einen Dienst durchführt, der in unser Ansichtsmodell eingefügt wird. Diese Methode empfängt den name-Eigenschaftswert und die verwendete Instanz ValidationContext, die Elemente wie die Ansichtsmodellinstanz, den Namen der überprüften Eigenschaft und optional einen Dienstanbieter und einige benutzerdefinierte Flags enthält, die wir verwenden oder festlegen können. In diesem Fall rufen wir die RegistrationForm-Instanz aus dem Validierungskontext ab und verwenden von dort aus den injizierten Dienst zur Validierung der Eigenschaft. Beachten Sie, dass diese Validierung neben den in den anderen Attributen angegebenen Validierungen ausgeführt wird. Wir können also benutzerdefinierte Validierungsmethoden und vorhandene Validierungsattribute nach Belieben kombinieren.

Benutzerdefinierte Validierungsattribute

Eine andere Möglichkeit der benutzerdefinierten Validierung besteht darin, eine benutzerdefinierte [ValidationAttribute]-Methode zu implementieren und dann die Validierungslogik in die überschriebene IsValid-Methode einzufügen. Dies ermöglicht eine zusätzliche Flexibilität im Vergleich zu dem oben beschriebenen Ansatz, da es sehr einfach ist, dasselbe Attribut an mehreren Stellen einfach wiederzuverwenden.

Angenommen, wir möchten eine Eigenschaft auf der Grundlage ihres relativen Werts in Bezug auf eine andere Eigenschaft im selben Ansichtsmodell überprüfen. Der erste Schritt wäre das Definieren eines benutzerdefinierten [GreaterThanAttribute], wie folgt:

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

Als Nächstes können wir dieses Attribut zu unserem Ansichtsmodell hinzufügen:

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

In diesem Fall haben wir zwei numerische Eigenschaften, die sich in einem bestimmten Bereich befinden müssen und mit einer bestimmten Beziehung zueinander (A muss größer sein als B). Wir haben die neue [GreaterThanAttribute] über die erste Eigenschaft hinzugefügt und wir haben auch einen Aufruf ValidateProperty an den Setter für B hinzugefügt, sodass das A erneut überprüft wird, wenn B-Änderungen vorgenommen werden (da der Überprüfungsstatus davon abhängt). Wir benötigen nur diese beiden Codezeilen in unserem Ansichtsmodell, um diese benutzerdefinierte Überprüfung zu aktivieren, und wir profitieren auch von einem wiederverwendbaren benutzerdefinierten Validierungsattribut, das auch in anderen Ansichtsmodellen in unserer Anwendung nützlich sein könnte. Dieser Ansatz hilft auch bei der Modularisierung des Codes, da die Validierungslogik nun vollständig von der eigentlichen Definition des Ansichtsmodells entkoppelt ist.

Beispiele

  • Sehen Sie sich die Beispiel-App (für mehrere Benutzeroberflächen-Frameworks) an, um das MVVM-Toolkit in Aktion zu sehen.
  • Weitere Beispiele finden Sie auch in den Komponententests.