ASP.NET Server Controls Demystified
By Peter A. Bromberg, Ph.D.
Printer - Friendly Version
Peter Bromberg

Being a professional developer for a living (or should I have said, "living to be a professional developer?"), as I continue to hone and sell my skills in ASP.NET and the .NET platform to willing buyers, there remains one particular area that I've determined to continue to focus on: ASP.NET Server Controls. (For some background on this quest, see my book review on the Wrox book on Server Controls, which review is now almost nine months old as I write. There are now other, newer books on this subject.)




Server Controls are important to you as an ASP.NET developer for several reasons:

1) They are an almost complete encapsulation of the whole concept of the .NET Platform and what it's all about. They use inheritance, polymorphism and other object - oriented programming techniques, and represent first - class examples of the idea of promoting code re-use, particularly among developers working together in a group that needs to cooperate closely to deliver products.

2) Secondarily (but certainly no less important), people will purchase the functionality you have created in Server Controls for their own use. If you develop something really useful, and it's priced correctly and marketed effectively, you can make many thousands of dollars for your hard work and entrepreneurial abilities. One only needs to visit our Component Store here at eggheadcafe.com and look at the growing list of ASP.NET components and controls to see that there is indeed a thriving market here for enterprising developers, and it can only grow bigger over time. I don't know about you, but I believe that whether SUN eventually succeeds in forcing Microsoft to ship their latest JRE on Windows or not, the ASP.NET Server Control market can be predicted to remain strangely unaffected (you can bet your bottom applet on that).

3) Server Controls that you develop are essentially"self - contained" bundles of application logic that provide a browser - independent UI component to provide properties and methods, raise both client - side and server -side events, detect the client device type (including mobile devices) and render the appropriate markup ( HTML 4.0, 3.2, WML, Javascript, etc.) to handle the functionality you desire.

As a .NET developer, you can certainly create a .NET class library that needs to be instantiated and has methods that will spit out customized HTML, but this is not a control - it's just a class library that spits out HTML.  A Control is a class that derives from either System.Web.UI.Control or System.Web.UI.WebControls.WebControl, or from a pre-existing ASP.NET WebControl, such as the System.Web.UI.WebControls.DropDownList. In this manner, your Control inherits from and becomes an integral part of the rich class hierarchy of System.Web.UI.Control--> System.Web.UI.WebControls.WebControl. This class hierarchy defines the set of methods, properties and events (over 55 in all) that all Server Controls share, and is precisely what delivers the incredible power that mastering Server Control authoring techniques can provide.

ASP.NET gives us the ability to provide a much more "Windows - like" user web experience because of the unique lifecycle that all Server Controls go through in an ASP.NET Page class instance:

Initialize: The Init event is raised (you override with OnInit() ) and the control is set up with its initial state to allow it to be added to the Controls collection of the Page class.

Load View State: Control is checked to see if it had a previous existence and whether any ViewState needs to be restored to it. We override this with the LoadViewState() method to add our own custom functionality.

Process PostBack Data: If our Control implements the IPostBackDataHandler interface, it analyzes incoming data and updates its appropriate properties. We override this with the LoadPostData() method to implement our custom functionality.

Send PostBack Change Notifications: This is where Controls that raise PostBack events respond to any changes in state. Here we override RaisePostDataChangedEvent().

Handle PostBack events: Here is where the client-side event that raised that caused a PostBack is handled, by overriding the RaisePostBackEvent() method.

Prerender: This is where any changes that are made to the Control's state before final output is rendered can be saved.

Save State: Our Control's ViewState data will be automatically persisted to ViewState at this stage. We can also customize ViewState data at this point by overriding SaveViewState().

Render: This is the stage where we create the customized output that our control actually returns to the client in the Render() method.

Dispose: Final cleanup (if any) is performed here, and we can gain access to this stage with the Dispose() method.

Let's take a look at the basics and build an actual control. Think about something easy, but useful for starters. . . How about this: I often have to create various types of dropdown listboxes for some of the most common items - a list of US States, countries, Times by hours and minutes, and so on. If I need a DropdownListBox of US States for my Webform, wouldn't it be convenient if I could simply drag a "US States" control from my Toolbox onto my WebForm Designer Surface, set an optional selected State, and be ready to go?

Actually, once you know what you are doing, the amount of work to take all that typing of <OPTION> elements and convert it all into a full-fledged Server Control is only incrementally more effort. The advantage is that you never have to type them again. This is a simplified version of a control that I might write in order to sell- a more complete, mature version might offer to load the list of states from a Xml File specified as a Property in the Control's Property Sheet.

