ASP.NET MVC 2: Model Validierung

Dies ist der zweite Teil aus der Serie Neuerungen bei ASP.NET MVC 2. Ich möchte hier die Verbesserungen der Validierung vorstellen.

ASP.NET MVC 2 Validierung

Validierung der Benutzereingaben und das Durchsetzen der Geschäftslogik gehören zu den Grundanforderungen der meisten Webapplikationen.
ASP.NET MVC 2 beinhaltet neue Möglichkeiten, welche die Validierung der Benutzereingaben und das Durchsetzen der Geschäftslogik auf Basis von Models bzw. ViewModels stark vereinfachen.
Diese Feature wurden so entwickelt das die Validierungslogik immer auf dem Server erzwungen wird und optional auf dem Client via JavaScript geprüft werden kann.
Die Validierungsinfrastruktur in ASP.NET MVC 2 wurde entwickelt damit:

  1. Entwickler das im .NET Framework integrierte Feature Datenanmerkungen (DataAnnotation) benutzen können, um Klassen zu validieren.
    (Datenanmerkungen schaffen einen einfachen Weg um deklarativ Validierungslogik an Objekte und Eigenschaften anzubinden)
  2. Entwickler optional eigenen Validierungsmechanismen integrieren können oder Frameworks, wie z.B. Castle Validator oder die Enterprise Library (Validation Application Block) einbinden.

 

Dies bedeutet, dass für die meisten Szenarios das Einbinden von Validierungslogik sehr einfach geworden ist. Auch komplexe Validierungslogik kann einfach und flexibel integriert werden kann.

Validierung mit Datenanmerkungen

An folgendem Beispiel möchte ich Ihnen die neuen Möglichkeiten der Validierung mit Datenanmerkungen aufzeigen.
Ich gehe hierbei von einem einfachen CRUD Szenario aus, welches ein Formular für die Eingabe von Freunden enthält:

Ich möchte sicher stellen, dass die Daten geprüft werden, bevor sie in die Datenbank gelangen. Außerdem soll der Benutzer bei falschen Eingaben Fehlermeldungen erhalten.

Die Prüfung der Daten soll Server- sowie Client-Seitig geschehen.
Außerdem möchte ich die Validerungslogik nur einmal implementieren müssen (Nicht für den Server und für den Client).

Schritt 1: Erzeugen der "FreundeController" Controller Klasse (vorerst ohne Validierung)

Als erstes erzeuge ich eine Klasse für die Person:

public class Person
{
  public string Vorname { get; set; }
  public string Nachname { get; set; }
  public int Alter { get; set; }
  public string EMail { get; set; }
}

 

Als nächstes erzeuge ich die "FreundeController" Klasse, welche zwei "Create" Aktionen enthält.
Die erste Aktion wird bei einer HTTP-GET Anfrage für die /Freunde/Create URL aufgerufen. Diese zeigt ein leeres Formular an.
Die zweite Aktion wird bei einer HTTP-PUT Anfrage für die /Freunde/Create URL aufgerufen. Diese verbindet das übermittelte Formular mit dem Personen Objekt, prüft nach Fehlern und bei Erfolgreicher Prüfung werden die Daten in die Datenbank geschrieben (Die Datenbankzugriffe werden später in diesem Beispiel hinzugefügt). Falls die Datenprüfung fehl schlägt, wird das Formular mit Fehlermeldungen angezeigt.

public class FreundeController : Controller
{
  //
  // GET: /Freunde/Create

  public ActionResult Create()
  {
    Person neuerFreund = new Person();
    return View(neuerFreund);
  } 

  //
  // POST: /Freunde/Create

  [HttpPost]
  public ActionResult Create(Person freundZumSpeichern)
  {
    if (ModelState.IsValid)
    {
      // TODO: Wenn das freundZumSpeichern Object "Valid" ist,
      // dann wird es in der Datenbank gespeichert.

      // Nach dem Speichern wird der Benutzer auf die 
      // Startseite umgeleitet
      return Redirect("/");
    }

    return View(freundZumSpeichern);
  }
}

 

Anschliessen öffne ich über das Kontextmenü den "Add View" Dialog (Rechter-Mausklick in eine der beiden Aktionsmethoden und dann den Menüpunkt "Add View" klicken).
Dort wähle ich im Bereich "Create a strong-typed view" die "Person" Klasse aus.

Visual Studio erzeugt nun das Grundgerüst für das Formular (Create.aspx) im ViewsFreunde Verzeichnis des Projektes. Neu in ASP.NET MVC 2 sind die Stark Typisierte HTML Hilfsmethoden die hierbei verwendet werden.

