ASP.NET Mvc 3 unobtrusive validation - erweitern mit eigenen jQuery Adaptern und Validatoren
- Teil 1: ASP.NET Mvc 3 unobtrusive validation
- Teil 2: Unobtrusive validation - Clientseitige Adapter
- Teil 3: Unobtrusive validation - Eigene Adapter erstellen
Grundsätzlich gibt es serverseitig zwei Möglichkeiten die eigenen Attribute für die Validierung zu Gestalten:
Implementierung einer Basisklasse wie
RegularExpressionAttribute sowie des Interfaces
IClientValidatableImplementierung einer Basisklasse wie
RegularExpressionAttribute und den Einbau eines
DataAnnotationsModelValidatorIm Prinzip hat
der letzte Post gezeigt, dass ein jQuery Adapter die Konvertierung von HTML5 Attributen in die für jQuery kompatiblen Metadaten vornimmt.
Diese HTML5 Attribute haben alle die selbe Form: data-val-<xxx>
Als Beispiel gibt es bereits vordefinierte Konstellationen wie
data-val-regex und data-
val-regex-pattern.
Somit würde der im Hintergrund stehende Adapter nichts anderes machen, als zu überprüfen, ob ein Feld mit dem Attribut data-val-regex gesetzt ist und wenn ja, das dazugehörige pattern auslesen und validieren.
Im nachstehenden Beispiel wird ein eigenes, triviales Email Attribut erstellt, um die Umsetzung etwas deutlicher zu erklären.
Möglichkeit 1) IClientValidatableDas Attribut
public class EmailWithoutValidatorAttribute : RegularExpressionAttribute, IClientValidatable
{
public EmailWithoutValidatorAttribute() : base(@"<emailregex>")
{
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata,
ControllerContext context)
{
var rule = new ModelClientValidationRule(...);
return new[] { rule };
}
}
Eigentlich nur ein RegularExpressionAttrubite wie von früheren Zeiten gewohnt, sowie eine zusätzliche Methode GetClientValidationRules, die die Rückgabe einer Liste von Regeln erwartet. Ob eine oder mehrere Regeln zurückgegeben werden, ist irrelevant.
Nun reicht eine normale
ModelClientValidationRule für das Vorhaben nicht aus, somit wird eine eigene erstellt. Diese hat den Vorteil, dass ein eigener
validationType (Der eigentliche Name des Adapters, der später benötigt wird) angegeben werden kann.
public class NamedRegexValidationRule : ModelClientValidationRule
{
public NamedRegexValidationRule(string errorMessage,
string pattern, string validationType)
{
this.ErrorMessage = errorMessage;
this.ValidationType = validationType;
this.ValidationParameters.Add("pattern", pattern);
}
}
Die im Code gezeigten ValidationParemeters können beliebig gefüllt werden, benötigt wird hier ausschließlich das Pattern.
Hinweis: Die <string, object> collection kann beliebig gefüllt werden. Letztendlich wird jedes Element als ein html Attribut data-val-<string>="<value>" gerendert. Es ist somit egal, ob "pattern" oder "blubr" vorkommt.Die angepasste Methode im Attribut würde somit wie folgt aussehen (Wichtig ist der Name „customemail“, dieser wird als Adaptername verwendet und kann ausschließlich mit Kleinbuchstaben angegeben werden)
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata,
ControllerContext context)
{
var rule = new NamedRegexValidationRule(this.ErrorMessage, this.Pattern, "customemail");
return new[] { rule };
}
Angenommen es ist ein Model "Person" mit einer Eigenschaft "Email" vorhanden, die mit dem eben erstellen Attribut versehen ist:
public class Person
{
[EmailWithoutValidator(ErrorMessage = "Not a valid email.")]
public string Email { get; set; }
}
Das dazugehörige Feld als gerenderter Version
@Html.TextBoxFor(p => p.Email)
<input type="text" value="" name="Email" id="Email" class="input-validation-error"
data-val-customemail-pattern="<EmailPattern>"
data-val-customemail="Not a valid email."
data-val="true">
Zwei Sachen wurden damit erreicht:
- data-val-customemail steht für die angegebene Fehlermeldung.
- data-val-customemail-pattern steht für das angegebene Email Pattern.
Möglichkeit 2) DataAnnotationsModelValidator
public class EmailWithValidatorAttribute : RegularExpressionAttribute
{
public EmailWithValidatorAttribute() : base(@"<EmailRegex>")
{
}
}
Nichts anderes als bei Möglichkeit 1, ohne der Implementierung des IClientValidatable Interfaces und somit der Erstellung der Regeln.
Diese werden bei dieser Variante
in den Validator ausgelagert:
public class EmailValidator : DataAnnotationsModelValidator<EmailWithValidatorAttribute>
{
private readonly string _errorMessage;
private readonly string _pattern;
public EmailValidator(ModelMetadata metadata, ControllerContext context, EmailWithValidatorAttribute attribute)
: base(metadata, context, attribute)
{
this._errorMessage = attribute.ErrorMessage;
this._pattern = attribute.Pattern;
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
var rule = new NamedRegexValidationRule(this._errorMessage, this._pattern, "customemail");
return new[] { rule };
}
}
Auch hier gibt es eine Methode
GetClientValidationRules(), die eigentlich die selbe Logik beinhaltet, wie in Möglichkeit 1.
Zusätzlich muss der Validator nun in der Global.asax (In der application start Methode) noch mit Angabe des Attributes registriert werden:
DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof(EmailWithValidatorAttribute),
typeof(EmailValidator));
Im Prinzip bringen beide Möglichkeiten das selbe Ergebnis, Möglichkeit 1 hat den Nachteil (zumindest für den ein oder anderen), dass die Logik direkt im Attribut steht und Möglichkeit 2 lagert dies in einen externen Validator aus. Somit ist das Attribut nicht MVC abhängig und könnte auch für andere Zwecke verwendet werden, bzw. bereits vorhandene Attribute könnten problemlos wiederverwendet werden.
Last but not least,
der clientseitige Aufbau des Adapters.Die jQuery unobtrusive validation Bibliothek stellt eine Methode Add() zur Verfügung, über welche neue, eigene Adapter registiert werden können. Diese nimmt als Parameter einmal den Adapternamen, den/die Attributnamen die benötig werden, sowie eine Funktion, über die die eigene Logik implementiert werden kann.
jQuery.validator.unobtrusive.adapters.add("customemail", ["pattern"], function (options) {
if (options.element.tagName.toUpperCase() == "INPUT") {
// Set the regex rule with the given pattern.
options.rules["regex"] = options.params.pattern;
if (options.message) {
options.messages["regex"] = options.message;
}
}
});
Wie im
letzten Post gezeigt, beinhaltet das options Objekt Regeln, Parameter und Mitteilungen, über die die Validierung gesteuert wird.
In Falle einer Email Validierung reicht eine regex Regel aus, es können aber beliebig viele hinzugefügt werden. Wichtig ist hierbei nur, dass diese als Paremter angegeben werden ["pattern", "<anderes Attribut">, ...]
Natürlich macht es nicht wirklich viel Sinn, ein Email Attribut so kompliziert zu gestalten, viel mehr geht es darum zu zeigen, wie es im Verhältnis doch einfach ist, eigene Adapter zu erstellen. Im Web 2.0 Zeitalter ist eine kombination aus client und server Validierung eher Standard als eine Ausnahme. Mit ein bisschen Mühe können tolle Sachen, wie bspw. eine Überprüfung, ob ein Username einzigartig ist entwickelt werden. Der Große Vorteil liegt auch hier in der Entkoppelung der Validierungslogik vom restlichen Teil der Anwendung.
Anbei gibt’s wie immer das Beispiel zum Post.