Introduction
With the richness of .NET, I want more. I want web form controls in ASP.NET that have built in validation -- no messing with all the validation controls! You should be able to just create an IntegerTextBox
or a MoneyTextBox
, etc. and specify that it won't accept being blank or that it has a value within a range, etc. Plus, it should return the value in the right format so I don't have to mess with it. I would even like it to change colors when there is an issue.
So, I have struggled to figure out how to do this so you won't have to.
Implementing the IValidator interface
I delved into the SDK documentation and discovered that any control can be a page validator if it implements the IValidator
interface. Here is a simple example derived from TextBox
.
using System;
using System.Web.UI.WebControls;
using System.Web.UI;
namespace MyValidatingControls {
public class TextBox : System.Web.UI.WebControls.TextBox, IValidator {
private bool _valid = true;
private string _errorMessage = "";
public bool IsValid {
get { return _valid; }
set { _valid = value; }
}
public string ErrorMessage {
get { return _errorMessage; }
set { _errorMessage = value; }
}
public void Validate() {
}
}
}
Of course this doesn't do anything, but the code above fulfills the basic IValidator
contract (i.e. it will compile). I created two private fields to hold the state and the error message, if there is one. To ensure our validator is executed, we have to add our validator to the list of page validators for the containing page.
I did a little homework reading the SDK and it mentions that validators add themselves to the list during initialization. IValidator
s basically register themselves. So, we override OnInit
and OnUnload
to add and remove our validator from the page's Validators
list.
protected override void OnInit(EventArgs e) {
base.OnInit(e);
Page.Validators.Add(this);
}
protected override void OnUnload(EventArgs e) {
if (Page != null) {
Page.Validators.Remove(this);
}
base.OnUnload(e);
}
Finishing the "Setup"
Before we can write our validator, I want to setup a few more helper items that will make things a little neater. I don't want to have to provide error messages based on each one of the validation issues. I will program those into the control, based on what type of data it should expect. Therefore, I need to provide a little more information to the control so that it can properly give error messages.
I will add a property called FriendlyName
which will be used in all error messages to refer back to this control. So, if the control we are asking for has an ID of RetailPrice
we make our FriendlyName
be Retail Price
or whatever it is labeled on the page.
private string _friendlyName = "";
public string FriendlyName {
get { return _friendlyName; }
set { _friendlyName = value; }
}
Last, before we write our validation, I want to update IsValid
to change my control to a light red color if it is invalid.
public bool IsValid {
get { return _valid; }
set {
_valid = value;
if (!_valid) {
this.BackColor = Color.LightCoral;
}
else {
this.BackColor = Color.White;
}
}
}
No blanks allowed
For our first validation, lets make it optional that the text field won't accept blanks. We need to make a property that can be set to "enable" this validation.
private bool _blankAllowed = true;
public bool AllowBlank {
get { return _blankAllowed; }
set { _blankAllowed = value; }
}
Finally, we can write the Validation
function and place it on a web page.
public virtual void Validate() {
this.IsValid = true;
if (!this.AllowBlank) {
bool isBlank = (this.Text.Trim() == "");
if (isBlank) {
this.ErrorMessage =
String.Format("'{0}' cannot be blank.",
this.FriendlyName);
this.IsValid = false;
}
}
}
Expanding on the idea
Now that we have a basic text field with built-in validation, we can expand on the idea and create more interesting validating text box controls.
The next simple expansion on the idea would be an IntegerTextBox
. I want my IntegerTextBox
to set the range of valid values, but still we can allow for blanks. So, like before, we add the needed properties.
Since we built the basic TextBox
, we only need derive from it and override the Validate
and add new properties and we are in business.
private int _minValue = Int32.MinValue;
private int _maxValue = Int32.MaxValue;
public int MinValue {
get { return _minValue; }
set {
_minValue = value;
if (_minValue > _maxValue) {
int swap = _minValue;
_minValue = _maxValue;
_maxValue = swap;
}
}
}
public int MaxValue {
get { return _maxValue; }
set {
_maxValue = value;
if (_minValue > _maxValue) {
int swap = _minValue;
_minValue = _maxValue;
_maxValue = swap;
}
}
}
Then, we update our Validate
method and add a native value output.
public override void Validate() {
this.IsValid = true;
bool isBlank = (this.Text.Trim() == "");
if (isBlank) {
if (!AllowBlank) {
this.ErrorMessage = String.Format("'{0}' " +
"cannot be blank.", this.FriendlyName);
this.IsValid = false;
}
} else {
try {
_value = Int32.Parse(this.Text);
if (_value < this.MinValue) {
this.ErrorMessage = String.Format("'{0}' cannot " +
"be less than {1}",
this.FriendlyName, this.MinValue);
this.IsValid = false;
}
if (_value > this.MaxValue) {
this.ErrorMessage = String.Format("'{0}' " +
"cannot be more than {1}",
this.FriendlyName, this.MinValue);
this.IsValid = false;
}
} catch {
this.ErrorMessage = String.Format("'{0}' " +
"is not a valid integer.", this.FriendlyName);
this.IsValid = false;
}
}
}
public int Value {
get { return _value; }
set {
_value = value;
this.Text = _value.ToString();
}
}
Conclusion
That's about it! Just extend on this class and you can create a DateTextBox
or a CurrencyTextBox
all with built-in validation. I have included a sample usage page that shows how nice it works.
Before we had:
<asp:TextBox id="Number" runat="server"/>
<asp:RequiredFieldValidator id="RequiredFieldValidator2"
ControlToValidate="Number"
Text="'Number' cannot be blank." runat="server"/>
<asp:RangeValidator id="Range1" ControlToValidate="Number"
MinimumValue="0" MaximumValue="100"
Type="Integer" Text="The value must be from 0 to 100!"
runat="server"/>
Now we have:
<MyControls:IntegerText id="Number"
FriendlyName="Number" MinValue="0" MaxValue="100"
AllowBlank="false" runat="server">
Mind you, my classes are far from finished nor does it provide the exact functionality as existing validation controls. One definite improvement would be to add client side scripting so that all validation doesn't happen on the server.
But, for someone like myself who doesn't use Visual Studio .NET, this can save a lot of typing and setting of properties.
I wanted everyone to see an example of how this is done, in case you are looking for the same kind of thing. Maybe in the future I will feel inspired enough to show you the completed set of classes I use to do this with.
History
I noticed in the SDK that adding the validator should occur in Init
and not Load
. So, I updated the text and code samples. I also added remove.