Wenn jetzt die Applikation gestartet und auf die /Freunde/Create URL navigiert wird erscheint folgendes Formular:

Da noch keine Validierungslogik in die Applikation implementiert wurde, hält derzeit nichts die Benutzer ab unsaubere Daten in das Formular einzugeben und an den Server zu schicken.

Schritt 2: Validierungen mit Hilfe von Datenanmerkungen

Als nächsten werde ich die Applikation mit ein paar grundlegenden Validierungsregeln erweitern.
Diese Regeln werden direkt auf der "Person" Model-Klasse angewendet und nicht im Controller oder der View. Vorteil dieser Vorgehenesweise (Logik an einer zentralen Stelle) ist, dass alle Elemente der Applikation diese Regeln befolgen werden (Auch wenn die Applikation nachträglich verändert oder erweitert wird).
ASP.NET MVC 2 erlaubt dem Entwickler Model- oder ViewModel-Klassen mit Validierungsattribute zu dekorieren und somit deklarativ Regeln festzulegen. Diese werden jedesmal verwendet, wenn ASP.NET MVC 2 die Model-Klasse bindet.

Um die Datenanmerkungen verwenden zu können, muss ich als erstes den Namensraum "System.ComponentModel.DataAnnotations" in meine Klasse importieren (using Anweisung).
Anschliessend dekoriere ich die Eigenschaften der "Person" Klasse mit den Validierungsattributen [Required], [StringLength], [Range] und [RegularExpression]:

public class Person
{
  [Required(ErrorMessage="Bitte geben Sie einen Vorname an.")]
  [StringLength(50, 
    ErrorMessage="Der Vorname darf nicht länger als 50 Zeichen sein.")]
  public string Vorname { get; set; }

  [Required(ErrorMessage = "Bitte geben Sie einen Nachname an.")]
  [StringLength(50, 
    ErrorMessage = "Der Nachname darf nicht länger als 50 Zeichen sein.")]
  public string Nachname { get; set; }

  [Required(ErrorMessage = "Bitte geben Sie ein Alter an.")]
  [Range(1, 120, ErrorMessage="Das Alter muss zwischen 1 und 120 liegen.")]
  public int Alter { get; set; }

  [Required(ErrorMessage = "Bitte geben Sie eine EMail Adresse an.")]
  [RegularExpression(
    "^[a-z0-9_\+-]+(\.[a-z0-9_\+-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*\.([a-z]{2,4})$"
    , ErrorMessage = "Die EMail Adresse ist nicht gültig.")]
  public string EMail { get; set; }
}

 

Anmerkung: Im hier verwendeten Beispiel, habe ich die Fehlermeldungen explizit angegeben. Alternativ kann man mit der Hilfe von Resource-Dateien die Fehlermeldungen Lokalizieren. Wie das geht, kann man hier sehen. (In Englisch).

Die Validierungsregeln sind nun implementiert. Mal sehen was passiert, wenn ich nun die Applikation starte und Fehleingaben an den Server schicke.

Die Applikation weist jetzt eine grundlegende Fehlerbehandlung von Falscheingaben auf.
Die Eingabe Elemente werden in Rot hervorgehoben und Validierungsfehlermeldungen werden angezeigt.
Des weiteren werden fehlerhafte Daten nicht verarbeitet, sondern zur Korrektur erneut angezeigt.

Um diese Verhaltensweise besser verstehen zu können, werfen wir nochmal einen Blick in die HTTP-POST "Create" Aktionsmethode des Controllers:

//
// POST: /Freunde/Create

[HttpPost]
public ActionResult Create(Person freundZumSpeichern)
{
  if (ModelState.IsValid)
  {
    // TODO: Wenn das freundZumSpeichern Object "Valid" ist,
    // dann wird es in der Datenbank gespeichert.

    // Nach dem Speichern wird der Benutzer auf die 
    // Startseite umgeleitet
    return Redirect("/");
  }

  return View(freundZumSpeichern);
}

 

Wenn das HTML Formular an den Sever geschickt wird, wir die HTTP-POST "Create" Aktionsmethode des Controllers aufgerufen.
Da die Aktionsmethode ein "Person" Objekt als Parameter enthält, verbindet ASP.NET MVC automatisch die Formularwerte mit den Objekteigenschaften.
Als Teil dieses Prozesses werden die Datenanmerkungen validiert.
Wenn alles in Ordnung ist, gibt die Eigenschaft ModelState.IsValid ein true zurück. In diesem Fall könnte das "Person" Objekt gespeichert werden.
Wenn das Objekt Validierungsfehler enthält, werden diese durch die letzte Code-Zeile ( return View(freundZumSpeichern); ) an das Formular zurückgegeben.
Die Fehlermeldungen werden durch die <%= Html.ValidationMessageFor() %> Hilfsmethoden in der Create.aspx View angezeigt.

