Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Numeric Validator for Web Text Controls

0.00/5 (No votes)
22 Sep 2004 1  
Bulding a numeric validator for WebControls containing a Text property.

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 Validators 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 TextBoxes and ComboBoxes, 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);
//if (ctrl==null) throw new Exception("No 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 {
        // Get control to validate

        // Validate core

    } 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:

// Get control to validate

string s = text;
int l = s.Length;
// Validate core

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 integers or doubles. 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:

// Get Control to validate

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;
/// <summary>

/// The numeric Value of the Validated control

/// </summary>

[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;
/// <summary>

/// ToValidate an integer or a double value

/// </summary>

[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;
/// <summary>

/// Customize the numeric validation number style.

/// </summary>

[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;
    
//...

// Get Control to Validate

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:

/// <SUMMARY>

/// Required method for Designer support - do not modify

/// the contents of this method with the code editor.

/// </SUMMARY>

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!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here