Extreme ASP.NET

Modellvalidierung und Metadaten in ASP.NET MVC 2

K. Scott Allen

Beispielcode herunterladen.

Eines der neuen Features, die ASP.NET MVC 2 hinzugefügt wurde, besteht in der Fähigkeit, sowohl auf dem Server als auch dem Client überprüfen. Sie müssen dem Framework lediglich einige Informationen zu den zu überprüfenden Daten geben, dann erledigt das Framework die Arbeit und kümmert sich um die Details.

Dieses Feature ist ein großer Segen für all diejenigen von uns, die benutzerdefinierten Validierungscode und benutzerdefinierte Modellbinder schrieben, um eine einfach Modellüberprüfung mit ASP.NET MVC 1.0 durchzuführen. In diesem Artikel wird die integrierte Validierungsunterstützung von ASP.NET MVC 2 behandelt.

Bevor ich die neuen Funktionen bespreche, werde ich mich jedoch noch einmal mit der alten Methodologie noch befassen. Die Validierungsfunktionen in ASP.NET WebForms haben mir viele Jahre gute Dienste geleistet. Ich denke, es sinnvoll, sie noch einmal zu besprechen, damit klar ist, was ein ideales Validierungsframework bietet.

Steuern der Validierung

Wenn Sie schon einmal mit ASP.NET WebForms gearbeitet haben, wissen Sie, dass es relativ einfach ist, einem WebForm Validierungslogik hinzuzufügen. Die Validierungsregeln werden mithilfe von Steuerelementen formuliert. Wenn Sie beispielsweise sicherstellen möchten, dass der Benutzer Text in eine TextBox-Steuerelement eingibt, dann fügen Sie einfach wie folgt ein RequiredFieldValidator-Steuerelement hinzu, das auf das TextBox-Steuerelement zeigt:

<form id="form1" runat="server"> <asp:TextBox runat="server" ID="_userName" /> <asp:RequiredFieldValidator runat="server" ControlToValidate="_userName" ErrorMessage="Please enter a username" /> <asp:Button runat="server" ID="_submit" Text="Submit" /> </form>

Das RequiredFieldValidator-Steuerelement kapselt sowohl clientseitige als auch serverseitige Logik, um sicherzustellen, dass der Benutzer einen Benutzernamen eingibt. Für die clientseitige Validierung gibt das Steuerelement JavaScript-Code in den Browser des Clients aus, und dieses Skript stellt sicher, dass der Benutzer alle Validierungsregeln einhält, bevor das Formular zum Server übertragen wird.

Denken Sie einmal darüber nach, was diese WebForm-Validierungssteuerelemente leisten, das ist unglaublich stark!

  • Die Validierungsregeln für eine Seite können an einer einzigen Stelle deklarativ ausgedrückt werden.
  • Durch die Validierung auf dem Client wird ein Roundtrip zum Server vermieden, wenn der Benutzer die Validierungsregeln nicht einhält.
  • Durch die Servervalidierung wird verhindert, dass böswillige Benutzer das Clientskript umgehen können.
  • Server- und Clientvalidierungslogik bleiben synchron, ohne zu einem Wartungsproblem zu werden.

In ASP.NET MVC können Sie diese Validierungsregeln allerdings nicht verwenden und gleichzeitig dem MVC-Entwurfsmuster treu bleiben. Glücklicherweise bietet Version 2 des Frameworks sogar noch etwas Besseres.

Steuerelemente und Modelle

Man kann sich WebForm-Steuerelemente, wie das TextBox-Steuerelement, als einfache Container für Benutzerdaten vorstellen. Sie können das Steuerelement mit einem Anfangswert füllen und den Wert anzeigen, und Sie können alle vom Benutzer eingegebenen oder bearbeiteten Werte abrufen, indem Sie das Steuerelement nach einem Postback inspizieren. Beim Einsatz des MVC-Entwurfsmusters übernimmt das M (Modell) die gleiche Rolle als Datencontainer. Sie füllen das Modell mit Informationen, die dem Benutzer übermittelt werden müssen, und das Modell gibt aktualisierte Werte der Anwendung zurück. Daher ist das Modell ideal für Definitionen von Validierungsregeln und Beschränkungen geeignet.

Es folgt ein Beispiel, das zum Lieferumfang gehört. Wenn Sie eine neue ASP.NET MVC 2-Anwendung erstellen, enthält das neue Projekt unter Anderem die Komponente AccountController. Sie ist für die Bearbeitung von neuen Benutzerregistrierungsanforderungen sowie für Anmelde- und Kennwortänderungsanforderungen zuständig. Für jede dieser Aktionen wird ein dediziertes Modellobjekt verwendet. Sie finden diese Modelle in der Datei AccountModels.cs im Ordner Models. Beispielsweise sieht die RegisterModel-Klasse ohne Validierungsregeln folgendermaßen aus:

public class RegisterModel { public string UserName { get; set; } public string Email { get; set; } public string Password { get; set; } public string ConfirmPassword { get; set; } }

Die Register-Aktion der AccountController-Komponente akzeptiert eine Instanz dieser RegisterModel-Klasse als Parameter:

[HttpPost] public ActionResult Register(RegisterModel model) { // ... }

Wenn die Methode gültig ist, leitet die Register-Aktion die Modellinformationen an einen Dienst weiter, der ein neues Benutzerkonto erstellen kann.

Das RegisterModel-Modell ist ein großartiges Beispiel für ein ansichtsspezifisches Modell oder kurz Ansichtmodell. Das ist kein Modell, das für die Interaktion mit einer bestimmten Datenbanktabelle, einem Webdienstaufruf oder Geschäftsobjekt konzipiert ist. Stattdessen ist es für die Interaktion mit einer bestimmten Ansicht vorgesehen (der Register.aspx-Ansicht, die in Abbildung 1 teilweise dargestellt ist). Jede Eigenschaft des Modells ist einem Eingabesteuerelement der Ansicht zugeordnet. Sie sollten Ansichtsmodelle verwenden, da sie viele Situationen in der MVC-Entwicklung erleichtern, einschließlich der Validierung.


Abbildung 1 Registrierungsdaten

Von Modellen und Metadaten

Wenn der Benutzer in der Register-Ansicht Kontodaten eingibt, stellt das MVC-Framework sicher, dass der Benutzer einen Benutzernamen (UserName) und eine E-Mail-Adresse (Email) eingibt. Das Framework stellt auch sicher, dass die für Password und ConfirmPassword eingegebenen Zeichenfolgen übereinstimmen und dass das Kennwort mindestens sechs Zeichen lang ist. Wie erledigt es das alles? Indem es die Metadaten, die der RegisterModel-Klasse angefügt wurden, untersucht und darauf reagiert. In Abbildung 2 ist die RegisterModel-Klasse zusammen mit ihren Validierungsattributen dargestellt.

Abbildung 2 Die RegisterModel-Klasse mit Validierungsattributen

[PropertiesMustMatch("Password", "ConfirmPassword", ErrorMessage = "The password and confirmation password do not match.")] public class RegisterModel { [Required] public string UserName { get; set; } [Required] public string Email { get; set; } [Required] [ValidatePasswordLength] public string Password { get; set; } [Required] public string ConfirmPassword { get; set; } }

Wenn der Benutzer die Register-Ansicht abschickt, versucht der Standardmodellbinder von ASP.NET MVC, eine neue Instanz der RegisterModel-Klasse zu erstellen, die als Parameter der Register-Aktion der AccountController-Komponente übergeben werden kann. Der Modellbinder ruft Informationen aus der aktuellen Anforderung ab, um das RegisterModel-Objekt damit zu füllen. Beispielsweise kann er automatisch den POST-Wert eines HTML-Eingabesteuerelement namens UserName ermitteln und diesen Wert der UserName-Eigenschaft des RegisterModel-Objekts zuweisen. Dieses Verhalten ist ASP.NET MVC seit Version 1.0 eigen, und daher wird es nicht neu für Sie sein, wenn Sie das Framework schon einmal verwendet haben.

Neu ist in Version 2 allerdings, dass der Standardmodellbinder einen Metadatenanbieter fragt, ob Metadaten für das RegisterModel-Objekt verfügbar sind. Ergebnis dieses Prozesses ist schließlich ein von ModelMetaData abgeleitetes Objekt, das einzig dazu dient, nicht nur die zum Modell gehörigen Validierungsregeln zu beschreiben, sondern auch Informationen zur Anzeige des Modells in einer Ansicht bereitzustellen. Brad Wilson, der Mitglied des ASP.NET-Teams ist, geht in einer Reihe von Blogbeiträgen ausführlich darauf ein, wie diese Modellmetadaten über Vorlagen die Anzeige eines Modells beeinflussen können. Sie finden den ersten Beitrag dieser Reihe unter der Adresse bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-1-introduction.html.

Sobald der Modellbinder dem Modell ein ModelMetaData-Objekt zugeordnet hat, kann er mithilfe der darin enthaltenen Validierungsmetadaten das Modellobjekt überprüfen. Standardmäßig verwendet ASP.NET MVC Metadaten aus DataAnnotations-Attribute wie [Required]. Natürlich lässt sich ASP.NET MVC austauschen und erweitern. Wenn Sie eine andere Quelle für Modellmetadaten entwickeln möchten, können Sie daher auch einen eigenen Metadatenanbieter implementieren. Ben Scheirman bietet in einem Artikel mit dem Titel "Customizing ASP.NET MVC 2—Metadata and Validation", der verfügbar ist unter dotnetslackers.com/articles/aspnet/customizing-asp-net-mvc-2-metadata-and-validation.aspx einige interessante Informationen zu diesem Thema.

Anmerkungen zu Daten

Wie wir später sehen werden, können Sie zwar eigene Validierungsattribute definieren, aber ich möchte kurz auf [Required] eingehen, das eines der Standardvalidierungsattribute ist, die in der System.ComponentModel.DataAnnotations-Assembly enthalten sind. Abbildung 3 enthält eine vollständige Liste der Validierungsattribute aus der DataAnnotations-Assembly.

Abbildung 3 Validierungsattribute aus der DataAnnotations-Assembly

Attribut Beschreibung
StringLength Definiert die im Datenfeld maximal zulässige Zeichenfolgenlänge.
Required Gibt an, dass ein Datenfeldwert erforderlich ist.
RegularExpression Legt fest, dass ein Datenfeldwert dem angegebenen regulären Ausdruck entsprechen muss.
Range Gibt die Grenzwerte des numerischen Bereichs an, in dem der Wert eines Datenfeld liegen muss.
DataType Legt den Namen eines zusätzlichen Typs fest, der einem Datenfeld zugeordnet werden soll (ein DataType-Aufzählungswert wie EmailAddress, Url oder Password).

Diese DataAnnotations-Attribute verbreiten sich schnell im Microsoft .NET Framework. Diese Attribute können nicht nur in ASP.NET MVC-Anwendungen verwendet werden, sondern ASP.NET Dynamic Data, Silverlight und Silverlight RIA-Dienste verstehen sie auch.

Anzeigen von Validierungen

Wenn die Validierungsmetadaten zugeordnet wurden, werden Fehler automatisch in der Ansicht angezeigt, sobald der Benutzer unzulässige Daten eingibt. Abbildung 4 zeigt, wie die Register-Ansicht aussieht, wenn ein Benutzer auf die Schaltfläche "Register" klickt, ohne Daten eingegeben zu haben.


Abbildung 4 Validierungsfehler

Die Anzeige in Abbildung 4 wurde mit einigen der neuen HTML-Hilfsfunktionen von ASP.NET MVC 2 erstellt, darunter das ValidationMessageFor-Hilfsfunktion. ValidationMessageFor steuert die Platzierung einer Validierungsmeldung, wenn die Überprüfung eines bestimmten Datenfeld fehlschlägt. Abbildung 5 enthält einen Auszug aus der Datei Register.aspx, der veranschaulicht, wie die Hilfsfunktionen ValidationMessageFor und ValidationSummary verwendet werden.

Abbildung 5 Verwendung der neuen HTML-Hilfsfunktionen

<% using (Html.BeginForm()) { %> <%= Html.ValidationSummary(true, "Account creation was unsuccessful. " + "Please correct the errors and try again.") %> <div> <fieldset> <legend>Account Information</legend> <div class="editor-label"> <%= Html.LabelFor(m => m.UserName) %> </div> <div class="editor-field"> <%= Html.TextBoxFor(m => m.UserName) %> <%= Html.ValidationMessageFor(m => m.UserName) %> </div>

Benutzerdefinierte Validierungen

Nicht alle der RegisterModel-Klasse zugewiesenen Validierungsattribute sind Attribute aus der DataAnnotations-Assembly von Microsoft. [PropertiesMustMatch] und [ValidatePasswordLength] sind benutzerdefinierte Attribute, die in der Datei AccountModel.cs definiert wurden, welche die RegisterModel-Klasse enthält. Sie müssen sich nicht um benutzerdefinierte Metadatenanbieter oder Metadatenklassen kümmern, wenn Sie lediglich eine benutzerdefinierte Validierungsregel bereitstellen möchten. Sie müssen lediglich ein Objekt von der abstrakten ValidationAttribute-Klasse ableiten und die IsValid-Methode implementieren. Die Implementierung des ValidatePasswordLength-Attributs ist in Abbildung 6 dargestellt.

Abbildung 6 Implementierung des ValidatePasswordLength-Attributs

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public sealed class ValidatePasswordLengthAttribute : ValidationAttribute { private const string _defaultErrorMessage = "’{0}’ must be at least {1} characters long."; private readonly int _minCharacters = Membership.Provider.MinRequiredPasswordLength; public ValidatePasswordLengthAttribute() : base(_defaultErrorMessage) { } public override string FormatErrorMessage(string name) { return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString, name, _minCharacters); } public override bool IsValid(object value) { string valueAsString = value as string; return (valueAsString != null && valueAsString.Length >= _minCharacters); } }

Das andere Attribut namens PropertiesMustMatch ist ein schönes Beispiel für ein Validierungsattribut, das auf Klassenebenen angewendet werden kann, um eigenschaftsübergreifende Überprüfungen durchzuführen.

Clientvalidierung

Die RegisterModel-Validierung, die wir uns bisher angesehen haben, findet nur auf dem Server statt. Glücklicherweise ist es einfach, auch auf dem Client Validierungen durchzuführen. Ich setzte nach Möglichkeit immer die Clientvalidierung ein, weil der Benutzer dann schnell Rückmeldungen erhalten kann und der Server gleichzeitig entlastet wird. Die serverseitige Logik muss trotzdem beibehalten werden für den Fall, dass jemand die Ausführung von Skripts im Browser deaktiviert hat (oder absichtlich versucht, ungültige Daten zum Server zu senden).

Zur Aktivierung der Clientvalidierung sind zwei Schritte erforderlich. In Schritt 1 muss sichergestellt werden, dass die Ansicht die richtigen Validierungsskripts enthält. Alle benötigten Skripts müssen sich bei einer neuen MVC-Anwendung im Ordner Scripts befinden. Das Skript MicrosoftAjax.js bildet den Kern der Microsoft AJAX-Bibliotheken und ist das erste Skript, das Sie einbinden müssen. Das zweite Skript heißt MicrosoftMvcValidation.js. Ich füge im Allgemeinen, wie unten gezeigt, der Hauptseite meiner MVC-Anwendungen ein ContentPlaceHolder-Objekt zum Speichern der Skripts hinzu:

<head runat="server"> <title><asp:ContentPlaceHolder ID="TitleContent" runat= "server" /></title> <link href="../../Content/Site.css" rel="stylesheet" type= "text/css" /> <asp:ContentPlaceHolder ID="Scripts" runat="server"> </asp:ContentPlaceHolder> </head>

Für eine Ansicht können dann mithilfe eines Content-Steuerelements jeweils die erforderlichen Skripts angegeben werden. Mit dem nachfolgenden Code wird sichergestellt, dass alle Validierungsskripts vorhanden sind:

<asp:Content ContentPlaceHolderID="Scripts" runat="server"> <script src="../../Scripts/MicrosoftAjax.js" type="text/javascript"></script> <script src="../../Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script> </asp:Content>

Der zweite Schritt beim Einsatz clientseitiger Validierung besteht darin, die HTML-Hilfsmethode EnableClientValidation in der Ansicht aufzurufen, in der die Validierungsunterstützung benötigt wird. Sie müssen diese Methode unbedingt vor der HTML-Hilfsfunktion BeginForm aufrufen, wie unten zu sehen ist:

<% Html.EnableClientValidation(); using (Html.BeginForm()) { %> <!-- the rest of the form ... --> <% } %>

Beachten Sie, dass die clientseitige Validierungslogik nur bei Verwendung der integrierten Validierungsattribute funktioniert. Für die Register-Ansicht bedeutet das, dass mit der clientseitigen Validierung das Ausfüllen der erforderlichen Felder sichergestellt werden kann, dass es jedoch nicht möglich ist, die Kennwortlänge zu überprüfen oder zu bestätigen, dass die Zeichenfolgen in den beiden Kennwortfeldern übereinstimmen. Glücklicherweise ist es einfach, benutzerdefinierte JavaScript-Validierungslogik hinzufügen, die sich in das JavaScript-Validierungsframework von ASP.NET MVC einbinden lässt. Phil Haack bietet in seinem Blogbeitrag "ASP.NET MVC 2 Custom Validation" unter haacked.com/archive/2009/11/19/aspnetmvc2-custom-validation.aspx hierzu Einzelheiten.

Zusammenfassend lässt sich feststellen, dass die integrierte Unterstützung für gängige Validierungsszenarien eine wichtige Neuerung in ASP.NET MVC 2 ist. Es ist nicht nur einfach, Validierungsregeln mithilfe von Attributen einem Modellobjekt hinzuzufügen, sondern die Validierungsfunktionen an sich sind auch flexibel und einfach zu erweitern. Sie sollten diese neuen Funktionen nutzen, um Zeit und Codezeilen bei Ihrer nächsten ASP.NET MVC-Anwendung zu sparen.

K. Scott Allen* ist technischer Mitarbeiter von Pluralsight und Gründer von OdeToCode. Sie können ihm unter scott@OdeToCode.com eine E-Mail senden oder seinen Blog unter odetocode.com/blogs/scott lesen oder ihn auf Twitter unter twitter.com/OdeToCode erreichen.*

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels:Brad Wilson