<h2>Neuer Freund</h2>

<% using (Html.BeginForm()) {%>

  <fieldset>
    <p>
      <%: Html.LabelFor(model => model.Vorname) %><br />
      <%: Html.TextBoxFor(model => model.Vorname) %>
      <%: Html.ValidationMessageFor(model => model.Vorname) %>
    </p>
    <p>
      <%: Html.LabelFor(model => model.Nachname) %><br />
      <%: Html.TextBoxFor(model => model.Nachname) %>
      <%: Html.ValidationMessageFor(model => model.Nachname) %>
    </p>
    <p>
      <%: Html.LabelFor(model => model.Alter) %><br />
      <%: Html.TextBoxFor(model => model.Alter) %>
      <%: Html.ValidationMessageFor(model => model.Alter) %>
    </p>
    <p>
      <%: Html.LabelFor(model => model.EMail) %><br />
      <%: Html.TextBoxFor(model => model.EMail) %>
      <%: Html.ValidationMessageFor(model => model.EMail) %>
    </p>
    <p>
      <input type="submit" value="Speichern" />
    </p>
  </fieldset>

<% } %>

 

Das Schöne an diesem Ansatz ist die Einfache Handhabung. Man kann auf einfache Art und Weise Validierungsregeln an Model-Klassen anfügen oder diese Ändern. Hierzu muss die Controller oder Views nicht angefasst werden.

Schritt 3: Client-Seitige Validierung

Die Applikation enthält nun eine Server-Seitige Validierung.
Das bedeutet, dass die Benutzer die Eingabefehler erst nach einem Roundtrip zum Server sehen können.

Um die Client-Seitige Validierung zu implementieren, müssen zwei JavaScript Referenzen und eine Zeile Code in die View hinzugefügt werden:

<h2>Neuer Freund</h2>

<script src="<%= Url.Content("~/Scripts/MicrosoftAjax.js") %>" 
  type="text/javascript"></script>
<script src="<%= Url.Content("~/Scripts/MicrosoftMvcValidation.js") %>" 
  type="text/javascript"></script>

<% Html.EnableClientValidation(); %>

<% using (Html.BeginForm()) {%>

  <fieldset>
    <p>
      <%: Html.LabelFor(model => model.Vorname) %><br />
      <%: Html.TextBoxFor(model => model.Vorname) %>
      <%: Html.ValidationMessageFor(model => model.Vorname) %>
    </p>
    <p>
      <%: Html.LabelFor(model => model.Nachname) %><br />
      <%: Html.TextBoxFor(model => model.Nachname) %>
      <%: Html.ValidationMessageFor(model => model.Nachname) %>
    </p>
    <p>
      <%: Html.LabelFor(model => model.Alter) %><br />
      <%: Html.TextBoxFor(model => model.Alter) %>
      <%: Html.ValidationMessageFor(model => model.Alter) %>
    </p>
    <p>
      <%: Html.LabelFor(model => model.EMail) %><br />
      <%: Html.TextBoxFor(model => model.EMail) %>
      <%: Html.ValidationMessageFor(model => model.EMail) %>
    </p>
    <p>
      <input type="submit" value="Speichern" />
    </p>
  </fieldset>

<% } %>

 

Nachdem diese drei Zeilen hinzugefügt wurden, erzeugt ASP.NET MVC 2 aus den Validierungs-Meta-Daten, die der "Person" Klasse hinzugefügt worden sind, Client-Seitiges JavaScript.
Somit werden dem Benutzer die Validierungsfehlermeldungen sofort (während seiner Eingabe) angezeigt.

Um die Änderungen zu testen, starte ich erneut die Applikation.
Jetzt werden die Fehlermeldungen sofort angezeigt, ohne das das Formular an den Server geschickt werden muss.

Wenn ich jetzt eine ungültige eMail Adresse eingebe, springt die Fehlermeldung direkt von "Email Required” zu “Not a valid email”: 

 Wenn ich anschliessend eine gültige eMail Adresse eingebe, verschwindet die Fehlermeldung und die TextBox wird nicht mehr hervorgehoben.

Fazit
Für die Client-Seitige Validierung mussten wir kein eigenes JavaScript schreiben, und unsere Validierungsregeln liegen weiterhin an einer zentralen Stelle (in der "Person" Model-Klasse) auf dem Server.

