Monday, April 28, 2008

.Net 3.5 Validator Extensibility - Part Three

Today I would like to show you how to do some extra something that you would like to before and after the evaluationfuncton of a validator is fired. The key to make this work is about swapping the default evaluationfunction of a validator with your own function. The following are the steps to achieve this purpose.



Create a control derived from ScriptControl

First of all, we create a control derived from ScriptControl and override GetScriptDescriptors and GetScriptReferences in order to create the script descriptors and references associated with this control.



Second, this script control has four additional properties: TargetFormID, InvalidColor, ValidColor and ValidationGroup. These properties will be passed to its descriptor's properties.



Third, remember to specify the javascript file to the assembly with [assembly:WebResource(.....)].



Create data object and collection that collects information to associate a label with validators

Now, we need to collect information about relating a specific label to validators. You may want to ask why not the controls validators validate? That information can be obtained from the validator element in javascript throught one of its property named "controltovalidate". This data object contains to public accessors which are the LabelID and ValidatorIDs along with two friendly constructors. one takes labelId and validatorIds of array as parameters and the other take labelId of string and validators of BaseValidator as parameters.



Next, we need a collection object to retain these data objects and also give it the ability to convert data into json string. So, we create a collection that derived from List<[data object]> which takes data object as template. Then we create a method that does the transformation by using DataContractJsonSerializeer object. In order to make this object to understand the data object we created previously, we need to mark the data object with DataContract attribute for class definition, and DataMember for property definition.



Now, we are ready to create the key component that makes the whole thing works.



Create a Javascript file

So, the last step is to create a javascript file and write scripts according to ASP.NET AJAX convension. The following is the sample showing you how to create a ASP.NET AJAX client control:




Type.registerNamespace("Demo");

// Constructor
Demo.ValidationExtension= function(element) {
Demo.ValidationExtension.initializeBase(this, [element]);
}

Demo.ValidationExtension.prototype = {
// Release resources before control is disposed.
dispose: function() {
Demo.HoverButton.callBaseMethod(this, 'dispose');
},

//initialize resources
initialize: function() {
Demo.HoverButton.callBaseMethod(this, 'initialize');
}
}

