| 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. |
|