Anmerkung: Zur Sicherheit werden weiterhin die Validierungsregeln Server-Seitig überprüft, selbst wenn die Client-Seitigige Prüfung aktiviert wurde. Dies verhindert, dass Hacker die Client-Seitige Prüfung umgehen.

Die Client-Seitige Validierung funktioniert mit allen Validierungs-Framework, die von ASP.NET MVC 2 unterstützt werden.
In Zukunft soll auch eine jQuery Unterstützung für die Client-Seitige Validierung in ASP.NET MVC 2 mitgeliefert werden.

Schritt 4: Ein eigenes Validierungsattribut erzeugen (eMail Validierung)

Der System.ComponentModel.DataAnnotations Namesraum des .NET Frameworks enthält einige Validierungsattribute.
Im bisherigen Beispiel wurden folgene verwendet: [Required], [StringLength], [Range] und [RegularExpression].

Es ist außerdem möglich eigene Validierungsattribute zu erzeugen.
Zum Einen kann eine komplett eigene Logik auf Basis der ValidationAttribute Klasse angelegt werden.
Zum Anderen kann man bereits existierende Validierungsattribute erweitern.

Im folgenden Beispiel möchte ich ein eMail Validierungsattribut zu erzeugen.
Meine neues Validierungsattribut erbt von der RegularExpression Klasse und ruft dabei den Konstruktor der Basis Klasse auf, um den Regulären Ausdrück für die eMail Prüfung festzulegen.

using System;
using System.ComponentModel.DataAnnotations;

public class EMailAttribute : RegularExpressionAttribute
{
  public EMailAttribute()
    : base("^[a-z0-9_\+-]+(\.[a-z0-9_\+-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*\.([a-z]{2,4})$")
  { 
  }
}

 

Anschliessend kann ich das eMail Validierungsattribut in der "Person" Model-Klasse verwenden und somit das RegularExpression Validierungsattribut ersetzen.

public class Person
{
  [Required(ErrorMessage="Bitte geben Sie einen Vorname an.")]
  [StringLength(50, 
    ErrorMessage="Der Vorname darf nicht länger als 50 Zeichen sein.")]
  public string Vorname { get; set; }

  [Required(ErrorMessage = "Bitte geben Sie einen Nachname an.")]
  [StringLength(50, 
    ErrorMessage = "Der Nachname darf nicht länger als 50 Zeichen sein.")]
  public string Nachname { get; set; }

  [Required(ErrorMessage = "Bitte geben Sie ein Alter an.")]
  [Range(1, 120, ErrorMessage="Das Alter muss zwischen 1 und 120 liegen.")]
  public int Alter { get; set; }

  [Required(ErrorMessage = "Bitte geben Sie eine EMail Adresse an.")]
  [EMail(ErrorMessage = "Die EMail Adresse ist nicht gültig.")]
  public string EMail { get; set; }
}

 

Eigenen Validierungsattribute schaffen erneut eine Möglichkeit Validierungslogik zentral anzulegen. Diese wird sowohl Server- wie auch Client-Seitig verwendet.

Validierungsattribute können auch auf Klassenebene benutzt werden. Dies erlaubt Logik über mehrere Attribute anzuwenden.
Ein Beispiel hierfür ist in der mitgelieferten Beispielapplikation von ASP.NET MVC 2 zu finden (ASP.NET MVC 2 Web Project in Visual Studio 2010).
Sehen Sie sich hierfür die "PropertiesMustMatchAttribute" Validierungsattributsklasse an.

Schritt 5: Verbindung mit der Datenbank

Als Letztes muss noch die Logik implementiert werden, die das "Person" Objekt in der Datenbank speichert.

//
// POST: /Freunde/Create

[HttpPost]
public ActionResult Create(Person freundZumSpeichern)
{
  if (ModelState.IsValid)
  {
    // TODO: Wenn das freundZumSpeichern Object "Valid" ist,
    // dann wird es in der Datenbank gespeichert.

    // Nach dem Speichern wird der Benutzer auf die 
    // Startseite umgeleitet
    return Redirect("/");
  }

  return View(freundZumSpeichern);
}

 

Derzeit arbeiten wir mit einer einfache C# Klasse (manchmal wird dies auch als "POCO" Klasse "Plain Old CLR Object" bezeichnet).
Man könnte zum Einen seinen eigenen Code schreiben, der die Klasse in der Datenbank speichert.
Zum Anderen kann man einen O/R Mapper (Object/Relational Mapping), wie z.B. NHibernate, verwenden.

Visual Studio 2010 liefert mit dem Entity Framework 4 (EF) auch einen O/R Mapper mit, der nun auch POCO Klassen verarbeiten kann.
Hierzu muss als erstes ein ADO.NET Entity Model dem Projekt hinzugefügt werden:

