You can download the files here.
Until a few weeks ago I did not know about a feature called Control State. I knew of course about ViewState but not Control State. I would love to know everything about everything in this game i.e. computing, but as you will agree that is extremely difficult to do. Another ting I have found myself doing in the past is for example using a client side script helper, e.g. calendar date picker, to populate a textbox. This looks a lot nicer, helps the user and reduces validation errors. Now getting this value was a case of accessing the Request data for that specific control. i.e.
Request.Form[MyTextBox.UniqueID]
Because we used a client side script to populate the control the server side TextChanged event would not get fired so we could not unfortunately do this:
So, on this occasion a situation arose where by I had to create a few controls, being a custom HierarchialDataSource, a custom HierarchialDataSourceControl and also a nice custom TreeView with valid CSS. I opted to create my own as opposed to CSS Control Adapters simply because I require some really bespoke features, plus I love learning how to make these things lol.
Now I am not going into these controls on this post but rather features of them, meaning the Control State and IPostBackDataHandler interface. The control I want to make today involves 4 fields of which one will be a date field which will use the Ajax Control Toolkit Calendar Extender. The fields will be Title, Firstname, Lastname and Date of Birth.
In title I have also stated "Method Single Responsibility" and my reason for this is because only the other day I was listening to one of Scott Hanselman's pod casts on which he was speaking to Robert C. Martin or "Uncle Bob" as he is known in the programming world. He stated that Single Responsibility should also be applied to methods as well or reason for change. It was stated that lots of methods with smaller amounts of code wrapped around a single responsibility was easier to read, debug and maintain that fewer methods with bloated amounts of code which would undoubtedly contain lots of indentations. So I am applying this, and wonder why I have never done this before, but this is all a learning curve.
Ok the first thing we will do on this control is create the fields and properties we require. We will also create another class inside the control with the exact fields and properties as we have for the control, so copy and paste job; this will be used to control the state of the control in a much more strongly typed fashion, in fact truly strongly typed. So here is the start of the control:
public class PageInformationControl
{
#region Fields and Properties
private string title;
/// <summary>
/// The person's title
/// </summary>
public string Title
{
get { return title; }
set { title = value; }
}
private string firstname;
/// <summary>
/// The person's firstname
/// </summary>
public string Firstname
{
get { return firstname; }
set { firstname = value; }
}
private string lastname;
/// <summary>
/// The person's lastname
/// </summary>
public string Lastname
{
get { return lastname; }
set { lastname = value; }
}
private DateTime dateOfBirth;
/// <summary>
/// The person's date of birth
/// </summary>
public DateTime DateOfBirth
{
get { return dateOfBirth; }
set { dateOfBirth = value; }
}
#endregion
#region StateObject
/// <summary>
/// An object to store the state of the object between postbacks
/// </summary>
[Serializable]
protected class PageInformationControlControlState
{
private string title;
public string Title
{
get { return title; }
set { title = value; }
}
private string firstname;
public string Firstname
{
get { return firstname; }
set { firstname = value; }
}
private string lastname;
public string Lastname
{
get { return lastname; }
set { lastname = value; }
}
private DateTime dateOfBirth;
public DateTime DateOfBirth
{
get { return dateOfBirth; }
set { dateOfBirth = value; }
}
}
#endregion
}
Next we need to add the Base Class and also the Interface we are going to be using.
public class PageInformationControl : CompositeControl,IPostBackDataHandler
Next we want to do the following:
- Override the base OnInit method and register a couple of instructions with the page
- Implement the IPostBackDataHandler, this relies on the point above, i.e. otherwise the interface methods will not be called.
- Override two methods of the base class being
- SaveControlState
- LoadControlSate
Inside the OnInit method we will tell the ASP.NET Page that this control requires two things:
- Control State
- It also needs to be informed of PostBacks so it can alter and update its state
Here is the override OnInt event:
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
//To instruct the page to call the methods require to manage Control State
Page.RegisterRequiresControlState(this);
//Required so that the IPostBackDataHandler are invoked.
Page.RegisterRequiresPostBack(this);
}
Followed by the overridden methods and implemented ones:
protected override object SaveControlState()
{
return base.SaveControlState();
}
protected override void LoadControlState(object savedState)
{
base.LoadControlState(savedState);
}
#region IPostBackDataHandler Members
public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
{
throw new NotImplementedException();
}
public void RaisePostDataChangedEvent()
{
throw new NotImplementedException();
}
#endregion
OK, that is kind of the shell of our control, next we need the interface components which will be the following:
- A DropDownList for the Title of a person
- A TextBox for the Person's Firstname
- A TextBox for the Person's Lastname
- A TextBox for the Person's DateOfBirth
- It is this last one which we will use the AjaxControlToolkit and add a calendar extender. This will populate the textbox from the client side yet you will see how you can strongly type your way to the value.
- An AjaxControlToolkit TextBoxWatermarkExtender for the Firstname field so the client knows what to enter
- An AjaxControlToolkit TextBoxWatermarkExtender for the LastName field so the client knows what to enter
- An AjaxControlToolkit TextBoxWatermarkExtender for the DateOfBirth field so the client knows what to enter
- An AjaxControlToolkit Calendar extender for the DateOfBirthField so we can get good data.
NOTE: If you have not done so already, you need to add the AjaxControlToolkit assembly to your project as a reference. You can find it here: Ajax Control Toolkit at asp.net/ajax
So we will declare them as protected members.
#region User interface
protected DropDownList DropDownListTitle;
protected TextBox TextBoxFirstName;
protected TextBox TextBoxLastName;
protected TextBox TextBoxDob;
protected AjaxControlToolkit.TextBoxWatermarkExtender WatermarkFirstname;
protected AjaxControlToolkit.TextBoxWatermarkExtender WatermarkLastName;
protected AjaxControlToolkit.TextBoxWatermarkExtender WatermarkTextBoxDob;
protected AjaxControlToolkit.CalendarExtender CalendarDob;
#endregion
Next is quite a long run. We want to do the following:
- Override the CreateChildControls method
- Create a virtual method which starts the control generation
- Create virtual methods for handling each part of the user interface, but using the method in the above point to call them in the correct order.
- I have also put in a method which generates a CSS friendly wrapper for each control so as to avoid a table layout.
In each method I will specify the ID of each of the controls to get a real feel for the control. I will also do this so that we can easily follow how i apply the control extenders to the controls.
protected override void CreateChildControls()
{
Controls.Clear();
CreateControlHierarchy();
ClearChildViewState();
}
protected virtual void CreateControlHierarchy()
{
CreateTitle();
CreateFirstName();
CreateLastName();
CreateDateOfBirth();
}
#region Single Responsibility UI Creation Methods
protected virtual void CreateTitle()
{
Control wrapper = GetCssFriendlyControlWrapper();
DropDownListTitle = new DropDownList();
string[] titles = new string[] { "Mr", "Mrs", "Ms", "Miss", "Dr", "Prof" };
foreach (string s in titles)
{
ListItem titleItem = new ListItem(s);
DropDownListTitle.Items.Add(titleItem);
}
ListItem baseItem = new ListItem("Please select title");
DropDownListTitle.Items.Insert(0, baseItem);
wrapper.Controls.Add(DropDownListTitle);
Controls.Add(wrapper);
}
protected virtual void CreateFirstName()
{
Control wrapper = GetCssFriendlyControlWrapper();
TextBoxFirstName = new TextBox();
TextBoxFirstName.ID = this.ID + "_TextBoxFirstName";
WatermarkFirstname = new AjaxControlToolkit.TextBoxWatermarkExtender();
WatermarkFirstname.ID = this.ID + "_WatermarkFirstname";
WatermarkFirstname.WatermarkText = "Firstname";
WatermarkFirstname.TargetControlID = TextBoxFirstName.ID;
wrapper.Controls.Add(TextBoxFirstName);
wrapper.Controls.Add(WatermarkFirstname);
Controls.Add(wrapper);
}
protected virtual void CreateLastName()
{
Control wrapper = GetCssFriendlyControlWrapper();
TextBoxLastName = new TextBox();
TextBoxLastName.ID = this.ID + "_TextBoxLastName";
WatermarkLastName = new AjaxControlToolkit.TextBoxWatermarkExtender();
WatermarkLastName.ID = this.ID + "_WatermarkLastName";
WatermarkLastName.WatermarkText = "Lastname";
WatermarkLastName.TargetControlID = TextBoxLastName.ID;
wrapper.Controls.Add(TextBoxLastName);
wrapper.Controls.Add(WatermarkLastName);
Controls.Add(wrapper);
}
protected virtual void CreateDateOfBirth()
{
Control wrapper = GetCssFriendlyControlWrapper();
TextBoxDob = new TextBox();
TextBoxDob.ID = this.ID + "_TextBoxDob";
WatermarkTextBoxDob = new AjaxControlToolkit.TextBoxWatermarkExtender();
WatermarkTextBoxDob.ID = this.ID + "_WatermarkTextBoxDob";
WatermarkTextBoxDob.WatermarkText = "Date of Birth (yyyy-mm-dd)";
WatermarkTextBoxDob.TargetControlID = TextBoxDob.ID;
CalendarDob = new AjaxControlToolkit.CalendarExtender();
CalendarDob.ID = this.ID + "_CalendarDob.ID";
CalendarDob.Format = "yyyy-MM-dd";
CalendarDob.TargetControlID = TextBoxDob.ID;
wrapper.Controls.Add(TextBoxDob);
wrapper.Controls.Add(WatermarkTextBoxDob);
wrapper.Controls.Add(CalendarDob);
Controls.Add(wrapper);
}
protected virtual Control GetCssFriendlyControlWrapper()
{
return new HtmlGenericControl("div");
}
#endregion
Ok we are nearly ready to test this. What we need to ensure next, is that we handle postbacks correctly, and we maintain state. We are going to make use of the state object which we made above. One alternative is to use a HashTable for example but I like to have a strongly typed version.
So final few things:
- SaveControlState
- LoadControlState
- LoadPostData
- RaisePostDataChangedEvent
- Although I am not demoing this event in this post, this can be used for example in conjunction with custom EventArgs and maybe create a Changed Event, where subscribers can access your data when the subscribed event is fired
protected override object SaveControlState()
{
//Create an instance of the state object and fill it with the
//contents of the properties in the controls
PageInformationControlControlState state = new PageInformationControlControlState
{
DateOfBirth = this.DateOfBirth,
Firstname = this.Firstname,
Lastname = this.Lastname,
Title = this.Title
};
return state;
}
protected override void LoadControlState(object savedState)
{
//If the saved state is null, just return
if (savedState == null)
return;
//Cast the savedState as your state object
PageInformationControlControlState state = (PageInformationControlControlState)savedState;
//Repopulate your properties after postback
Title = state.Title;
Firstname = state.Firstname;
Lastname = state.Lastname;
DateOfBirth = state.DateOfBirth;
}
#region IPostBackDataHandler Members
public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
{
//Make sure you controls have been created
EnsureChildControls();
//Fill the properties using the post data.
Title = postCollection[DropDownListTitle.UniqueID];
Firstname = postCollection[TextBoxFirstName.UniqueID];
Lastname = postCollection[TextBoxLastName.UniqueID];
DateTime outDateTime;
if (DateTime.TryParse(postCollection[TextBoxDob.UniqueID], out outDateTime))
DateOfBirth = outDateTime;
return true;
}
public void RaisePostDataChangedEvent()
{
//throw an updated event
}
#endregion
OK so we are now ready to create a test, were by I will add this new control to the screen and also a button to raise a postback and a textarea which will display an aggregation of the data formatted in a tidy way. On thing I have done at this point is add the reference to the control inside the web.config. I have started off with an ASP.NET WebApplication, if you are using a normal ASP.NET Website and hence using the App_Code folder just omit the assembly attribute but makesure you give your control a namespace inside the App_Code folder and that it relates to what you are about to put into the web.config. The following is inline with my program properties, and it is the first control reference:
<pages>
<controls>
<add tagPrefix="eapps" namespace="WebApplication1.Controls" assembly="WebApplication1"/>
<add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</controls>
</pages>
I will now create the web display:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication1._Default" %>
<!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:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<div>
<eapps:PageInformationControl ID="information1" runat="server" />
</div>
<asp:Button ID="Button1" runat="server" Text="Test PostBack"
onclick="Button1_Click" /><br />
<asp:TextBox ID="TextBoxTest" runat="server" TextMode="MultiLine" Rows="6"
Columns="10" Width="239px"></asp:TextBox>
</form>
</body>
</html>
Which gives a nice desgin time layout. I will make a post about DesignTimeDesigning which is a cool topic and leads you into Windows Forms if you want to go that far, you don't have to like, BUT, it shows you the power of integration that is at your finger tips and can lead to commercial control development and may be an income dependant on the popularity and effectiveness of your controls. As you will see form the graphic below you need to add a Script Manager to your page prior to running the code otherwise it will tell you the same thing lol.