Demo.ValidationExtension.registerClass('Demo.ValidationExtension, Sys.UI.Control);



Next, we need to define some properties for this client control. As we have mentioned in creating server side script control, there are several properties will be translated into client control's properties and they are "InvalidColor", "ValidColor", "ValidationGroup" and "Fields" which is the json converted from the data object describing the relationship between labels and validators.



After defining these properties, we need to swap the original evaluationfunction with our own and keep the original for later use. First, we loop through Fields properties to do the swapping and save a record of labelid, elementids and validatorid and its orginal evaluationfunction. Remember, this property is an array and it contains objects with two properties: LabelID and ValidatorIDs. This must be done in initialize function.



The last step is to define our own validation function. Because we swap the original evaluationfunction with our own, when validation is triggered, our validation function will be invoked. Here is where we can do something before and after the validator's evaluationfunction is fired and in this case, it will change the color of the label and of controls associated with this validator according to the result of validation.



Below is a sample of the programming:




[assembly:WebResource("Demo.FieldValidationExtension.js", "text/javascript")]

namespace Demo
{
public class ValidatorExtension : ScriptControl
{
private FieldValidationExtenderCollection _extenders;
private Color _validColor;
private Color _invalidColor;
private string _formId;
private string _validationGroup;

public string TargetFormID
{
get { return _formId; }
set { _formId = value; }
}

public string ValidationGroup
{
get { return _validationGroup; }
set { _validationGroup = value; }
}

public FieldValidationExtenderCollection Extenders
{
get { return _extenders; }
}

public Color ValidColor
{
get { return _validColor; }
set { _validColor = value; }
}

public Color InvalidColor
{
get { return _invalidColor; }
set { _invalidColor = value; }
}

public ValidatorExtension()
{
_extenders = new FieldValidationExtenderCollection();
_validColor = Color.Black;
_invalidColor = Color.Red;
}

public void InvalidControl(Label label, WebControl control)
{
label.ForeColor = _invalidColor;
control.ForeColor = _invalidColor;
}

public void ValidControl(Label label, WebControl control)
{
label.ForeColor = _validColor;
control.ForeColor = _validColor;
}

protected override IEnumerable GetScriptDescriptors()
{
ScriptControlDescriptor descriptor = new ScriptControlDescriptor("Demo.FieldValidationExtension", this.Page.FindControl(this.TargetFormID).ClientID);
descriptor.AddScriptProperty("Fields", _extenders.ToJSON());
descriptor.AddProperty("InvalidColor", _invalidColor.Name);
descriptor.AddProperty("ValidColor", _validColor.Name);
descriptor.AddProperty("ValidationGroup", _validationGroup);
yield return descriptor;
}

protected override IEnumerable GetScriptReferences()
{
yield return new ScriptReference("Demo.FieldValidationExtension.js", "Patmos.Web");
}
}
}




namespace Demo
{
[DataContract]
public class FieldValidationExtender
{
private string _labelId;
private List _validatorIds;

[DataMember]
public string LabelControlID
{
get { return _labelId; }
set { _labelId = value; }
}

[DataMember]
public string[] Validators
{
get { return ValidatorIds.ToArray(); }
set { ValidatorIds = new List(value); }
}

public List ValidatorIds
{
get { return _validatorIds; }
set { _validatorIds = value; }
}

public FieldValidationExtender()
{
_labelId = "";
_validatorIds = new List();
}

public FieldValidationExtender(string labelId)
{
_labelId = labelId;
_validatorIds = new List();
}

public FieldValidationExtender(string labelId, params string[] validatorIds)
{
_labelId = labelId;
_validatorIds = new List(validatorIds);
}

public FieldValidationExtender(string labelId, params BaseValidator[] validators)
{
_labelId = labelId;
_validatorIds = new List();
foreach (BaseValidator v in validators)
{
_validatorIds.Add(v.ClientID);
}
}
}
}

namespace Demo
{
public class FieldValidationExtenderCollection : List
{
public string ToJSON()
{
DataContractJsonSerializer s = new DataContractJsonSerializer(this.GetType());
MemoryStream mem = new MemoryStream();
s.WriteObject(mem, this);
return Encoding.Default.GetString(mem.ToArray());
}
}
}




Type.registerNamespace("Demo");

Demo.FieldValidationExtension = function(element) {
Demo.FieldValidationExtension.initializeBase(this, [element]);

this._fields = null;
this._invalidColor = 'red';
this._validColor = 'black';
this._validationGroup = "";
this._validatorExtenders = new Array;
this._validationMethodOverride = Function.createDelegate(this, this._onvalidate);
}

Demo.FieldValidationExtension.prototype = {
initialize: function() {
Demo.FieldValidationExtension.callBaseMethod(this, 'initialize');

for(var i = 0; i < this._fields.length; i++)
{
for(var j = 0; j < this._fields[i].Validators.length; j ++)
{
var elm = $get(this._fields[i].Validators[j]);
var elements = new Array();

if (elm.controltovalidate !== "undefined" && elm.controltovalidate.length > 0)
{
var e = $get(elm.controltovalidate);
if (e.tagName.toLowerCase() == 'div' || e.tagName.toLowerCase() == 'span')
{
var controlIds = elm.controlstovalidate.split(',');
for(var k = 0; k < controlIds.length; k ++)
{
Array.add(elements, $get(controlIds[k]));
}
}
else
Array.add(elements, e);
}
else
{
var controlIds = elm.controlstovalidate.split(',');
for(var k = 0; k < controlIds.length; k ++)
{
Array.add(elements, $get(controlIds[k]));
}
}

Array.add(this._validatorExtenders, {Label: $get(this._fields[i].LabelControlID), ValidatorId: this._fields[i].Validators[j], ElementsToValidate: elements, OriginalEvlFunc: Function.createDelegate(elm, elm.evaluationfunction)});
elm.evaluationfunction = this._validationMethodOverride;
}
}
if (typeof(Page_ClientValidate) == "function") {
Page_ClientValidate(this._validationGroup);
}
},

dispose: function() {
//Add custom dispose actions here
Demo.FieldValidationExtension.callBaseMethod(this, 'dispose');
},

_onvalidate : function(val) {
var idx = this._findValIndex(val);
if (idx == null)
Error.argumentUndefined(val.id, 'validator id can\'t be found.');
else
{
var indexes = this._findAllValByLabel(this._validatorExtenders[idx].Label.id);
var isValid = true;
for(var i = 0; i < indexes.length; i++)
{
var v = $get(this._validatorExtenders[indexes[i]].ValidatorId);
var isValValid = this._validatorExtenders[indexes[i]].OriginalEvlFunc(v);
if (!isValValid)
v.style.display = 'inline';
else
v.style.display = 'none';

isValid = isValid && isValValid;
}
this._setAssociatedControlsStatus(this._validatorExtenders[idx], isValid);
/*if (!isValid)
val.style.display = 'inline';
else
val.style.display = 'none';*/
return isValid;
}
},

_findValIndex : function(val) {
var index = null;

for(var i = 0; i < this._validatorExtenders.length; i ++)
{
if (this._validatorExtenders[i].ValidatorId == val.id)
index = i;
}

return index;
},

_findAllValByLabel : function(labelId) {
var valIdexes = new Array();
for(var i = 0; i < this._validatorExtenders.length; i ++)
{
if (this._validatorExtenders[i].Label.id == labelId)
Array.add(valIdexes, i);
}
return valIdexes;
},

_setAssociatedControlsStatus: function(valext, isvalid) {
if (!isvalid)
{
valext.Label.style.color = this._invalidColor;
this._setElementsToValidateStatus(valext.ElementsToValidate, this._invalidColor);
}
else
{
valext.Label.style.color = this._validColor;
this._setElementsToValidateStatus(valext.ElementsToValidate, this._validColor);
}
},

_setElementsToValidateStatus: function(elements, color) {
for(var i = 0; i < elements.length; i ++)
{
elements[i].style.color = color;
}
},

get_InvalidColor: function() {
return this._invalidColor;
},

set_InvalidColor: function(value) {
if (this._invalidColor != value) {
this._invalidColor = value;
this.raisePropertyChanged("InvalidColor");
}
},

get_ValidColor: function() {
return this._validColor;
},

set_ValidColor: function(value) {
if (this._validColor != value) {
this._validColor = value;
this.raisePropertyChanged("ValidColor");
}
},

get_ValidationGroup: function() {
return this._validationGroup;
},

set_ValidationGroup: function(value) {
if (this._validationGroup != value) {
this._validationGroup = value;
this.raisePropertyChanged("ValidationGroup");
}
},

get_Fields: function() {
return this._fields;
},

set_Fields: function(value) {
if (this._fields != value) {
this._fields = value;
this.raisePropertyChanged("Fields");
}
}
}
Demo.FieldValidationExtension.registerClass(Demo.FieldValidationExtension', Sys.UI.Control);



I wish these articles that I put out related to ASP.NET validation extensibility are helpful to some degrees for those who read. Next time, I am planning to put out an article of how to call a webservice to validate inputs at browser. Thanks!

Sunday, April 6, 2008

.Net 3.5 Validator Extensibility - Part Two

Starting from this week, I will be showing some examples of how to create your own .net validation control starting with Phone validator.

Issue

Often we as developer receive requests like handling phone number in three textboxes instead of one. and the required number of validators grows from two (the required field validator and regular expression validator) to six (the two previously mentioned validators for each textbox). Although it is not a complicated task, a ui developer still need to spend time to drag and drop textboxes and respective validators accordingly. It could be considerd as boring.

Solution

Therefore, a solution is advised and plan is set to create two controls; one PhoneNumber TextBox control and PhoneNumber Validation control.

First Step - Create PhoneNumber Control

Obviously, a PhoneNumber control needs to be able to render three TextBoxes. These TextBoxes will be placing as PhoneNumber control's child controls. But there is a catch in terms of its renderation. In order to hook up the ControlToValidate property of PhoneNumber validator, which will be created in the later part of this article, with PhoneNumber TextBox control, these three textboxes need to be placed under a div or a span block. The best way to do this is to create this PhoneNumber control inherited from WebCotrol and override some of its properties and key methods to make it render the result we want. There is also another feature that I would like to add to this control which will allow a jump to next textbox when input is valid. We can do so by attaching a javascript library to this control. It can be done in several ways; but here, I would like to implement this control with IScriptControl interface.(Details about how to implement a ScriptControl class, please consult the online tutorials on www.asp.net/ajax.)

Here are its programming and javascript library.:


[DefaultProperty("PhoneNumber")]

[ToolboxData("<{0}:PhoneTextBox runat=server>")]

public class PhoneTextBox : WebControl, IScriptControl

{

private TextBox _tbxAreaCode;

private TextBox _tbxPrefix;

private TextBox _tbxSuffix;

private ScriptManager _sm;



[Bindable(true)]

[Category("General")]

[DefaultValue("")]

[Localizable(true)]

public string PhoneNumber

{

get

{



String s = (String)ViewState["PhoneNumber"];

return ((s == null) ? string.Empty : s);

}

set

{

if (value.Length == 0)

ViewState["PhoneNumber"] = "";

else

{

Regex regex = new Regex("^\\d{10}$");

string tmp = value.Replace("(", "").Replace(")", "").Replace("-", "").Replace(" ", "");

if (!regex.IsMatch(tmp) && tmp.Length > 0)

throw new ArgumentException("Phone number must be 10 digits of number", "PhoneNumber");



ViewState["PhoneNumber"] = tmp;

if (value.Length == 10)

{

_tbxAreaCode.Text = tmp.Substring(0, 3);

_tbxPrefix.Text = tmp.Substring(3, 3);

_tbxSuffix.Text = tmp.Substring(6, 4);

}

}

}

}

[Bindable(true)]

[Category("General")]

[DefaultValue("")]

[Localizable(true)]

public string ControlToJumpTo

{

get

{



String s = (String)ViewState["ControlToJumpTo"];

return ((s == null) ? string.Empty : s);

}

set

{

ViewState["ControlToJumpTo"] = value;

}

}



[Bindable(true)]

[Category("Validation")]

[DefaultValue("")]

[Localizable(true)]

public string ValidationGroup

{

get

{

String s = (String)ViewState["ValidationGroup"];

return ((s == null) ? string.Empty : s);

}



set

{

ViewState["ValidationGroup"] = value;

}

}



protected override HtmlTextWriterTag TagKey

{

get

{

return HtmlTextWriterTag.Span;

}

}



protected override string TagName

{

get

{

return "span";

}

}



protected override void OnInit(EventArgs e)

{

EnsureChildControls();

base.OnInit(e);

}



protected override void CreateChildControls()

{

base.CreateChildControls();

_tbxAreaCode = new TextBox();

_tbxAreaCode.ID = "_tbxAreaCode";

_tbxAreaCode.MaxLength = 3;

_tbxAreaCode.Width = new Unit("30px");

Controls.Add(_tbxAreaCode);

_tbxPrefix = new TextBox();

_tbxPrefix.ID = "_tbxPrefix";

_tbxPrefix.MaxLength = 3;

_tbxPrefix.Width = new Unit("30px");

Controls.Add(_tbxPrefix);

_tbxSuffix = new TextBox();

_tbxSuffix.ID = "_tbxSuffix";

_tbxSuffix.MaxLength = 4;

_tbxSuffix.Width = new Unit("40px");

Controls.Add(_tbxSuffix);

_v = new PhoneValidator();

_v.ID = ID + "_v";

_v.ControlToValidate = ID;

_v.IsPhoneRequired = IsRequired;

_v.ValidationGroup = ValidationGroup;

_v.ErrorMessageForInvalidPhoneNumber = PhoneInvalidErrorMessage;

_v.ErrorMessageForMissingPhoneNumber = PhoneMissingErrorMessage;

_v.Text = "*";

Controls.Add(_v);

}



protected override void OnLoad(EventArgs e)

{

base.OnLoad(e);

if (Page.IsPostBack Page.IsCallback)

{

PhoneNumber = _tbxAreaCode.Text + _tbxPrefix.Text + _tbxSuffix.Text;

}

else

{

if (!string.IsNullOrEmpty(PhoneNumber))

{

_tbxAreaCode.Text = PhoneNumber.Substring(0, 3);

_tbxPrefix.Text = PhoneNumber.Substring(3, 3);

_tbxSuffix.Text = PhoneNumber.Substring(6, 4);

}

}

}



protected override void OnPreRender(EventArgs e)

{

base.OnPreRender(e);

if (!this.DesignMode)

{

// Test for ScriptManager and register if it exists

_sm = ScriptManager.GetCurrent(Page);



if (_sm == null)

throw new HttpException("A ScriptManager control must exist on the current page.");



_sm.RegisterScriptControl(this);

}

}



protected override void Render(HtmlTextWriter writer)

{

if (!this.DesignMode)

_sm.RegisterScriptDescriptors(this);

base.Render(writer);

}



protected override void RenderContents(HtmlTextWriter writer)

{

_tbxAreaCode.RenderControl(writer);

writer.Write(" - ");

_tbxPrefix.RenderControl(writer);

writer.Write(" - ");

_tbxSuffix.RenderControl(writer);

_v.RenderControl(writer);

}



#region IScriptControl Members



public IEnumerable GetScriptDescriptors()

{

ScriptControlDescriptor descriptor = new ScriptControlDescriptor("Patmos.Web.UI.WebControls.PhoneTextBox", this.ClientID);

descriptor.AddElementProperty("Areacode", _tbxAreaCode.ClientID);

descriptor.AddElementProperty("Prefix", _tbxPrefix.ClientID);

descriptor.AddElementProperty("Suffix", _tbxSuffix.ClientID);

descriptor.AddElementProperty("ControlToJumpTo", Page.FindControl(ControlToJumpTo).ClientID);

yield return descriptor;

}



public IEnumerable GetScriptReferences()

{

yield return new ScriptReference("PathTo.PhoneTextBoxFunction.js", this.GetType().Assembly.FullName);

}

}



Type.registerNamespace("Patmos.Web.UI.WebControls");
Patmos.Web.UI.WebControls.PhoneTextBox = function(element) {

Patmos.Web.UI.WebControls.PhoneTextBox.initializeBase(this, [element]);
this._areacode = null;
this._prefix = null;
this._suffix = null;
this._jumpTo = null;
this._keyUpHandler = Function.createDelegate(this, this._onKeyUp);

}

Patmos.Web.UI.WebControls.PhoneTextBox.prototype = {

initialize: function() {
Patmos.Web.UI.WebControls.PhoneTextBox.callBaseMethod(this, 'initialize');
$addHandler(this._areacode, 'keyup', this._keyUpHandler);
$addHandler(this._prefix, 'keyup', this._keyUpHandler);
$addHandler(this._suffix, 'keyup', this._keyUpHandler);

},

dispose: function() {
//Add custom dispose actions here
Patmos.Web.UI.WebControls.PhoneTextBox.callBaseMethod(this, 'dispose');
},

get_Areacode: function() {
return this._areacode;
},

set_Areacode: function(value) {
this._areacode = value;
this.raisePropertyChanged("Areacode");
},

get_Prefix: function() {
return this._prefix;
},

set_Prefix: function(value) {
this._prefix = value;
this.raisePropertyChanged("Prefix");
},

get_Suffix: function() {
return this._suffix;
},

set_Suffix: function(value) {
this._suffix = value;
this.raisePropertyChanged("Suffix");
},

get_ControlToJumpTo: function() {
return this._jumpTo;
},

set_ControlToJumpTo: function(value) {
this._jumpTo = value;
this.raisePropertyChanged("ControlToJumpTo");
},

_onKeyUp: function(e) {
if (e.target.id == this._areacode.id)
{
if (this._areacode.value.length == 3)
{
this._prefix.select();
}
}
else if (e.target.id == this._prefix.id)
{
if (this._prefix.value.length == 3)
{
this._suffix.select();
}
}
else
{
if (this._suffix.value.length == 4)
this._jumpTo.focus();
}
}
}

Patmos.Web.UI.WebControls.PhoneTextBox.registerClass('Patmos.Web.UI.WebControls.PhoneTextBox', Sys.UI.Control);




Second Step - Create PhoneNumber Validator

First of all, I create a class derived from BaseValidator and reprogram some of its properties and methods to make it work in our favor. Because I also want this validator to be able to do same things as RequiredField and RegularExpression validators, I added three properties to handle it and there are MissingPhoneNumberErrorMessage, InvalidPhoneNumberErrorMessage and IsPhoneRequired. Second, I implement EvaluateIsValid method (It is required because it's a abstract method in BaseValidator). This is actually a server side validation method and it is triggered when Page.Validate is called. I override ControlPropertiesValid to make sure that the control to validate is of the type of PhoneNumber TextBox control. Finally, I override AddAttributesToRender and OnPreRender methods. In OnPreRender event, I register its javascript library using client script manager. In AddAttributesToRender, I register its associated properties that will eventually be used in its client validation function. Without registering these attributes, the client validation will not engage.

Here are its programming and javascript library:


public class PhoneValidator : BaseValidator
{
private string _error;

public new string ErrorMessage
{
get { return _error; }
set { throw new NotSupportedException("ErrorMessage is not supported. Please use ErrorMessageForMissingPhoneNumber if phone number is required, or ErrorMessageForInvalidPhoneNumber if phone number contains illegal characters."); }
}

[Browsable(true)]
[Category("Behavior")]
[Themeable(false)]
[DefaultValue(true)]
[Description("Is Phone is required?")]
public bool IsPhoneRequired
{
get
{
return (bool)(ViewState["IsPhoneRequired"] ?? true);
}
set
{
ViewState["IsPhoneRequired"] = value;
}
}

[Browsable(true)]
[Category("Behavior")]
[Themeable(false)]
[DefaultValue("Phone number is required.")]
[Description("Error message for missing areacode.")]
public string ErrorMessageForMissingPhoneNumber
{
get
{
return (string)(ViewState["ErrorMessageForMissingPhoneNumber"] ?? string.Empty);
}
set
{
ViewState["ErrorMessageForMissingPhoneNumber"] = value;
}
}

[Browsable(true)]
[Category("Behavior")]
[Themeable(false)]
[DefaultValue("Phone number is invalid.")]
[Description("Error message for invalid phone number.")]
public string ErrorMessageForInvalidPhoneNumber
{
get
{
return (string)(ViewState["ErrorMessageForInvalidPhoneNumber"] ?? string.Empty);
}
set
{
ViewState["ErrorMessageForInvalidPhoneNumber"] = value;
}
}

protected override void AddAttributesToRender(System.Web.UI.HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);

if (this.RenderUplevel)
{
string clientID = this.ClientID;
PhoneTextBox c = (PhoneTextBox)Page.FindControl(ControlToValidate);
Page.ClientScript.RegisterExpandoAttribute(clientID, "evaluationfunction", "PhoneValidatorEvaluateIsValid");
Page.ClientScript.RegisterExpandoAttribute(clientID, "controlstovalidate", string.Format("{0},{1},{2}", c.AreacodeClientID, c.PrefixClientID, c.SuffixClientID));
Page.ClientScript.RegisterExpandoAttribute(clientID, "areacodecontroltovalidate", c.AreacodeClientID);
Page.ClientScript.RegisterExpandoAttribute(clientID, "prefixcontroltovalidate", c.PrefixClientID);
Page.ClientScript.RegisterExpandoAttribute(clientID, "suffixcontroltovalidate", c.SuffixClientID);
Page.ClientScript.RegisterExpandoAttribute(clientID, "setfocusonerror", this.SetFocusOnError.ToString());
Page.ClientScript.RegisterExpandoAttribute(clientID, "isphonerequired", this.IsPhoneRequired.ToString());
Page.ClientScript.RegisterExpandoAttribute(clientID, "missingphonenumber", this.ErrorMessageForMissingPhoneNumber);
Page.ClientScript.RegisterExpandoAttribute(clientID, "invalidphonenumber", this.ErrorMessageForInvalidPhoneNumber);
Page.ClientScript.RegisterExpandoAttribute(clientID, "validationgroup", this.ValidationGroup);
Page.ClientScript.RegisterExpandoAttribute(clientID, "errormessage", "");
}
}

protected override bool ControlPropertiesValid()
{
if (!(Page.FindControl(ControlToValidate) is PhoneTextBox))
throw new NotSupportedException("ControlToValidate property must point to PhoneTextBox.");
return true;
}

protected override bool EvaluateIsValid()
{
string value = GetPhoneNumber();
if (IsPhoneRequired)
{
if (string.IsNullOrEmpty(value))
{
_error = ErrorMessageForMissingPhoneNumber;
return false;
}
}

if (!string.IsNullOrEmpty(value))
{
Regex regex1 = new Regex("^\\d{10}$");
if (!regex1.IsMatch(value))
{
_error = ErrorMessageForInvalidPhoneNumber;
return false;
}
}
return true;
}

private string GetPhoneNumber()
{
PhoneTextBox c = (PhoneTextBox)Page.FindControl(ControlToValidate);
return c.PhoneNumber;
}

protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);

if (base.RenderUplevel)
{
this.Page.ClientScript.RegisterClientScriptResource(typeof(PhoneValidator), "PathTo.PhoneValidationFunction.js");
}
}
}



