Introduction
Continuing the trend introduced by my previous article Numeric Edit Box, I'm going to use the same technique of validating the text string, reducing one char at a time from the end until I'll get a valid number, this time - for Web Pages. And, because Validator
s exist, this time I'll take a different approach: I'll integrate the code, not in an edit control, but in a validator.
Challenges
A big challenge is to make the validation work on different controls containing a Text
field or property. Well, Paul Ingles tells in his article "Credit Card Validator control for ASP.NET" that most controls have a Text
property and it does not make sense to validate the text in a button. Guess what: BaseValidator
does not accept such controls! That would let us only TextBox
es and ComboBox
es, so working with polymorphic methods would do. However, we may encounter some other custom controls as a CustomComboBox
, for which we cannot prepare our code just using type checking, because at design time, we cannot know such types. The solution would be to use Reflection:
string text = "";
Control ctrl = this.Page.FindControl(ControlToValidate);
Type t = ctrl.GetType();
System.Reflection.FieldInfo fi = t.GetField("Text");
if (fi!=null) {
text = (string)(fi.GetValue(ctrl));
} else {
System.Reflection.PropertyInfo pi = t.GetProperty("Text");
if (pi!=null)
text = (string)(pi.GetValue(ctrl, new object [] {}));
else
throw new Exception("ControlToValidate " +
"should have a Text field or property.");
}
The above code makes sure that the ControlToValidate
has either a Text
field or property. It does not make sense to test for the existence of the control, since the BaseValidator
already signals the absence of a suitable control to validate. We could override the ControlPropertiesValid
method, but the basic behavior is good enough. Also, we need not overload the code with a more specific exception since we'll catch this exception to the very next level in the same method:
protected override bool EvaluateIsValid() {
try {
} catch (Exception e) {
Text = e.Message+" "+e.InnerException;
return false;
}
}
So, whatever error comes directly (with throw new Exception
) or indirectly (unknown reasons), this code will catch and display it in the Text
of the validator.
Express Yourself
HTML Edit Controls do not have the SelStart
and SelLength
properties that I've previously used to highlight the error in Windows controls. Besides, we cannot call individually validating actions by the time we exit each control. While that is still possible, is not advisable: each call means a round trip to the server and back, so - unless we do the validation on client (and in this article, we do not!) - is better to pack the validation of all controls in just one submit. That means that I'll have to produce another manner of expression for explicitly showing where the error occurs.
Of course, the easiest way is to fragment the text from the input control using a separator, something like:
string s = text;
int l = s.Length;
if (s.Length!=l) {
Text = "<i>"+ErrorMessage+":</i> " + s +
"->"+text.Substring(s.Length,l-s.Length);
return false;
} else return true;
Tip: italic makes the error message more visible.
The result looks ... not very well:
Then, using the control from my previous article "Simple WebControl to Display Unicode Tables", I got some fancy characters to use as separator:
Text = "<i>"+ErrorMessage+":</i> "+ s +
"\u25ba"+text.Substring(s.Length,l-s.Length);
Too loaded... besides, for other cultures or implementations, these chars might look different - I did not investigate and this is a risk.
Other variations: strike out (123abc) - close, but never liked how strike-out font comes on the screen; underline (123abc) - good enough, but not convincing (or the other way?...)
And then, it hits me:
HTML Tables are very versatile
What a ... HTML tables have to do with validation controls? Well, I could use an HTML table for the text of the message. This way, I can color the background of the error part of the entry:
Text = "<TABLE border=\"0\" cellpaddingleft=\"2\" " +
"cellpaddingright=\"1\" cellspacing=\"0\">"
+"<tr><td><font color=\""
+System.Drawing.ColorTranslator.ToHtml(ForeColor)+"\"><i>"
+ErrorMessage+": </i><b>"+s+"</font></b></td>"
+"<td bgcolor=\""+System.Drawing.ColorTranslator.ToHtml(ForeColor)
+"\"><font color=\""
+System.Drawing.ColorTranslator.ToHtml(BackColor)+"\"><b>"
+text.Substring(s.Length,l-s.Length)+
"</b></font></td></tr></TABLE>";
With a bit of attention to details - cellpaddingleft=2
and cellpaddingright=1
- I'm adding just the needed extra space for making the two parts of the entry not to look too close to the margins. Using the ForeColor
and the BackColor
of the validator control and adding a Solid
border with 1px BorderWidth
, here is the final result:
Validation Core
Like in the previous article, the validation starts with the full entry string s
and then it reduces one character at a time from the end, until the Parse
method of the corresponding type succeeds; this time, I'm not messing anymore with the float
type - I'm going to allow the validator work on either integer
s or double
s. For that, I need to define the NumValidate
enumeration and a property through which I can choose what the control ToValidate
is. Note also the other needed properties with nothing special, just designated for the Appearance
category, given defaults and descriptions both for the Property Inspector and for the XTML documentation:
string s = text;
int l = s.Length;
while (s.Length>0)
try {
Value = (ToValidate==NumValidate.Integer)
? int.Parse(s, NumStyles)
: double.Parse(s, NumStyles);
break;
} catch {
s = s.Substring(0,s.Length-1);
}
if (s.Length!=l) {
Text =
return false;
} else return true;
}
private double _value;
[Category("Appearance"),
DefaultValue(0),
Description("The numeric Value of the Validated control")]
public virtual double Value {
get { return _value; }
set { _value = value; }
}
private NumValidate toValidate = NumValidate.Integer;
[Category("Appearance"),
DefaultValue(NumValidate.Integer),
Description("ToValidate an integer or a double value")]
public NumValidate ToValidate {
get { return toValidate; }
set { toValidate = value; }
}
private NumberStyles numStyles=
NumberStyles.Number | NumberStyles.AllowExponent;
[Category("Appearance"),
DefaultValue(NumberStyles.Number | NumberStyles.AllowExponent),
Description("Customize the numeric validation number style.")]
public NumberStyles NumStyles {
get { return numStyles; }
set { numStyles = value; }
}
public enum NumValidate {
Integer,
Double
}
External Validation
CancelEventHandler
is not suitable anymore for this project: first of all, that was a Windows event, secondly - it applies when one tries to exit the control, which is not the case here. We need another event with a different set of arguments, and I'm going to make one. To begin with, I'll introduce the ValidateEventArgs
type as a new class in the project:
public class ValidateEventArgs : EventArgs {
public ValidateEventArgs():this("") {
}
public ValidateEventArgs(string text) {
_Validated=false;
_Value=0;
_Text=text;
}
private bool _Validated;
public bool Validated {
get { return _Validated; }
set { _Validated = value; }
}
private string _Text;
public string Text {
get { return _Text; }
set { _Text = value; }
}
private Double _Value;
public Double Value {
get { return _Value; }
set { _Value = value; }
}
}
It inherits from the basic EventArgs
and has the responsibility to convey back and forth the text of the ControlToValidate
, the new value when the external validation works and the acceptance of the procedure. Note - I'll need to call the constructor with a string as the text for validation, and by default the validation fails.
Now, I can use this event argument structure in a new event declared in the main class:
public delegate void ValidatingEventHandler(object sender,
ValidateEventArgs e);
public event ValidatingEventHandler Validating;
string s = text;
int l = s.Length;
if (Validating!=null) {
ValidateEventArgs e = new ValidateEventArgs(s);
Validating(this,e);
if (e.Validated) {
Value = e.Value;
return true;
}
while
While that was the approach from the first article, this code can improve in two ways:
Separate External Validator Handlers
To use this event, we usually go on the main form, click the validator WebControl, go in Properties panel, in the Events tab, and double-click on the Validating
event; Visual Studio creates a new method in the code-behind where we add the new validation code:
private void NumEditValidator1_Validating(object sender,
TH.WebControls.ValidateEventArgs e) {
if (e.Validated) return;
else if (e.Text=="Pi") {
e.Value=Math.PI;
e.Validated=true;
}
}
Here, I've validated just the named constant Pi
. Let's say I want to add validation for another constant like E
without touching this handler. This assumption is valid, since the first handler could be embedded in an inherited control-form our NumEditValidator
, or because we may create very different types of external validation and we do not want to mix them up. For example, one handler could deal with mathematical constants, while another can search some data in a database, and another could try to solve some expressions. Remember that our event is still a delegate and we can add multiple event handlers with the +=
operator. To do that, we have to adjust just a little bit in our code:
foreach (ValidatingEventHandler validating in Validating.GetInvocationList()) {
validating(this,e);
if (e.Validated) {
Value = e.Value;
return true;
}
We still need a little trick for being able to add multiple handlers from Visual Studio: go in the main form's code behind and open the region Web Form Designer generated code. In the method InitializeComponent
, find the line which adds the Validator's handler. The idea would be to duplicate that line and to modify it for accepting a second handler. But caution: the method has a very important warning:
private void InitializeComponent() {
This translates in whatever you add here may disappear sooner or later! However, deleting the line won't make Visual Studio to put it back; instead, the corresponding handler disappears from the Properties box. This completes the idea in the following manner: cut the line and paste it in the OnInit
method just after the call to InitializeComponent
; duplicate this line and the corresponding handler and modify them in the way you need. For example, let's add another one to decode the constant E
:
#region Web Form Designer generated code
override protected void OnInit(EventArgs e) {
InitializeComponent();
this.NumEditValidator1.Validating += new
TH.WebControls.NumEditValidator.ValidatingEventHandler(this.ValidatingPi);
this.NumEditValidator1.Validating += new
TH.WebControls.NumEditValidator.ValidatingEventHandler(this.ValidatingE);
base.OnInit(e);
}
private void InitializeComponent() {
#endregion
private void ValidatingPi(object sender, TH.WebControls.ValidateEventArgs e) {
if (e.Validated) return;
else if (e.Text=="Pi") {
e.Value=Math.PI;
e.Validated=true;
}
}
private void ValidatingE(object sender, TH.WebControls.ValidateEventArgs e) {
if (e.Validated) return;
else if (e.Text=="E") {
e.Value=Math.E;
e.Validated=true;
}
}
Guess what: it works!
Feedback from External Validator Handlers
Now, let's type instead of Pi
an error - Pie
: we get the message:
And this is not nice: without another hint (help, use manual, technical support, hot line, topic forum, a little voice etc.) one can never find that the control supports the constant Pi
. Then, why didn't I apply the same idea with numbers in the first place? Let's just move the external validation code inside the while
loop which cuts one char at a time from the end of the entry string until parsing succeeds:
ValidateEventArgs e = new ValidateEventArgs(s);
while (s.Length>0)
try {
if (Validating!=null) {
e.Text = s;
foreach (ValidatingEventHandler validating
in Validating.GetInvocationList()) {
validating(this,e);
if (e.Validated) {
Value = e.Value;
break;
}
}
}
if (!e.Validated) {
Value = (ToValidate==NumValidate.Integer)
? int.Parse(s, NumStyles)
: double.Parse(s, NumStyles);
}
break;
} catch {
s = s.Substring(0,s.Length-1);
}
That's all folks! Now, let's draw some -
Conclusions
Programming is fun, and by the time the project approached the final stage, I let it go, but not without some common sense: sometimes the sources of inspiration are so narrow that a good forum is the last hope before wishing that a little voice told you what to do (no - I don't hear some!). I've tried here to produce a control for those in need for help, hints, or support as little as possible. You tell me if I've succeeded!
In this exercise, I've discovered the Microsoft way to do validation with separate controls instead of those used for data entry. A round trip to the server for validation is not too expensive when the validation for all edit-controls in a page can happen in just one PostBack. The BaseValidator
has enough properties, which if used properly can lead to great results. Extending the event system of .NET can produce controls near perfection, while System.Reflection
assembly gives better solutions than polymorphism.
I hope you've enjoyed this project as much as I did!