The Vision Column - Custom validator controls in ASP.NET
The Vision Column wordt verzorgd door ontwikkelaars van
The Vision Web.
Door Arnold Jan van der Burg
7 juli 2006
In ASP.NET is validatie van webformulieren een stuk eenvoudiger dankzij de specifieke
validators. Er zijn verschillende validators beschikbaar, voor bijvoorbeeld het
valideren van een bepaalde range van waarden (RangeValidator control), of valideren
met reguliere expressies (RegularExpressionValidator). Als de standaard validatie
controls geen uitkomst bieden, kun je ook nog gebruik maken van het CustomValidator
control en daar zelf een client-side validatie script aan hangen.
De T-Mobile case
De meeste .NET programmeurs zullen met bovenstaande informatie wel bekend zijn. Ik wil
hier dan ook nog de mogelijkheid bespreken tot het bouwen van een custom validator,
en daarbij als case T-Mobile aanvoeren. Wat is er namelijk aan de hand?
In de huidige (classic ASP) situatie wordt voor validatie Javascript gebruikt, waarbij
de ingevulde velden worden gecontroleerd met behulp van reguliere expressies. Indien
een veld niet correct is gevuld, wordt de CSS class van het invoerveld veranderd.
Dit gedrag wilde ik nabootsen met behulp van ASP.NET Validator controls. Omdat de
validatie en het veranderen van de CSS class op de client-side moet gebeuren, zocht
ik in eerste instantie mijn heil in het CustomValidator control.
Echter, het CustomValidator control dwingt geen verplichting af, d.w.z. een textbox
die geen waarde heeft, is volgens dit control ook geldig. Dit kun je weer ondervangen
door een extra RequiredFieldValidator control toe te voegen, je kunt meerdere validators
hetzelfde control laten controleren. Helaas kent dit control weer geen mogelijkheid om
een custom validatie script toe te voegen voor het inbouwen van de CSS switch, dus had
ik twee keuzes: de RegularExpressionValidator overerven of zelf een custom validator
control bouwen.
BaseValidator class
Een eigen validator class zou je vanaf de grond op kunnen bouwen door de IValidator
interface te implementeren. Gelukkig heeft Microsoft in het .NET framework de
BaseValidator class opgenomen. Deze class bezit over alle basiseigenschappen die een
validator class nodig heeft, waarvan je er slechts een paar hoeft te overriden om je
eigen validator control te creëren.
Een paar van de belangrijkste (protected) methods:
EvaluateIsValid(). Retourneert een boolean waarde. Deze methode gebruik je om control uit de ControlToValidate property op te halen en te bepalen of de waarde van deze control geldig is.
RegisterValidatorCommonScript(). Deze functie kan gebruikt worden om client-side validatie script te emitten op de pagina.
ControlPropertiesValid(). Helper functie, die aangeeft of de te valideren control geldig is.
AddAttributesToRender(). Standaard functie van de WebControl class, die gebruikt kan worden om attributen te renderen.
Custom validator control
De custom validator control die ik nodig had, maakt gebruik van een reguliere expressie.
Als het te controleren invoerveld een match vormt met deze expressie, bevat het
invoerveld een geldige waarde. Zo niet, dan moet er dus van CSS class veranderd worden.
Hiervoor heb ik de BaseValidator overerfd en daar een aantal zaken aan toegevoegd
(zie de code hieronder).
1: public class RequiredRegularExpressionValidator : BaseValidator
2: {
3: (...)
4: protected override void AddAttributesToRender(HtmlTextWriter writer)
5: {
6: Control control;
7:
8: if (this._errorCssClass != null && this._errorCssClass.Length > 0)
9: {
10: writer.AddAttribute("errorcssclass", this._errorCssClass);
11: }
12:
13: if (this._validationExpression != null &&
14: this._validationExpression.Length > 0)
15: {
16: writer.AddAttribute("validationexpression", this._validationExpression);
17: }
18:
19: // If target textbox is found, inject script code to set
20: // css class back to it's original state if validated
21: control = base.FindControl(base.ControlToValidate);
22: if (control != null)
23: {
24: if ( ((TextBox)control).CssClass != null
25: && ((TextBox)control).CssClass.Length > 0)
26: {
27: writer.AddAttribute("originalcssclass", ((TextBox)control).CssClass);
28: }
29: }
30:
31: writer.AddAttribute("evaluationfunction", _validationFunction);
32: base.AddAttributesToRender (writer);
33: }
34:
35: protected override bool EvaluateIsValid()
36: {
37: string controlValue;
38: controlValue = this.GetControlValidationValue(this.ControlToValidate);
39:
40: if (controlValue == null || controlValue.Length == 0)
41: {
42: return false;
43: }
44:
45: // Looking for an exact match, not just a search hit.
46: Match m = Regex.Match(controlValue, this.ValidationExpression);
47: return(m.Success && m.Index == 0 && m.Length == controlValue.Length);
48: }
49:
50: protected override bool ControlPropertiesValid()
51: {
52: Control ctrl = base.FindControl(base.ControlToValidate);
53: if (ctrl != null)
54: {
55: _textBox = (TextBox) ctrl;
56: return (_textBox != null);
57: }
58: else
59: {
60: return false; // raise exception
61: }
62: }
63:
64: protected override void OnPreRender(EventArgs e)
65: {
66: //Create validation script if necessary
67: if (this.EnableClientScript &&
68: !this.Page.IsClientScriptBlockRegistered("CustomRegularExpressionValidator"))
69: {
70: this.Page.RegisterClientScriptBlock(
71: "CustomRegularExpressionValidator",
72: CreateValidationScript());
73: }
74: base.OnPreRender (e);
75: }
76: (...)
77: }
Wat doet deze validator nou eigenlijk? Allereerst heb ik een tweetal properties
toegevoegd: ValidationExpression en CssErrorClass. De ValidationExpression property
bevat de reguliere expressie waarmee we gaan valideren, de CssErrorClass property
bevat de CSS class die in geval van invalide data zal worden toegekend aan het te
valideren control. Deze twee properties worden meegegeven in de HTML output via de
AddAttributesToRender methode. Om ervoor te zorgen dat de originele CSS class bewaard
blijft, word deze ook als extra attribuut meegenomen. Ook de client-side functie die
zal worden gebruikt wordt meegegeven via het evalutationfunction attribuut (hierover
later meer).
In de EvaluateIsValid methode wordt de inhoud van het target control tegen de reguliere
expressie aangehouden. Verder wordt de PreRender methode het client-side validatie
script gegenereerd.
Client-side validatie
De client-side validatie optie is binnen ASP.NET een verhaal apart. Wat gebeurt er op
het moment dat client-side de validatie moeten worden uitgevoerd? Allereerst injecteert
het .NET Framework een script verwijzing naar het WebUIValidation.js bestand, dat
standaard door het .NET Framework wordt toegevoegd aan je website directory
([websitenaam]/aspnet_client/system_web/[Framework versie nr.]/ WebUIValidation.js).
Daarnaast wordt voor de validator controls Javascript ge-emit dat twee globale
Javascript variabelen definieert - een voor de validation summary (indien aanwezig),
en één voor de validator controls:
1: <script language="javascript" type="text/javascript">
2: <!--
3: var Page_ValidationSummaries =
4: new Array(document.all["<Client ID van validatorsummary control>"]);
5: var Page_Validators = new Array(
6: document.all["<Client ID van validator control1>"],
7: document.all["<Client ID van validator control1>"], etc. etc.
8: );
9: // -->
10: </script>
In het WebUIValidation.js script zijn functies opgenomen die de basis vormen voor de
client-side validatie. Wat dit script in het kort doet, is de te controleren controls
voorzien van extra eventhooks (zoals het onchange event). Vervolgens wordt op het
moment dat er een waarde van één van de invoervelden verandert, het Page_Validator
array afgelopen en de validators geactiveerd. Dit gebeurt via de ValidatorValidate
functie. Hierin wordt de evaluationfunction property van de validator gebruikt om de
validatie aan te roepen.
1: function ValidatorValidate(val) {
2: val.isvalid = true;
3: if (val.enabled != false) {
4: if (typeof(val.evaluationfunction) == "function") {
5: val.isvalid = val.evaluationfunction(val);
6: }
7: }
8: ValidatorUpdateDisplay(val);
9: }
Deze functie wordt op zijn beurt weer aangeroepen door de Page_ClientValidate functie:
1: function Page_ClientValidate() {
2: var i;
3: for (i = 0; i < Page_Validators.length; i++) {
4: ValidatorValidate(Page_Validators[i]);
5: }
6: ValidatorUpdateIsValid();
7: ValidationSummaryOnSubmit();
8: Page_BlockSubmit = !Page_IsValid;
9: return Page_IsValid;
10: }
Hier is goed te zien hoe het mechanisme werkt. Het array van validators wordt afgelopen
en het validatie script wordt afgevuurd. De meeste functies uit het .js bestand gaan
op deze manier te werk (bijvoorbeeld voor het hooken van de events).
Het is aan te raden om het WebUIValidation.js bestand eens nader te bestuderen, als
je zelf aan de slag wilt met client-side validatie dan wel custom validator classes.
Een CheckBoxList control kan bijvoorbeeld niet door de standaard meegeleverde
validator controls worden gevalideerd. Zo kwam ik er achter dat er een klein bugje
zit in het WebUIValidation.js bestand, maar daarover misschien meer in een volgende
column.
Referenties
BaseValidator class:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemwebuiwebcontrolsbasevalidatorclasstopic.asp
ASP.NET validatie:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/aspplusvalid.asp
|