One of the most common tasks we perform as ASP.NET developers is adding validated fields to a form. Typically, this involves adding:
- a label to tell the user what information is expected
-
a TextBox, DropDrown, CheckBox or other element to collect the data
- a Validator to check whether the data entered is good or not
Rather than always adding these three elements, we can combine them into a single reusable server control. The less elements we need to add to a page, the less time it takes to do so.
Step 1: RequiredInput abstract class
An abstract class is a base class that has no implementation of its own. It must be used as a building block for another class. Since each type of input (TextBox, DropDownList, etc) has a slightly different implentation, We can collect all of the functionality that is common to each and put them into the RequiredInput abstract class, which is what we will use as the base class for our other controls such as the Required TextBox.
[NOTE: Since I'm using Visual Studio 2008, I used the newer Automatic Properties. This project is written for and completely compatible with the .NET 2.0 Framework. If you're going to be using Visual Studio 2005 to edit this project, you will need to implement private variables]
1:
2: using System.ComponentModel;
3: using System.Web.UI.WebControls;
4: using System.Web.UI;
5:
6: namespace OurCurrentFuture
7: {
8: /// <summary>
9: /// A base for Labeled, Required Form Fields
10: /// </summary>
11: public abstract class RequiredInput : CompositeControl
12: {
13: #region " Members "
14:
15: protected RequiredFieldValidator rfvValidator;
16: private bool _required = true;
17: protected Label lblLabel;
18:
19: #endregion
20:
21: #region " Properties "
22: /// <summary>
23: /// Different positions for the Label
24: /// </summary>
25: public enum LabelPosition
26: {
27: /// <summary>
28: /// Places the Label before the input
29: /// </summary>
30: Before,
31: /// <summary>
32: /// Places the Label after the input
33: /// </summary>
34: After
35: }
36:
37: /// <summary>
38: /// Gets or sets the label.
39: /// </summary>
40: /// <value>The label.</value>
41: [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
42: public Label Label
43: {
44: get;
45: set;
46: }
47:
48: /// <summary>
49: /// Gets or sets the label location.
50: /// </summary>
51: /// <value>The label location.</value>
52: public LabelPosition LabelLocation
53: {
54: get;
55: set;
56: }
57:
58: /// <summary>
59: /// Gets or sets a value indicating whether this <see cref="RequiredInput" /> is required.
60: /// </summary>
61: /// <value><c>true</c> if required; otherwise, <c>false</c>.</value>
62: public bool Required
63: {
64: get
65: {
66: return _required;
67: }
68: set
69: {
70: rfvValidator.Enabled = value;
71: _required = value;
72: }
73: }
74:
75: /// <summary>
76: /// Gets or sets the validator.
77: /// </summary>
78: /// <value>The validator.</value>
79: [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
80: public RequiredFieldValidator Validator
81: {
82: get;
83: set;
84: }
85:
86: #endregion
87:
88: #region " Constructors "
89:
90: /// <summary>
91: /// Initializes a new instance of the <see cref="RequiredInput" /> class.
92: /// </summary>
93: public RequiredInput()
94: {
95: EnsureChildControls();
96: LabelLocation = LabelPosition.Before;
97: Validator.Text = "*";
98: Validator.Display = ValidatorDisplay.Dynamic;
99: }
100:
101: #endregion
102:
103: #region " Methods "
104:
105: /// <summary>
106: /// Renders the label.
107: /// </summary>
108: /// <param name="writer">The writer.</param>
109: protected void RenderLabel(HtmlTextWriter writer)
110: {
111: Label.RenderControl(writer);
112: writer.Write(writer.NewLine);
113:
114: if (string.IsNullOrEmpty(Validator.ErrorMessage) && !string.IsNullOrEmpty(Label.Text))
115: {
116: Validator.ErrorMessage = string.Format("{0} is Required", Label.Text);
117: }
118:
119: Validator.RenderControl(writer);
120: writer.Write(writer.NewLine);
121:
122: }
123:
124: /// <summary>
125: /// Called by the ASP.NET page framework to notify server controls that use composition-based
implementation to create any child controls they contain in preparation for posting back or rendering.
126: /// </summary>
127: protected override void CreateChildControls()
128: {
129: Label = new Label();
130: Label.ID = "lblLabel";
131:
132: Validator = new RequiredFieldValidator();
133: Validator.ID = "rvfValidator";
134: }
135:
136: #endregion
137: }
138: }
}
Let's take a look at some of the important pieces there:
public abstract class RequiredInput : CompositeControl
I'm deriving RequiredInput from the CompositeControl class. This gives me some of the ASP.NET plumbing I need to create a control.
LabelPosition - An Enum allowing use to easily specify whether we want the Label control before or after the Input control
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public Label Label
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public RequiredFieldValidator Validator
Here, we've used the DesignerSerializationVisibility attribute set to "Contents" to specify that the designer should expose the properties of the underlying object. This will allow us to easily set the properties of the Label and Validator from our own server control later on.
In the constructor, we first call EnsureChildControls. This checks to see if we have already created the controls that are going in our server control.
If they have not been created yet, then CreateChildControls is called to make sure they are available for us to work with. CreateChildControls simply creates the Label and Validator objects. If we tried to set properties before these objects were created, we'd get Null Reference Exceptions.
Finally, we have the RenderLabel method which is used by the control to actually generate the HTML for the Label and the Validator. In order to make it even esier to use this control, we'll set the Validator.ErrorMessage property to a simple default if the ErrorMessage value is empty, but the Label is set. That way we don't have to specify the ErrorMessage, but it's still populated with something useful.
Step 2: RequiredTextBox Control
1: using System;
2: using System.ComponentModel;
3: using System.Web.UI;
4: using System.Web.UI.WebControls;
5:
6: namespace OurCurrentFuture
7: {
8: /// <summary>
9: /// A Textbox with a built in label control and RequiredFieldValidator
10: /// </summary>
11: [ValidationPropertyAttribute("Text")]
12: public class RequiredTextBox : RequiredInput
13: {
14:
15: #region "Properties"
16:
17: /// <summary>
18: /// Gets or sets the text.
19: /// </summary>
20: /// <value>The text.</value>
21: [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
22: public TextBox TextBox
23: {
24: get;
25: set;
26: }
27:
28:
29: /// <summary>
30: /// Gets or sets the text.
31: /// </summary>
32: /// <value>The text.</value>
33: public string Text
34: {
35: get
36: {
37: return TextBox.Text;
38: }
39: set
40: {
41: TextBox.Text = value;
42: }
43: }
44: #endregion
45:
46: #region "Constructors"
47:
48: /// <summary>
49: /// Initializes a new instance of the <see cref="RequiredTextBox" /> class.
50: /// </summary>
51: public RequiredTextBox()
52: {
53: EnsureChildControls();
54: }
55:
56: #endregion
57:
58: #region "Methods"
59:
60: /// <summary>
61: /// Called by the ASP.NET page framework to notify server controls that use composition-based
implementation to create any child controls they contain in preparation for posting back or rendering.
62: /// </summary>
63: protected override void CreateChildControls()
64: {
65: base.CreateChildControls();
66:
67: TextBox = new TextBox();
68: TextBox.ID = "vtbTextBox";
69: Controls.Add(TextBox);
70:
71: Label.AssociatedControlID = TextBox.ID;
72: Controls.Add(Label);
73:
74: Controls.Add(Validator);
75: Validator.ControlToValidate = TextBox.ID;
76: if (string.IsNullOrEmpty(Validator.ErrorMessage) && !string.IsNullOrEmpty(Label.Text))
77: {
78: Validator.ErrorMessage = String.Format("{0} is Required", Label);
79: }
80: Validator.Enabled = Required;
81: }
82:
83:
84: /// <summary>
85: /// Writes the <see cref="T:System.Web.UI.WebControls.CompositeControl" /> content to the
specified <see cref="T:System.Web.UI.HtmlTextWriter" /> object, for display on the client.
86: /// </summary>
87: /// <param name="writer">An <see cref="T:System.Web.UI.HtmlTextWriter" /> that represents
the output stream to render HTML content on the client.</param>
88: protected override void Render(HtmlTextWriter writer)
89: {
90: if (!string.IsNullOrEmpty(CssClass))
91: {
92: writer.AddAttribute("class", CssClass);
93: }
94:
95: writer.RenderBeginTag("div");
96:
97: if (LabelLocation == LabelPosition.Before)
98: {
99: RenderLabel(writer);
100: }
101:
102: TextBox.RenderControl(writer);
103: writer.Write(writer.NewLine);
104:
105: if (LabelLocation == LabelPosition.After)
106: {
107: RenderLabel(writer);
108: }
109:
110: writer.RenderEndTag();
111: }
112:
113: #endregion
114:
115: }
116: }
We next create a Control that inherits from our abstract RequiredInput class. For a simple example, we'll add a TextBox, again specifying the DesignerSerializationVisibility to DesignerSerializationVisibility.Content so that we will be able to access the properties of the TextBox. We will also expose a public property "Text" to be able to easily grab the text value from the TextBox.
In CreateChildControls, we first call base.CreateChildControls so that our Label and RequiredFieldValidator are created by the RequiredInput base class. Then we create the TextBox object and add it to the Control, give it an ID and then hookup the Label's AssociatedControlID to this ID. This will render the Label as an actual <label> tag in the HTML and move the cursor to the TextBox when the label itself is clicked.
We also add the Validator to the Control and set the ControlToValidate property to the TextBox's ID and we set the Enabled property of the Validator to the Required value of the RequiredTextBox control. This allows us to shut off validation of the TextBox if we don't need it.
In the Render method, write a <div> wrapper for the Label, Validator, and TextBox, adding a "class" attribute if the RequiredTextBox's CssClass has been set. We then render out the contents of the Label, Validator and Textbox in the correct order based on the LabelLocation.
Step 3: Using the Controls
To use these controls, you can build the server control .dll and add it as a reference to your web project, then register the control:
<%@ Register Assembly="RequiredInput" Namespace="OurCurrentFuture" TagPrefix="cc1" %>
.. and declare it in your code
<cc1:RequiredTextBox ID="rtbName" runat="server" Label-Text="Name" />
Notice the Property "Label-Text". Since we used the DesignerSerializationVisibility attribute above, we can now access all of the Properties for the TextBox, Label, and Validator right from the .aspx page. For example, to set the CSSClass of each, you could declare your control like this:
<cc1:RequiredTextBox ID="rtbName" runat="server"
CssClass="control"
TextBox-CssClass="input"
Label-Text="Name"
Label-CssClass="label"
Validator-CssClass="error"
/>
Click here to download a .zip file containing all of the code in a Visual Studio 2008 solution file along with a demo web project showing how it can be used.
Got ideas on how to make this better? Post a comment and let me know!