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!


If you liked this post, please be sure to subscribe to my RSS Feed.


    Finally.. after many modifications, the AJAX AutoComplete for ASP.NET 2.0 web user control is available for download.  This web control contains a text box. As you type in the box, suggestions fill in a drop down, very similar to Google Suggest. 

    There are many configuration options, so if you are looking for a quick fix, you shouldn't have to get your hands too dirty.  On the other hand if you're looking for a base to build on, all of the C# and javascript code is included - hack away to your heart's content. If you make something cool, leave a comment and let me know about it. 

    The styling for all of the components is done with CSS contained in an external file.  All you need to do is set the control's properties to the class names for each element.  There are other display options, optional features like AutoPostBack, case-sensitivity, server-side caching and other search options as well.  For more information on what you can do, read the documentation.

    Or if you're one of those people who are more of a hands-on learner, you can play with the demo which is also included in the .zip file.

    Download the Visual Studio 2005 Solution .zip file (18k)

    I designed this control to be very easy to use, however it does assume you have a working knowledge of ASP.NET.  I will do my best to answer any questions, but the software is offered with no guarantees.


    If you liked this post, please be sure to subscribe to my RSS Feed.



      I've noticed over the past few days that I've been getting a lot of hits on the AJAX Autocomplete post I made, so I've decided to package up what I've done into a downloadable and reusable user control.  At the moment, I'm taking a lot of the options I had hardcoded (case sensitivity, whether it autocompletes based on "Starts with" or "Contains") and creating them as Parameters.

      Check back soon for an update or better yet, subscribe to the RSS feed to know when it's been released.

      EDIT:  It is now available - More Information

       


      If you liked this post, please be sure to subscribe to my RSS Feed.


        Edit: 4/12/06

        I'm going to make this available as a download as soon as I have it cleaned up.  See here for more details.

        Edit: 4/21/06: It is now available - More Information

        Tonight, I was able to get my category AutoComplete feature working using AJAX in ASP.NET.  I have a query text box that fill the matching categories into a textbox as you type, similar in idea to Google Suggest.

        Here's how it works..

        As you type, the onKeyPress event is triggered, sending the query back to the server in the background.  On the first call of the page, it checks the Cache to see if the category list is already loaded.  If not, it goes out to the database and loads it into a Generic List object and then saves that object into Cache.  If it is located in Cache, it just grabs the Cache Item, casts it to List<string> and uses that.

        Then I take what the user has typed so far and use List.FindAll() to find all matching values. Since it's coming out of memory most of the time, it's pretty fast.  There also aren't many categories, so even when it needs to go out to the database, it doesn't take too much time. Once it has all of the matching values, they are concatenated with commas and sent back to the client side.

        The Javascript then takes that string, splits it along the commas and adds a new line to the textbox.


        function fillAutoComplete(result, context) {
            fields=result.split(',');
            var txtAutoComplete = getObj('AutoComplete');
            txtAutoComplete.obj.value='';
            for (var i=0; i<fields.length; i++) {
                txtAutoComplete.obj.value += fields[i]+'\n';
           }
        }

        Currently, I have it just filling a textbox for test purposes, but eventually, it will be in a clickable dropdown.  The key to this is making it easy for the user to select a category of food to either add to their daily diet tracking or for the classification of custom foods.  This UI interface is a great blend of browsing and searching.  As you type, a search is performed which progressively limits the items you have to browse through.

        It's coming along.. slowly, but surely.


        If you liked this post, please be sure to subscribe to my RSS Feed.



          Subscribe

          About the author

          Wayne Hunt I am a web application developer and second degree black belt living in Providence, RI.

          More about Me..

          E-mail me Send mail

          Google Connect

          Dugg Sites

          Disclaimer

          The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

          © Copyright 2010

          Sign in