function PhoneValidatorEvaluateIsValid(val) {
var isRequired = val.isphonerequired;
if (isRequired.toLowerCase() == 'true')
{
if ((ValidatorTrim(ValidatorGetValue(val.areacodecontroltovalidate)) == '') || (ValidatorTrim(ValidatorGetValue(val.prefixcontroltovalidate)) == '') || (ValidatorTrim(ValidatorGetValue(val.suffixcontroltovalidate)) == ''))
val.errormessage = val.missingphonenumber;
return false;
}

var pattern1 = /^\d{10}$/;

var value = ValidatorTrim(ValidatorGetValue(val.areacodecontroltovalidate)) + ValidatorTrim(ValidatorGetValue(val.prefixcontroltovalidate)) + ValidatorTrim(ValidatorGetValue(val.suffixcontroltovalidate));

if (value.length > 0)
{
if (!pattern1.test(value))
{
val.errormessage = val.invalidphonenumber;
return false;
}
}
return true;
}

Stay tuned for next week's article. I will be showing an example of how to extend client validation functions (You will find out what it means next week!).

Saturday, March 29, 2008

.Net 3.5 Validator Extensibility - Part One

Background


As a .net developer, I have often thought of creating custom validators that would do just what I wanted. Unfortunately, after searching and googling, I can't find any decent article that can guide people like me specifically in direction to how to create a custom .net validator that abides the validation architecture in .Net framework (Maybe there is, but i wasn't able to find it). So, i decided to dig a little deeper to find out how it actually works both on server and on client side. The purpose of this article will be the first of few to give you a bird's eye view of .net framework's validation architecture. Let's get to the point. How do we create our own validator? In order to answer this question, we will need to firstly look at how a validator functions in .net framework by studying its server control and client scripts that perform validations.