Als nächstes hat man die Auswahl die EF-Model-Objekte aus bestehenden Tabellen bzw. Sichten einer Datenbank generieren zu lassen oder mit einem leeren Model zu starten.
Bei einem leeren Model kann man, nach dem manuellen Erstellen der Objekte, die zugehörigen Datenbankskripte erzeugen lassen.

Der nächste Screenshot zeigt die "Person" Klasse im ADO.NET EF Designer in Visual Studio 2010.
Der obere Fensterteil definiert die Person Klasse.
Der untere Fensterteil zeigt den Mapping Editor der die Eigenschaften der Klasse mit den Feldern der Datenbanktabelle verbindet.

Das EF generiert nun eine weitere "Person" Klasse, die mit der bereits bestehenden "Person" Model-Klasse kollidiert.
Um dieses Problem zu lösen, kann man mit einer Teil-Klasse die bestehende EF-Klasse erweitern.
Diese Teilklasse kann anschliessend mit einer MetaData-Klasse dekoriert werden.
Diese MetaData-Klasse enthält die Datenanmerkungen zur Validierung (In diesem Bespiel heißt die MetaData-Klasse "Person_Validierung").

[MetadataType(typeof(Person_Validierung))]
public partial class Person
  {
    // Teil-Klasse, welche die vom Entity Framework erzeuge Klasse erweitert.
  }

[Bind(Exclude="ID")]
public class Person_Validierung
{
  [Required(ErrorMessage="Bitte geben Sie einen Vorname an.")]
  [StringLength(50, 
    ErrorMessage="Der Vorname darf nicht länger als 50 Zeichen sein.")]
  public string Vorname { get; set; }

  [Required(ErrorMessage = "Bitte geben Sie einen Nachname an.")]
  [StringLength(50, 
    ErrorMessage = "Der Nachname darf nicht länger als 50 Zeichen sein.")]
  public string Nachname { get; set; }

  [Required(ErrorMessage = "Bitte geben Sie ein Alter an.")]
  [Range(1, 120, ErrorMessage="Das Alter muss zwischen 1 und 120 liegen.")]
  public int Alter { get; set; }

  [Required(ErrorMessage = "Bitte geben Sie eine EMail Adresse an.")]
  [EMail(ErrorMessage = "Die EMail Adresse ist nicht gültig.")]
  public string EMail { get; set; }
}

 

Zum Schluss muss die "FreundeController" Klasse nur noch um ein paar Zeilen erweitert werden.
Diese Zeilen speichern das Freund-Objekt in der Datenbank.

//
// POST: /Freunde/Create

[HttpPost]
public ActionResult Create(Person freundZumSpeichern)
{
  if (ModelState.IsValid)
  {
    // Den Freund in der Datenbank speichern
    using (FreundeDBEntities freunde = new FreundeDBEntities())
    {
      freunde.AddToPerson(freundZumSpeichern);
      freunde.SaveChanges();
    }

    // Nach dem Speichern wird der Benutzer auf die 
    // Startseite umgeleitet
    return Redirect("/");
  }

  return View(freundZumSpeichern);
}

 

Fazit

ASP.NET MVC 2 schafft durch die Verwendung von Datenanmerkungen einen einfach Weg Validierungslogik an einer zentralen Stelle im Quellcode zu implementieren.
Auch Client-Seitige Validierung kann mit wenigen Zeilen Code aktiviert werden. Hierbei wird ebenfalls die bereits definierte Validierungslogik angewendet.
Die Validierungsinfrastruktur von ASP.NET MVC 2 schafft einfache Erweiterungsmöglickeiten für das Anbinden von externen Validierungsframeworks.

[Orginal Post auf ScottGu's Blog: link]


Download
Download der Beispielanwendung:

 

 

Check Also

SQL Saturday #409 Rheinland – Slides und Demos

Vergangenen Samstag fand der dritte deutsche SQL Saturday statt. Wie auch im letzten Jahr konnte dafür als Austragungsort die Hochschule Bonn-Rhein-Sieg genutzt werden. Gemeinsam mit Alexander Karl durfte ich dort eine Session zum Thema "SQL Server vs. Azure DocumentDB – Ein Battle zwischen XML und JSON" halten. Die verwendeten Slides und Code Beispiele findet ihr in diesem Blog Post.

One comment

  1. Auf dieser Seite findet sich ein weiteres gutes Beispiel zur Umsetzung eines MVC2 Projektes: http://kevingerndt.blogspot.com/2011/04/webshop-beispielanwendung-mit-mvc2.html