Next we will populate the TextBox you see above with the data from our control in a formatted fashion when the Test postback button is clicked.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Text;
namespace WebApplication1
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void Button1_Click(object sender, EventArgs e)
{
StringBuilder sb1 = new StringBuilder();
sb1.AppendFormat("Title : {0}", information1.Title);
sb1.AppendLine();
sb1.AppendFormat("Firstname : {0}", information1.Lastname);
sb1.AppendLine();
sb1.AppendFormat("Lastname : {0}", information1.Firstname);
sb1.AppendLine();
//Notice the next part. It is nice and strongly typed even though the
//textBox is populated using clientside code.
//I am formatting the date as I do not want the Time part
sb1.AppendFormat("Date of Birth : {0}", information1.DateOfBirth.ToString("yyyy-MM-dd"));
sb1.AppendLine();
TextBoxTest.Text = sb1.ToString();
sb1 = null;
}
}
}
All that is left is to run the code.

Conclusion
Making controls this way, may seem long winded but at the end you will get a reusable, portable control. You can extend it, you can improve it, you can change it etc... My favourite part about it is the fact that you can access postdata from you control in a strongly typed fashion from the C# server side layer.
Extensions I can think of straight away involves validation and also integrating the Ajax Control Toolkit Validator Callout.
You can download the files here.