DataAnnotations sind eigentlich eine nette Sache. Implementiert wurden sie für die RIA Services, können jedoch auch ohne verwendet werden. Die Klassen befinden sich im Namespace System.ComponentModel.DataAnnotations. Bereit gestellt werden folgende Klassen:

Validator Kann über ValidateProperty, ValidateObject etc. Eigenschaften und Objektinstanzen validieren.
ValidatorContext Beschreibt den Kontext, der für die Validierung Gültigkeit hat. Beispielsweise kann hier der Name des Members mitgegeben werden, der zu validieren ist.
ValidatorAttribute Davon abgeleitet existieren zahlreiche Klassen, welche über eine Eigenschaft IsValid verfügen und den neuen Wert gegen bestehende Werte, welche über den ValidatorContext geliefert werden prüft.
ValidationResult Liefert das Ergebnis zurück. Entweder ein leeres ValidationResult-Objekt (ValidationResult.Success) oder gefüllt mit einer ErrorMessage.
ValidationException Diese wird ausgelöst, wenn die Validierung fehl schlägt und kann in den Bindings per ValidatesOnExceptions abgefragt werden.

Der Vorteil dieser Variante ist, dass sämtliche Validierungslogik in eigenen Klassen implementiert und per Attribute eingebunden wird. Einfache Pflege, keine ellenlangen Validierungslogiken in den Settern sind die Vorteile.

Ohne RIA-Services sieht das dann in etwa so aus:

[Required(ErrorMessage="Name is Required")]

[StringLength(20, MinimumLength=5, ErrorMessage="Name must have between 5 to 20 characters")]

public string Name

{

    get { return name; }

    set 

    {

        Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = "Name" });

        name = value; 

    }

}

Und hier das XAML dazu:

<TextBox Style="{StaticResource SimpleTextBoxStyle}" 

         Text="{Binding Path=StartDate, 

                        Mode=TwoWay, 

                        ValidatesOnExceptions=True, 

                        NotifyOnValidationError=True}"/>

Problemfall

Die Validierung selbst muss nun nicht zwangsweise am Model statt finden, sondern kann auch im ViewModel geschehen, vor allem dann, wenn die gebundenen Werte nicht sofort in das Model durchgeschrieben werden. Hier kommt es in der Praxis nun dazu, dass Basisklassen von mehreren Ableitungen benötigte Eigenschaften etc. zur Verfügung stellen und die entsprechenden Attribute für die Validierung darauf gesetzt werden. Beispiel:

public class BudgetViewModel

{

    private decimal budgetLimit;

 

    [Required]

    public decimal BudgetLimit

    {

        get { return budgetLimit; }

        set

        {

            Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = "BudgetLimit" });

            budgetLimit = value;

        }

    }

}

 

public class SpecialBudgetViewModel : BudgetViewModel

{

    private string specialProperty;

 

    [Required]

    public string SpecialProperty

    {

        get { return specialProperty; }

        set

        {

            Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = "BudgetLimit" });

            budgetLimit = value;

        }

    }

}

Bei der Ausführung und Test der Validierung schlagen aber sämtliche – in der Basis gesetzte Attribute – nicht an. D.h. die Validierung verläuft immer positiv.

Nach einer kurzen Recherche und zumeist nur sehr einfachen Beispielen blieb nur mehr der Weg, per .NET Reflector die in Frage kommenden Klassen zu betrachten.

Der implementierte Validator verwendet nun eine interne Klasse ValidationAttributeStore, welche die zu validierenden Typen und die zugehörigen Attribute verwaltet. Die Methode GetTypeStoreItem liefert nun im Falle einer Validierung die gewünschten Informationen zu einem bestimmten Typ zurück. Werden diese nicht gefunden, wird ein entsprechender Eintrag für diesen Store erstellt. Dabei werden die benutzerdefinierten Attribute per

Type.GetCustomAttributes(bool inherit)

ausgelesen. Und genau hierin liegt das Problem. Die hauseigene Dokumentation sagt dazu folgendes:

This method ignores the inherit parameter for properties and events. To search the inheritance chain for attributes on properties and events, use the appropriate overloads of the Attribute.GetCustomAttributes method.

Fazit

Der Ansatz ist nett, funktioniert aber nur, wenn keine Basisklassen/Vererbung im Spiel sind/ist. Andernfalls muss man sich nach einer anderen Lösung umsehen und gegebenenfalls eine eigene Implementierung starten.

Über den Autor

Norbert Eder

Ich bin ein leidenschaftlicher Softwareentwickler und Fotograf. Mein Wissen und meine Gedanken teile ich nicht nur hier im Blog, sondern auch in Fachartikeln und Büchern.

4 Kommentare

  • Hallo Norbert,
    du schreibst so verheißungsvoll, dass die DataAnnotations auch ohne RIA verwendet werden können.
    Wie weit geht das, könnte ich das auch in einem WPF-SmartClient nutzen?
    Der Hinweis auf msdn.microsoft.com/de-de/library/cc679203(v=VS.100).aspx wirkt auf mich irgendwie abschreckend:
    [quote]Wenn das MVC-Datenmodell oder die partielle Klasse der Entität ein Feld enthält, das mit dem RequiredAttribute-Attribut kommentiert wird, aber die Seite nicht die Eigenschaft enthält, wird kein Fehler ausgelöst.Validierung tritt nur für Felder auf, die an den Server gesendet werden.[/quote]

    Wäre natürlich auch hochinteressant, zu wissen wie sich die ValidationAttributes mit IDataErrorInfo und in XAML definierten ValidationRules vertragen.

  • Naja, das Problem daran ist folgendes:
    Die Eigenschaften werden durch die Attribute und den Validator-Aufruf nur am "Server" validiert. Das bedeutet nun was:

    Gegeben sei eine Eigenschaft vom Typ Int32. Dargestellt wird dies in der View durch eine TextBox. Diese kann aber nun alphanumerische Werte aufnehmen. Durch das Binding auf einen Int32-Typ entsteht eine BindingException und der "fehlerhafte" Wert wird nicht nach hinten gegeben. Dadurch erhältst du auch keinen Fehler. Eine Abhilfe kann eine Attached-Property sein, welche die Informationen aus Validation.Errors an ein ViewModel bindet, wodurch etwaige Validierungsfehler der View auch im ViewModel verfügbar sind und der Benutzer nun entsprechend benachrichtigt werden kann.

    Ist es das, was du meinst?

  • Zusammen mit IDataErrorInfo und ValidationRules habe ich diese Variante noch nicht eingesetzt. Erstere wird definitiv durch die DataAnnotations abgedeckt (und zwar in – aus meiner Sicht – einer wesentlich besseren Form).

    Die ValidationRules führen zu einem Eintrag in Validation.Errors – dazu siehe meine obige Antwort.

  • Vielen Dank, dass du dir die Zeit genommen hast.
    Die Attached-Property find ich ich gut, notfalls muss ich meine Validierung in meiner ViewModel-Basisklasse noch etwas erweitern, aber das passt schon.
    Was mir noch fehlte war, wer die DataAnnotation-Attribute überhaupt auswertet, denn ich habe ja keine asp.net-Anwendung oder direkte Datenbankanbindung.
    Ich schätze wenn ich das hier noch dazunehme, hab ich alles was ich brauche (evtl. Link-Adresse manuell in Browser kopieren):
    babaandthepigman.spaces.live.com/blog/cns!4F1B7368284539E5!167.entry

    Viele Grüße,
    Simon