Server Side Control


BaseValidator is the base class from which all standard .net validators derived. There are few key methods and property accessors that are necessary to plug in your own validator into .net validation framework:


Properties:


  • ControlToValidate: contains the id of the control with which to validate against. The control can also be of HtmlGenericControl such as Div or Span (This part will be discussed further in Part Two in relation to multiple controls to validate).

  • ErrorMessage: The error message to be displayed when invalid.


Methods:


  • AddAtttibutesToRender: This is where the validator registration takes place. Certain Properties of Validator control are associated with the properties of DOM Element which represents the span html element that the validator control are rendered into. This registration process is a simple task that defines few properties of the DOM Element and gives them values which are obtained from the validator control.

  • ControlPropertiesValid: This is the method that validate the value of certain control properties that are essential to the success of registering your own validator to .net validation framework.

  • EvaluateIsValid: This is the server method triggered when Page.Validate is called. In other words, it is the server side validation method.

  • OnPreRender: This is where you register your own client validation function as a javascript file to be loaded in your html page.

After looking at these key properties and methods of the validator control, we will also need to aquire the knowledge of how it works at client side in details.


Client DOM Element


The Html tag the valiator rendered into is span. Its DOM Element is essential to .net client side validation. Now, I want to show you in brief of its position in client validation. (Beware that the handler with which to download the validation library is different when the request page has ScriptManager. Without it, it uses webresource.axd to download validation library and with it, scriptresource.axd. I will be describing the DOM element's significance with the library downloaded from scriptresource.axd.)


Properties associated with in DOM Element


  • controltovalidate: the element referenced by this id to be bound with evaluationfunction in onchange event.

  • errormessage: the error message to be displayed when the control to validate is invalid.

  • validationGroup: the name of the group of validators to be validated.

Functions associated with in DOM Element


  • evaluationfunction: this method is called when value in DOM Element is changed and the purpose of this function is to validate the value and return the validity of the value.

Another thing needed to know before we go on to next step creating custom validator control is when the DOM Element that respesents span block rendered by validator control from server side control is initialized with the above properties and function. This will gives us insight into how to create a custom validator control that is fully integrated into .net validation framework.

  • Firstly, all validator elements, the span DOM elements, are assigned with each respective validator server control properties and client evaluationfunction in one of the script blocks at the bottom of the page. Usually, it's the third script block from the bottom of the page. It looks like this:

var _tbxFirst_rfv = document.all ? document.all["_tbxFirst_rfv"] : document.getElementById("_tbxFirst_rfv");
_tbxFirst_rfv.controltovalidate = "_tbxFirst";
_tbxFirst_rfv.errormessage = "This field is requried.";
_tbxFirst_rfv.validationGroup = "BibleOrderStepOne";
_tbxFirst_rfv.evaluationfunction = "RequiredFieldValidatorEvaluateIsValid";
_tbxFirst_rfv.initialvalue = "";

  • In the second script block from the bottom of the page, ValidatorOnLoad() is called to hook up the control to validate with ValidatedControlOnBlur, ValidatorOnChange or ValidatedTextBoxOnKeyPress function when onchange/onblur/onkeypress event (depending on the type of the control with which to validate) occurs.

  • ValidatorHookupControlID is called to ensure that each control which the validator references to is attached to the functions that mentions in the above point. This is where it makes possible that you can assign to it a container which contains more than one controls and the function itself will detect that and registers the validation functions to every controls residing in that container, such as a div or span block.

Having knowing the key details of properties and methos of the validator server control, and of the client validation script's how-it-does-stuff, we are ready to see few examples of how to create your own validator server control and client validation script.

To be continued...

Saturday, March 22, 2008

Welcome to Enoch's Thoughts blog

I would like to welcome everyone who come across my blog. In the following weeks, i will be posting few articles in several installments that shows you how to create your own .net validator control. I will begin with digging into the framework specifically how validator control operates inside its structure. And later on, introduce how to create your own validator with targeted single control or multiple. Thank you.