In a previous post I explained a way to use a CustomValidator to validate a range of minimum/maximum checkboxes selected within a CheckBoxList.
Now let's incapsulate that validation logic within a reusable, universal, CheckBoxValidator control.
I like to call it "universal" because it is able to validate:
- a single CheckBox
- a minimum/maximum amount of CheckBoxes within a CheckBoxList
- a minimum/maximum amount of CheckBoxes within any containing Control (for example within a Panel)
Full source code is attached at the bottom of this article.
Let's give a look at some part of the code.
First of all, the CheckBoxValidator has to inherit from BaseValidator and implement at least the most important method a validator needs: EvaluateIsValid.
The CheckBoxValidator also exposes MinimumChecked and MaximumChecked properties to let us define at design-time how many checkboxes are allowed to be selected.
public class CheckBoxValidator : BaseValidator
{
private Control _controlToValidate;
private List<object> _checkBoxes;
private int _minimumChecked = 1;
private int _maximumChecked = int.MaxValue;
public int MinimumChecked
{
get { return _minimumChecked; }
set { _minimumChecked = value; }
}
public int MaximumChecked
{
get { return _maximumChecked != int.MaxValue ? _maximumChecked : 0; }
set { _maximumChecked = value != 0 ? value : int.MaxValue; }
}
protected override bool EvaluateIsValid()
{
int count = getCheckedCount();
return count >= _minimumChecked && count <= _maximumChecked;
}
}
As you can see, the evaluation function gets a count of selected checkboxes and verifies if it is within the allowed range.
Here is the getCheckedCount() method:
private int getCheckedCount()
{
int count = 0;
foreach (object checkBox in _checkBoxes)
{
bool;
if (checkBox is ListItem)
selected = ((ListItem)checkBox).Selected;
if (checkBox is CheckBox)
selected = ((CheckBox)checkBox).Checked;
if (selected)
count++;
}
return count;
}
You can notice the _checkBoxes list actually does not contain only checkboxes. It is a list of objects, and may contain CheckBox objects, as well as ListItem objects.
This is to enable the validator to handle both CheckBox and CheckBoxList objects.
You can find the getCheckBoxes() method in the attached source file.
There is another method the validator needs to override: ControlPropertiesValid.
The BaseValidator invokes this method to verify if the ControlToValidate property is properly set.
protected override bool ControlPropertiesValid()
{
_controlToValidate = Page.FindControl(ControlToValidate);
bool isValid = _controlToValidate is CheckBox || _controlToValidate is CheckBoxList || _controlToValidate.Controls.Count > 0;
if(isValid)
_checkBoxes = getCheckBoxes();
return isValid;
}
The CheckBoxValidator wants the ControlToValidate property to point to a CheckBox, a CheckBoxList, or any other control having at least one child control.
So, this is everything the CheckBoxValidator needs to work on the server-side.
Two more overrides allow us to leverage client-side validation.
They both include a if(EnableClient) condition, so we can disable client-side validation at design-time, like we do with other validators.
I registered two custom (expando) attributes of the validator.
The first attribute (evaluationfunction) lets the framework know which javascript function to invoke for client-side validation.
The second one (CheckedCount) is a custom property that stores the count of selected checkboxes.
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);
if (EnableClientScript)
{
Page.ClientScript.RegisterExpandoAttribute(this.ClientID, "evaluationfunction", "CodeGolem_CheckBoxValidator", false);
Page.ClientScript.RegisterExpandoAttribute(this.ClientID, "CheckedCount", getCheckedCount().ToString(), false);
}
}
Finally, here is the javascript function, registered on the page within the OnPreRender event.
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if (EnableClientScript)
{
string onclick = string.Format("if(this.checked) document.getElementById('{0}').CheckedCount++; else document.getElementById('{0}').CheckedCount--;", ClientID);
foreach (object checkBox in getCheckBoxes())
{
if (checkBox is CheckBox)
((CheckBox)checkBox).Attributes.Add("onclick", onclick);
if (checkBox is ListItem)
((ListItem)checkBox).Attributes.Add("onclick", onclick);
}
Page.ClientScript.RegisterClientScriptBlock(GetType(), "ValidationFunction", string.Format(@"
function CodeGolem_CheckBoxValidator(sender)
{{
return sender.CheckedCount >= {0} && sender.CheckedCount <= {1};
}}
", _minimumChecked, _maximumChecked), true);
}
}
This adds an onclick event handler on each checkbox, incrementing or decrementing the Validator's CheckCount.
Validation passes if the CheckCount property is within the allowed range.
Here is a sample page using the CheckBoxValidator on a single CheckBox
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="CheckBoxValidator_Default" %>
<%@ Register Assembly="App_Code" Namespace="CodeGolem" TagPrefix="CodeGolem" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:CheckBox runat="server" ID="CheckBox1" Text="CheckBox 1" />
<CodeGolem:CheckBoxValidator runat="server" ErrorMessage="Must be checked!" ControlToValidate="CheckBox1" />
<asp:Button runat="server" Text="Submit" />
</form>
</body>
</html>
A sample CheckBoxValidator used with a CheckBoxList:
<asp:CheckBoxList runat="server" ID="CheckBoxList">
<asp:ListItem Text="CheckBox 1" />
<asp:ListItem Text="CheckBox 2" />
<asp:ListItem Text="CheckBox 3" />
<asp:ListItem Text="CheckBox 4" />
</asp:CheckBoxList>
<CodeGolem:CheckBoxValidator runat="server" ErrorMessage="Must check min 2, max 3" MinimumChecked="2" MaximumChecked="3" ControlToValidate="CheckBoxList" />
And finally a sample with multiple checkboxes in a containing control:
<asp:Panel runat="server" ID="CheckBoxPanel">
<asp:CheckBox runat="server" Text="CheckBox 1" /><br />
<asp:CheckBox runat="server" Text="CheckBox 2" /><br />
<asp:CheckBox runat="server" Text="CheckBox 3" /><br />
<asp:CheckBox runat="server" Text="CheckBox 4" />
</asp:Panel>
<CodeGolem:CheckBoxValidator runat="server" ErrorMessage="Must check min 2, max 3" MinimumChecked="2" MaximumChecked="3" ControlToValidate="CheckBoxPanel" />
NOTE:The containging panel may also contain a mix of CheckBox and CheckBoxList controls.
Hope you find it useful in your projects!
Any comment and feedback is greatly appreciated!
Thank you for reading! 
CodeGolem_CheckBoxValidator.zip (1.53 kb)