Neuigkeiten in der .NET-Klassenbibliothek, Teil 7: Neue Datenannotationen

Der Dotnet-Doktor  –  0 Kommentare

Die Version 4.5 der .NET-Framework-Klassenbibliothek enthält 947 neue Klassen. Eine Artikelserie stellt zehn Neuerungen vor, die nicht die großen Bibliotheken betreffen und daher nicht so im Licht der Öffentlichkeit stehen. Nachdem zuletzt asynchrone Methoden in ADO.NET vorgestellt wurden, geht es heute in Teil 7 um neue Datenannotationen.

Der Namensraum System.ComponentModel.DataAnnotations wurde in .NET 3.5 Service Pack 1 eingeführt. Er enthält .NET-Attribute, mit denen man .NET-Datenklassen und .NET-Geschäftsobjektklassen bzw. deren Eigenschaften auszeichnen kann, damit diese der Benutzeroberfläche Metainformationen geben über die Form der Darstellung oder die Validierung der Eingaben. Die Datenannotationen wurden zuerst in ASP.NET Dynamic Data verwendet, dann auch in WCF RIA Service und ASP.NET Model Binding sowie dem ADO.NET Entity Framework.

Ursprünglich gab es Datenannotationen wie [Required], [Range], [StringLength] und [RegularExpression]. In .NET 4.5 sind einige Datenannotationen hinzugekommen, siehe Tabelle. Die Klassen zur Mapping-Festlegung im Code-First-Modell des ADO.NET Entity Framework sind im Unternamensraum System.ComponentModel.DataAnnotations.Schema und wurden schon mit dem Add-on ADO.NET Entity Framework 4.1 eingeführt.

Annotations-Klasse Bedeutung
UrlAttribute Validierung, ob Wert eine URL ist
PhoneAttribute Validierung, ob Wert eine Telefonnummer ist
MinLengthAttribute Validierung, ob Wert eine Mindestlänge erfüllt
MaxLengthAttribute Validierung, ob Wert eine Maximallänge erfüllt
FileExtensionsAttribute Validierung, ob Wert ein Dateiname mit bestimmten Dateinamenserweiterung ist
EmailAddressAttribute Validierung, ob Wert eine E-Mail-Adresse ist
CreditCardAttribute Validierung, ob Wert eine Kreditkartennummer ist
CompareAttribute Validierung, ob ein Wert einem anderen entspricht

Datenannotationen kann man in beliebigen .NET-Klassen vergeben und dann die Überprüfung (Validierung) der Instanzen per Programmcode anstoßen. Das folgende Listing zeigt eine mit reichlich Datenannotationen versehene .NET-Klasse für einen Kunden. Dabei kommt neben den in .NET 4.5 vordefinierten Datenannotationen auch die in der letzten Ausgabe vorgestellte selbstdefinierte Validierung [Choice] zum Einsatz.

/// <summary>
/// Eine mit validierenden Datenannotationen versehende .NET-Klasse
/// </summary>
class Kunde
{
[Required]
[MinLength(4)]
[RegularExpression("K-[0-9]*", ErrorMessage = "Kundennummer
muss so aufgebaut sein: K-00000")]
public string KundenID { get; set; }

[MaxLength(30)]
public string Firmenname { get; set; }

[Required]
[MinLength(1)]
[MaxLength(20)]
public string Vorname { get; set; }

[Required]
[MinLength(2)]
[MaxLength(30)]
public string Nachname { get; set; }

[EmailAddress]
public string EMail { get; set; }

[Phone]
public string Telefon { get; set; }

[CreditCard]
public string Kreditkarte { get; set; }

[Url]
public string Website { get; set; }

[Range(0, 99999999999999999)]
public double Umsatz { get; set; }

[Compare("Kennwort2")]
public string Kennwort1 { get; set; }
public string Kennwort2 { get; set; }

Das nächste Listing zeigt das Anlegen einer mit einigen Fehlern behafteten Instanz der Klasse Kunde: Die Kundennummer ist falsch aufgebaut. Der Vorname fehlt. Der Umsatz darf nicht mit 0 angegeben werden und die beiden erfassten Kennwörter stimmen nicht überein.

public static void Run()
{
var k = new Kunde();
k.Firmenname = "www.IT-Visions.de";
k.Vorname = "";
k.Nachname = "Schwichtenberg";
k.KundenID = "K1111";
k.Telefon = "+49 (201) 7490-700";
k.Website = "http://www.IT-Visions.de";
k.Umsatz = -1;
k.Kreditkarte = "4111 1111 1111 1111 "; // Visa-Beispielnummer
k.Kennwort1 = "geheim";
k.Kennwort2 = "geheimer";

Console.WriteLine("Kundendaten erfasst...");

// überprüfung eines Kunden
Console.WriteLine("Kunde wird nun überprüft:");

var validationResults = Validate(k);

if (validationResults.Count > 0)
{
PrintValErrors(validationResults);
}
else
{
Console.WriteLine("Kunde ist OK!");
}
}


/// <summary>
/// Validierung eines beliebigen annotierten .NET-Objekts
/// </summary>
private static List<ValidationResult> Validate(object obj)
{
var validationContext = new ValidationContext(obj);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(obj, validationContext,
validationResults, true);
return validationResults;
}

/// <summary>
/// Ausgabe einer Liste von Validierungsfehlern
/// </summary>
public static void PrintValErrors(List<ValidationResult> veList)
{
Console.ForegroundColor = ConsoleColor.Red;
foreach (var ve in veList)
{
Console.WriteLine("########### Fehler: " + ve.ErrorMessage);
foreach (var m in ve.MemberNames)
{
Console.WriteLine(" bei Eigenschaft " + m);
}
}
Console.ForegroundColor = ConsoleColor.Gray;
}

Um die Überprüfung dieses Objekts anzustoßen ist nicht viel notwendig, denn das .NET Framework liefert seit Version 4.0 mit den Klassen ValidationContext und Validator (beide im Namensraum System.ComponentModel.DataAnnotations). Für ein zu prüfendes Objekt erzeugt man zunächst einen ValidationContext und ruft dann eine der statischen Validate-Methoden der Klasse Validator auf. Während ValidateObject() beim ersten Fehler mit einer ValidationException aussteigt, liefert TryValidateObject() als Rückgabewert einen bool-Wert und als Ausgabeparameter eine Liste von Fehlern als List<ValidationResult>.

Das zweite Listing zeigt ebenso die Ausgabe der Fehler. Die Klasse ValidationResult besitzt zwei Eigenschaften: Eine Fehlermeldung und eine Liste von Eigenschaften der Klasse, denen diese zugeordnet ist. Man hätte es durchaus andersherum erwarten können. Mit einem LINQ-Befehl kann man die Liste aber auch nach Eigenschaften gruppieren:

var FehlerGruppierNachProperty = from ve in veList group 
ve by ve.MemberNames into m select new { Member = m.Key,
Errors = m };