We'll start out with a complete code listing, then we'll take it apart:

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.IO;
using System.Text;  
using System.Collections; 
namespace PAB.WebControls {
 /// <summary>
 /// StateList
 /// Simple control that renders  a list of State Names and codes.
 /// </summary>
 [ToolboxData("<{0}:StateList runat=server></{0}:StateList>")]
 public class StateList : System.Web.UI.WebControls.DropDownList 
 {
  internal bool IsLicensed=false;
  public StateList()
  {  
//you can comment this out starting here-- PAB.WebControls.Licensing lic=new PAB.WebControls.Licensing(); if(!lic.CheckLicenseKey()) this.Items.Add(new ListItem("NOT LICENSED", "NOT LICENSED"));
// -- and ending here to remove reference to licensing IsLicensed=true; for(int i=0;i<StateCode.Length;i++) { this.Items.Add (new ListItem(StateName[i],StateCode[i])); } }

private string[] StateCode = { "AL","KY","ND","AK","LA","OH","AZ","ME","OK","AR","MD","OR","CA","MA" ,"PA","CO","MI","RI","CT","MN","SC","DE","MS","SD","DC","MO","TN","FL", "MT","TX","GA","NE","UT","HI","NV","VT","IA","NH","VA","ID","NJ","WA","IL", "NM","WV","IN","NY","WI","KS","NC","WY","AS","MH","PR","FM","of","MP","VI", "GU","PW"}; private string[] StateName = { "Alabama","Kentucky","North Dakota","Alaska","Louisiana","Ohio","Arizona", "Maine","Oklahoma","Arkansas","Maryland","Oregon","California","Massachusetts", "Pennsylvania","Colorado","Michigan","Rhode Island","Connecticut","Minnesota", "South Carolina","Delaware","Mississippi","South Dakota","District Columbia", "Missouri","Tennessee","Florida","Montana","Texas","Georgia","Nebraska","Utah", "Hawaii","Nevada","Vermont","Iowa","New Hampshire","Virginia","Idaho","New Jersey", "Washington","Illinois","New Mexico","West Virginia","Indiana","New York","Wisconsin", "Kansas","North Carolina","Wyoming","American Samoa","Marshall Islands","Puerto Rico", "Federated States","Micronesia","N Mariana Islands","Virgin Islands","Guam","Palau"}; /// <summary> /// Override to save data to viewstate /// </summary> protected override object SaveViewState() { // create object array for Item count + 1 object[] allCountries = new object[this.Items.Count + 1]; // the +1 is to hold the base info object baseState = base.SaveViewState(); allCountries[0] = baseState; return allCountries; } /// <summary> /// Override to restore ViewState /// </summary> protected override void LoadViewState(object savedState) { if (savedState != null) { object[] myState = (object[])savedState; // restore base first if (myState[0] != null) base.LoadViewState(myState[0]); } } /// <summary> /// Override to render contents of dropdownlist /// </summary> protected override void RenderContents(HtmlTextWriter writer) { foreach(ListItem li in this.Items) { writer.WriteBeginTag("option"); if(li.Selected) writer.WriteAttribute("selected","selected",false); if(li.Attributes.Count > 0) li.Attributes.Render(writer); writer.WriteAttribute("value",li.Value.ToString()); writer.Write(HtmlTextWriter.TagRightChar); writer.Write(li.Text); writer.WriteEndTag("option"); writer.WriteLine(); } } } }

OK, Notice at the beginning of our class, we decorate with the ToolboxData attribute. This is what enables the IDE to "see" our control and its properties. You see that I've derived the class "StateList" from System.Web.UI.WebControls.DropDownList. Why? Well, ASP.NET already provides us with a DropDownList control, and since all we want to do is provide a customized one, there's no need to reinvent the wheel! Anytime you embark on a project to create a custom ServerControl the first thing you want to consider is whether you can simply override a base class, rather that creating everything you need from the ground up. And don't forget, you can also create what are called Composite Controls - these are Server Controls that comprise several different base controls that are designed to work together as a unit of programming logic.

Moving further down, you can see the class constructor, public StateList(), where I've added code to check for a license key (sorry, you can't have this code!). Basically, the licensing class checks the Registry for a valid trial or registered license key and returns the proper condition. In this case, I've decided that if the control is a trial or is not licensed, I'll add a nag item as the first item in the DropDownList. Do you think that might make them decide to buy it? (I'd probably just decompile the sucker and remove the stupid nag <grin/>).

Then, we iterate through the private string arrays that hold the list of States and State Abbreviations, and add them to the Items Collection (handily provided to use with no extra code to write, by the base class).

Moving farher down, we override the SaveViewState() method as described above and save the base state as well as the derived control's state to handle round - trips. As well, we override the LoadViewState() method to restore state to the base and derived classes on a PostBack event.

Finally, we override the RenderContents method, which enables a control to specify the content within the tags. Do this by passing in a an HtmlTextWriter, which provides a rich set of customized properties and methods specifically designed to render HTML tags and attributes for the correct browser version automatically. This ensures that the functionality implemented by WebControl, such as emitting attributes, is preserved. In the Property Sheet, you'll see all the additional items that WebControl offers, such as Background Color, Foreground Color, and so on.

If you start a new VS.NET Project of type "Web Control Library" and replace the template default class file with the above code, you should have a working control! Add a Webform project to your solution, right click on the Toolbox, and add the control dll to the Toolbox, and you should be able to drag a copy onto the design surface of any Webform. Presto! A fully functional StateList DropDownList control with no typing or pasting!

While this exercise only scratches the surface of the complexities and power of authoring custom Server Controls, I hope it gives you a glimpse of the opportunities!

 

 

 

 

 


Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform.