Thomas Bandt

Über mich | Kontakt | Archiv

ASP.NET MVC 2 - Bools validieren

Es sind die kleinen Dinge, die einen täglich aufhalten. Und so lang ich mich erinnern kann, waren es auch über die verschiedenen Generationen von ASP.NET hinweg auch immer wieder Themen rund um HTML-Checkboxen, die für Mehraufwand, Workarounds oder einfach nur graue Haare sorgten. Selbst im Jahr 8 von ASP.NET scheint sich das nicht geändert zu haben.

Worum geht es diesmal? Um den mit ASP.NET MVC 2 nun bald offiziell "neu" eingeführten Weg der Validierung über Attribute im View Model. Ähm was? Genau, also von vorn.

Bislang war eine der häufigsten (und bisweilen auch von mir verwendete) Vorgehensweise zum Validieren, dass jeder einzelne Wert aus dem Formular als Parameter der Controller-Methode übergeben und dann in dieser auf Herz und Nieren hin untersucht wurde.

Mit der neuen Version werden nun so genannte "DataAnnotations" eingeführt. Das heißt die Validierung erfolgt deklarativ über Attribute, die man den Properties seines View Models verpasst.

 1: public class FooViewModel
 2: {
 3:  [Required]
 4:  public string Text { get; set; }
 5: }

Durch das einfache Attribut [Required] lässt sich so sicherstellen, dass Text immer vom User angegeben werden muss, man spart sich also eine Menge Prüfungen und letztendlich auch (Unit-) Tests, also viel Tipparbeit. Eine ausführliche Einleitung, leider mit etwas veralteter Syntax, findet sich im Blog von Brad Wilson.

Aber natürlich gibt es auch hier Probleme, auf das erste bin ich gleich nach einer halben Stunde gestoßen, als ich etwas ganz übliches machen wollte: der User muss vor dem Abschicken eines Formulars eine Checkbox anhaken um sich mit den Allgemeinen Geschäftsbedingungen einverstanden zu erklären.

Mein View Model sah also wie folgt aus:

 1: public class SignUpViewModel
 2: {
 3:  [Required(ErrorMessage = "Bitte lesen und akzeptieren Sie die AGB.")]
 4:  [DisplayName("Ich habe die AGB gelesen und akzeptiere diese.")]
 5:  public bool AgreesWithTerms { get; set; }
 6: }

Das dazu gehörige Formular in der View:

 1: <h1>Registrierung<h1>
 2:  
 3: <%= Html.ValidationSummary() %>
 4:  
 5: <% Html.BeginForm(); %>
 6:  
 7:  <fieldset>
 8:  <%= Html.CheckBoxFor(m => m.AgreesWithTerms) %>
 9:  <%= Html.LabelFor(m => m.AgreesWithTerms)%>
 10:  <br />
 11:  <input type="submit" value="Account erstellen" /> 
 12:  <fieldset>
 13:  
 14: <% Html.EndForm(); %>

Was passiert hier? Nichts. Nach kurzer Überlegung und etwas Suchen, ist auch schnell klar, warum. Ich zitiere Brad Wilson:

"The first problem is that [Required] on non-nullable properties doesn't quite behave like you'd expect. Literally, the implementation of the [Required] attribute is that the value cannot be null. Since a non-nullable value type can never be null, the [Required] attribute doesn't actually ever say the field is invalid.

So, what's it for, then? Failing to provide a value for a non-nullable property is actually a model binding error, as I mentioned above. We query the validation system and ask it for whatever it considers to be the "required" validator for the property, so we can determine what the message should be. The "required" validator is treated specially during model binding failures on non-nullable types, so that you're not stuck with the vanilla message 'A value is required.'

The off-shoot of this is that [Required] on a non-nullable value type cannot act as a guarantee that the form included a value. If it doesn't include a value, then model binding is skipped, which means the model binding failure won't occur. Additionally, when the [Required] validator is run, it queries the value from the model -- which will contain the value-types default value, typically 0 -- and say "that's not null, everything is all good here!".

If you make the property a nullable version of the value type, say by turning "int" into "int?", you'll be able to use [Required] as a way to ensure that a value is posted."

Der letzte Satz stimmt so leider in diesem Fall nicht. Macht man "AgreesWithTerms" aus dem Beispiel nullable, führt das unweigerlich zu folgender netten Exception bei der Ausführung:

"Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions."

Was bleibt, ist ein eigenes Attribut zur Validierung. Glücklicherweise ist die komplette Architektur von ASP.NET MVC sehr offen, so dass man sich seine Erweiterungen beliebig selbst schreiben kann. In diesem Fall waren es nur ein paar Zeilen:

 1: public class BooleanRequiredToBeTrueAttribute : RequiredAttribute 
 2: {
 3:  public override bool IsValid(object value)
 4:  {
 5:  return value != null && (bool) value;
 6:  }
 7: }

Noch schnell die Deklaration geändert

 1: public class SignUpViewModel
 2: {
 3:  [BooleanRequiredToBeTrue(ErrorMessage = "Bitte lesen und akzeptieren Sie die AGB.")]
 4:  [DisplayName("Ich habe die AGB gelesen und akzeptiere diese.")]
 5:  public bool AgreesWithTerms { get; set; }
 6: }

Und siehe da: alles funktioniert wie es soll! Happy Coding :-)

Kommentare

  1. Thomas goes .NET schrieb am Montag, 15. Februar 2010 16:29:00 Uhr:

    Es gibt Daten für
  2. Thomas goes .NET schrieb am Dienstag, 16. Februar 2010 13:07:00 Uhr:

    Die
  3. Garb schrieb am Freitag, 17. September 2010 09:50:00 Uhr:

    Wie kann ich denn eine eigene Fehlermeldung ausgeben bei anderen Model-Binding fehlern??? Hab da Nichts dazu gefunden.
  4. Stevo schrieb am Dienstag, 7. Dezember 2010 00:38:00 Uhr:

    hey thomas! vielen dank! mach weiter so! thx, stevo


« Zurück  |  Weiter »