Export (0) Print
Expand All

A Crash Course on ASP.NET Control Development: Deriving New Controls from Existing Classes

 

Dino Esposito
October 2005

Applies to:
   ASP.NET 2.0
   Visual Studio 2005

Summary: This series of articles will look at creating ASP.NET controls. In this first article, Dino looks at deriving from the existing controls, and modifying their behavior. (16 printed pages)

Download the sample code in Visual Basic, CCCDerivingVB.msi.

Download the sample code in C#, CCCDerivingCS.msi.

Contents

What's a Custom Control, Anyway?
Guidelines for New Controls
Planning a Multicolor Drop-down List Control
A Custom DropDownList Control
Styling Individual Items
Enhancing the DropDownList Control
The ItemColoring Event
Summary

What's a Custom Control, Anyway?

Code reusability is one of the pillars of the ASP.NET platform and controls are one of the fundamental pieces of the ASP.NET reusability puzzle. ASP.NET controls are compiled components that encapsulate some functionality, and optionally some user interface, into reusable packages. ASP.NET ships with about a hundred built-in controls divided in two main families: Web and HTML controls. There's no real structural difference between the two families; they differ mostly in their programming interface and their public syntax. While HTML controls have a 1-to-1 correspondence with an HTML element, Web controls are more abstract and produce markup that optionally spans a group of HTML elements. The two families do overlap to some extent; for example, both have a button control. The button Web control, though, provides a slightly richer programming interface representing a more abstract object model. The object model exposed by HTML controls, instead, is nearly identical to the HTML 4.0 object model for the corresponding HTML element.

From a language perspective, ASP.NET server controls are plain classes. Deriving new controls, therefore, is neither a far-fetched idea nor rocket science. This article is the first of a long series entirely devoted to illustrating various aspects and issues of custom control development.

Custom controls are no different than standard ASP.NET server controls except that they are bound to a different tag prefix and must be registered and deployed explicitly. Aside from that, custom controls can have their own object model, fire events, and support all the design-time features of Microsoft Visual Studio .NET, such as the Properties window, the visual designer, property builders, and the Toolbox.

A custom server control is a .NET Framework class that inherits from Control. Control is the root class for all server controls in ASP.NET applications. It should be noted, though, that very few controls in ASP.NET really inherit directly from Control. For the most part, ASP.NET controls inherit from intermediate classes that encapsulate a given predefined behavior. Table 1 shows some of these classes and their hard-coded behavior.

Control ClassBase ClassVer.Behavior
WebControlControl1.xAdds an array of UI properties such as style settings, colors, font, borders
BaseDataBoundControlWebControl2.0Incorporates the basic mechanism and object model for data binding
DataBoundControlBaseDataBoundControl2.0Adds support for data source controls and overrides some methods marked abstract in the parent class
HierarchicalDataBoundControlBaseDataBoundControl2.0Adds support for hierarchical data source controls and overrides some methods marked abstract in the parent class
CompositeDataBoundControlDataBoundControl2.0Incorporates the mechanics of composite data-bound controls, with regard to viewstate management and building of the control's tree
ListControlDataBoundControl1.xAdds support and an object model tailormade for list controls such as CheckBoxList and DropDownList. Introduced in ASP.NET 1.x, this class was refactored in ASP.NET 2.0.
BaseDataListWebControl1.xAdds grid capabilities such as advanced rendering, templates, paging

Table 1. Intermediate control classes available in ASP.NET 2.0

Among the commonly used controls that inherit directly from Control you find Repeater, Substitution, MultiView, Placeholder, and LiteralControl. The base Control class incorporates a number of features and makes them available to all child controls. A quick list includes viewstate management, control identification, naming container capabilities, design-time support, and, in ASP.NET 2.0, theming, control state, and adaptive rendering.

Guidelines for New Controls

Built-in ASP.NET controls cover a wide range of functionality and certainly enable page developers to author great pages. In some cases, though, the standard set of features that built-in controls provide might just not be enough for you or, more simply, you might need to generate a UI that no existing controls are designed to render.

In these cases, I suggest that your first step is to verify whether a combination of styles, code behind, and existing controls can produce the expected results and how complex the wholesale procedure turns out to be. Be aware that ASP.NET can't perform any magic as far as markup is concerned. If the feature you have in mind cannot be obtained with HTML elements and JavaScript code, then there's no way for ASP.NET and any other server-side platform to pull the magic wand out of your pocket and make it happen.

So in the first place, make sure the feature you you want to create is really doable. Next, measure its level of complexity and how easy it is to reproduce it on a different page. If the feature is feasible, you must be able to implement it inside a sample page by using a combination of HTML markup, existing controls, literals, and client and server code. Once you're done, take a look at the artifact. How easy would it be to move the solution to another page? How many parameterized attributes can you spot in the solution? Does the behavior fire events that surrounding code might be interested in handling? Can you see any added value deriving for your organization from the engineering of the solution?

Let's define a step-by-step procedure to evaluate the need you have of new controls and the best way to lay them out.

1. Can you reproduce the feature you have in mind using only HTML markup, JavaScript code, existing ASP.NET controls, literals, or server-side event handlers in the host page?

YesAssign a level of complexity to the artifact you have come up with. The complexity level is on an arbitrary scale. In other words, the step simply serves the purpose of forcing an extra refactoring step to ensure the solution is reasonably the simplest possible.
NoForget it, there's not much ASP.NET can do for you. You need to explore other tools to reproduce the feature. Depending on what you have in mind, options can be: Flash movies, embedded ActiveX controls, or managed Windows Forms components. In some extreme cases, even a brand new type of application can be considered—a smart client Internet-deployed application instead of a plain Web application.

2. Can the artifact be moved to another page and what kind of efforts are involved?

YesIdentify attributes that can become parameters set at design-time; identify events that external components might be interested to handle and locate the position in the code flow where they originate.
NoYour level of reusability can't exceed cut-and-paste. Re-implement the solution in the context of each page that requires it.

If your answers are both yes, the control can be created as long as you have plans to really reuse it throughout pages and applications. Developing a control adds some complexity to the overall solution.

Having a completely modular solution is always great, but architecting and implementing modular components requires extra effort. Usually, components quickly and easily pay off these costs, but if you just don't have immediate plans for reusing that component, why make your schedule heavier?

OK, let's assume you are determined to build a new control. The next step is to move the core code you should have out of the page and inside a new Control-derived wrapper. The key issue to face here is: what's my best base class? In this article, I'll assume that a built-in control exists that is close enough to the characteristics you want. In future articles, I'll explore other options.

Even though the new control may work like a champ, be aware that you have no realistic control yet. So ask yourself the following:

3. Are more control-related features required?

YesInsert the control in the Visual Studio toolbox and drop it in a sample page. How is working with the control? At this stage, you typically realize that more properties are needed to improve the quality of the work and also you see that most of these properties require little effort of your own.
NoThe control is really a simple one or, being derived from a nearly identical base class, it already incorporates most of the required features.

Armed with these three guidelines, let's build a custom ASP.NET control to implement a feature that no native ASP.NET 2.0 controls provide. As mentioned, the feature I'm going to consider here is deliberately selected to find a close match in the standard set of controls in ASP.NET 2.0.

Planning a Multicolor Drop-down List Control

Years ago, I learned the Windows programming style the hard way—that is, from the Windows SDK. When I started, multicolor comboboxes were the feature that I would have most loved to be able to build in my applications. In the end, it took me only a few days of full immersion in the mythical Charles Petzold's Programming Windows book—the Windows 3.1 edition—to learn how to do it.

Is it possible to have the same feature working in Web applications? What I have in mind is a drop-down list that displays items with different background and foreground colors. ASP.NET comes with the DropDownList control that provides a pair of properties like ForeColor and BackColor. These two properties, though, apply to the control as a whole. If set, they change the color for all the items displayed in the list. To see if it is ever possible, let's go through the steps outlined above.

The following HTML markup produces the output in Figure 1.

<html>
  <body>
    <select>
      <option style="color:red">One</option>
      <option>Two</option>
      <option>Three</option>
    </select>
  </body>
</html>

Aa479311.controlscrashcourse-deriving01(en-us,MSDN.10).gif

Figure 1. A multicolor HTML <select> element in Internet Explorer 6 and Firefox

The output of a drop-down element consists of a root <select> tag that includes as many <option> elements as there are data items to display. By setting the style attribute on the <option> element you can control a few visual attributes on the given item. Interestingly enough, in this context Mozilla Firefox recognizes many more style attributes than Internet Explorer 6.0. However, as far as colors are concerned both browsers handle them properly. Unlike Internet Explorer, though, Firefox also recognizes font styles.

Having ascertained that the feature is definitely possible, let's see how to take it out of the sample HTML page and integrate in a server page. To create a drop-down list in an ASP.NET page, you typically use the DropDownList control. Obviously, nothing prevents you from using static HTML markup in ASP.NET pages, but this is normally detrimental to dynamic data binding. If you simply incorporate the preceding markup, you get multicolor drop-down lists in ASP.NET but have no way to take programmatic control of what's being displayed.

What's wrong with the built-in DropDownList server control? Simply put, it just doesn't let you configure the HTML style of displayed items. The style property applies to the control as a whole; so visual properties such as font, borders, and colors are always set for all items. In ASP.NET 2.0, data-bound controls feature a new DataBound event that fires when the control has been completely bound to its data source. For list controls like the DropDownList, the DataBound event means that its Items collection has been fully populated. Will the following code work?

protected void DropDownList1_DataBound(object sender, EventArgs e)
{
   string itemStyle = "color:black;background-color:lightyellow;";
   for (int i = 0; i < DropDownList1.Items.Count; i += 2)
      DropDownList1.Items[i].Attributes["style"] = itemStyle;
}

When the DataBound event fires, the code sets the Style attribute on every other item. Unfortunately, the code has no effect if run from within an ASP.NET page. The reason? The rendering code of the DropDownList control just ignores any style set for items. Furthermore, the same limitation applies to all list controls, including ListBox controls.

What steps are required to have data-bindable and colorful drop-down list controls? You need a new derived control that overrides the default rendering engine to accommodate for style properties set on child items. In addition, the new derived control is responsible for the definition of a custom object model to let page authors set styles easily and comfortably.

The new DropDownList control is just a specialized version of the built-in DropDownList control, simply living in a distinct assembly and namespace. In this case, choosing the base class for the new control is a child's game.

A Custom DropDownList Control

The creation of a new control requires a new class library project in Visual Studio 2005 and a class file that I called dropdownlist.cs. Here's the declaration of the new control:

public class DropDownList : System.Web.UI.WebControls.DropDownList
{
    public DropDownList()
    {
    }
    :
}

Being derived from the built-in DropDownList control, the new control inherits the same behavior, programming interface, and design-time capabilities of the parent.

The key aspect of the control that you need to change is the rendering engine. All controls use the following rendering code:

protected internal override void Render(HtmlTextWriter writer)
{
    RenderBeginTag(writer);
    RenderContents(writer);
    RenderEndTag(writer);
}

Defined on the WebControl class, the Render method is inherited by all ASP.NET controls and overridden only in a few cases. The RenderBeginTag overridable method takes care of rendering the begin tag of the markup that represents the control. Here's some pseudo-code for it:

public virtual void RenderBeginTag(HtmlTextWriter writer)
{
    AddAttributesToRender(writer);
    writer.RenderBeginTag(TagKey);
}

Even simpler is the default behavior of the RenderEndTag method:

public virtual void RenderEndTag(HtmlTextWriter writer)
{
    writer.RenderEndTag();
}

Basically, each list control maintains a tag name internally (it's Select) and renders it with associated attributes in the RenderBeginTag method, and closes the open tag in the RenderEndTag method. There's just nothing to change in these methods.

The RenderContents virtual method is in charge of everything being rendered in between the begin and end tag. The code below shows how you should write it to render out a normal drop-down list with additional item styles.

protected override void RenderContents(HtmlTextWriter writer)
{
   ListItemCollection items = this.Items;

   for (int i = 0; i < items.Count; i++)
   {
      ListItem item = items[i];
      writer.WriteBeginTag("option");
      if (item.Selected)
          writer.WriteAttribute("selected", "selected");
      writer.WriteAttribute("value", item.Value, true);

      // Add any style attribute defined
      writer.WriteAttribute("style", item.Attributes["style"]);

      writer.Write('>');
      HttpUtility.HtmlEncode(item.Text, writer);
      writer.WriteEndTag("option");
      writer.WriteLine();
   }
}

The boldface lines show the code that enables the custom DropDownList control to recognize and process any style information associated with child elements of the list. Basically, the DropDownList control creates an <option> HTML element for each element found in the Items collection. The <option> element is added text and value, plus the selected attribute, if applicable. With the additional code it will also add a style attribute, which is key for multicolor rendering.

Styling Individual Items

To see if it works, let's compile a DropDownList class that includes the RenderContents override. Next, drop an instance of the new control in a page that contains the DataBound event handler that was shown earlier. In practice, as soon as the data-binding mechanism has finished populating the list, the event fires and the page adds style information to some of the items. Next, the control renders out and RenderContents is invoked. This time, though, the control knows how to handle style information and the results are those shown in Figure 2.

Aa479311.controlscrashcourse-deriving02(en-us,MSDN.10).gif

Figure 2. A multicolor drop-down list defined as an ASP.NET custom control

To set the style for each item in the list, you can use the code below:

string itemStyle = "color:black;background-color:lightyellow;";
DropDownList1.Items[i].Attributes["style"] = itemStyle;

The variable itemStyle is a string that ties together a few style attributes in a single string. This string is assigned to the Attributes collection. Wouldn't it be better if the control could expose a public method like below?

public void SetItemStyle(int index, string foreColor, string backColor)
{
   string format = "color:{0};background-color:{1}";
   Items[index].Attributes["Style"] = String.Format(format,
                foreColor, backColor);
}
public void SetItemStyle(int index, Color foreColor, Color backColor)
{
   string format = "color:{0};background-color:{1}";
   Items[index].Attributes["Style"] = String.Format(format,
                ColorTranslator.ToHtml(foreColor), 
                ColorTranslator.ToHtml(backColor));
}

The SetItemStyle overloaded method accomplishes the same task, but simplifies and abstracts the programming interface visible to the page author. The overloading mechanism allows you to specify colors both as strings and as Color objects. Another pair of overloads can be added to specify the item being styled through a ListItem object rather than through a numeric index.

public void SetItemStyle(ListItem item, 
                         string foreColor, string backColor)
{
   string format = "color:{0};background-color:{1}";
   item.Attributes["Style"] = String.Format(format,
         foreColor, backColor);
}
public void SetItemStyle(ListItem item, Color foreColor, Color backColor)
{
   string format = "color:{0};background-color:{1}";
  item.Attributes["Style"] = String.Format(format,
                ColorTranslator.ToHtml(foreColor), 
                ColorTranslator.ToHtml(backColor));
}

The DataBound event in the ASP.NET page will now look like the following code snippet:

protected void DropDownList1_DataBound(object sender, EventArgs e)
{
   for (int i = 0; i < DropDownList1.Items.Count; i += 2)
       DropDownList1.SetItemStyle(i, Color.Black, Color.LightYellow);
}

Enhancing the DropDownList Control

The SetItemStyle method allows you to set background and foreground colors for a particular set of items. Items can be identified either by reference or by position. While this approach provides a great deal of flexibility, it still requires a bit of page-level work to address relatively common scenarios. For example, you might want to alternate styles by position or by initial and, more importantly, you might want to instruct the control to do so without the need of further code.

Let's start by adding the properties listed in Table 2.

PropertyTypeDescription
AlternateBackColorColorGets and sets the background color to use for alternate items.
AlternateForeColorColorGets and sets the foreground color to use for alternate items.
ItemColorModeItemColorModeIndicates how the control should style child items. Feasible values are defined by the ItemColorMode enum type.
InitialSubStringSizeintIndicates the size of the substring to consider as the initial when ItemColorMode is set to ByInitials.

Table 2. New properties added to the custom DropDownList control

The roles of AlternateBackColor and AlternateForeColor are self-explanatory. They represent the colors to use in case of automatic styling of the items. ItemColorMode is a property of ItemColorMode type. ItemColorMode is an enum type defined as below:

public enum ItemColorMode : int
{
    Explicit = 0,
    ByInitials = 1,
    Alternate = 2,
    Custom = 3
}

Default setting, Explicit indicates that the control render styles that are explicitly set at rendering time. When the control works in Explicit mode, you have to use SetItemStyle to define colors for a given item. ByInitials indicates that the control will automatically add style information to contained items so that all items whose text begins with the same substring are rendered with the same (alternate) color. The size of the substring can be programmatically adjusted using the InitialSubStringSize property, which defaults to 1. Alternate indicates that the DropDownList control will add alternate style information to every other item. When the mode is ByInitials or Alternate, no code is required as the feature is entirely declarative.

If you opt for Custom, an event will be fired to the host page so that item-specific server code can be executed to determine the colors to use. Custom gives the same flexibility as Explicit but through a different programming interface.

The code below shows the implementation of the aforementioned properties:

public ItemColorMode ItemColorMode
{
   get {
     object o = ViewState["ItemColorMode"];
     if (o == null)
       return ItemColorMode.Explicit;
     return (ItemColorMode)o;
   }
   set {
     ViewState["ItemColorMode"] = value;
   }
}
public Color AlternateItemBackColor
{
   get {
     object o = ViewState["AlternateItemBackColor"];
     if (o == null)
        return Color.Empty;
     return (Color)o;
   }
   set {
      ViewState["AlternateItemBackColor"] = value;
   }        
}
public Color AlternateItemForeColor
{
   get {
     object o = ViewState["AlternateItemForeColor"];
     if (o == null)
        return Color.Black;
     return (Color)o;
   }
   set {
      ViewState["AlternateItemForeColor"] = value;
   }        
}
public int InitialSubStringSize
{
   get {
     object o = ViewState["InitialSubStringSize"];
     if (o == null)
        return 1;
     return (int)o;
   }
   set {
     ViewState["InitialSubStringSize"] = value;
   }
}

Note that most ASP.NET built-in controls don't implement color properties as shown here. There's nothing bad with this approach but most server controls associate properties with a style object. The custom style object will group a variety of visual settings and provides for their serialization to and from the viewstate. This approach is simpler to write and certainly not less effective in terms of speed; using style objects is preferable when a lot of visual properties are involved.

To automatically style contained items, the DropDownList control needs to know when all the items are bound. For this reason, you build an internal handler for the DataBound event. This can be obtained by overriding the OnDataBound virtual method defined on the parent class.

protected override void OnDataBound(EventArgs e)
{
    base.OnDataBound(e);

    if (!ItemColorMode.Equals(ItemColorMode.Explicit))
         ApplyItemStyle();
}

If the mode is all but Explicit, you invoke a private method named ApplyItemStyle. When this method executes, all items are bound and you can proceed styling them in accordance with the selected coloring mode. Here's the code:

private void ApplyItemStyle()
{
   ListItemCollection items = this.Items;

   // ALTERNATE
   if (ItemColorMode.Equals(ItemColorMode.Alternate))
   {
     for (int i = 0; i < items.Count; i+=2)
        SetItemStyle(i, AlternateItemForeColor, AlternateItemBackColor); 
   }

   // BYINITIALS
   if (ItemColorMode.Equals(ItemColorMode.ByInitials))
   {
      string currentInitial = "!";
      Color currentBack = BackColor;
      Color currentFore = ForeColor;
      bool useDefaultColors = true;

      foreach (ListItem item in items)
      {
         if (!item.Text.StartsWith(currentInitial))
         {
             currentInitial = item.Text.Substring(0, 
                   InitialSubStringSize);
             useDefaultColors = !useDefaultColors;
             if (useDefaultColors)
             {
                currentBack = BackColor;
                currentFore = ForeColor;
             }
             else
             {
                currentBack = AlternateItemBackColor;
                currentFore = AlternateItemForeColor;
             }
          }
          SetItemStyle(item, currentFore, currentBack);
      }
   }

   // CUSTOM
   if (ItemColorMode.Equals(ItemColorMode.Custom))
   {
       for(int i=0; i<Items.Count; i++)
       {
           ItemColoringEventArgs e = new ItemColoringEventArgs();
           e.Item = Items[i];
           e.ItemIndex = i;
           e.AlternateItemBackColor = BackColor;
           e.AlternateItemForeColor = ForeColor;
           OnItemColoring(e);

           SetItemStyle(Items[i], e.ItemForeColor, e.ItemBackColor);
        }
    }
}

Alternate and ByInitials are two variations of the same technique. Visual settings vary every other item. The concept of "other item" is different in the two cases. In Alternate mode, the other item is the item on even rows (second, fourth, and so on). In ByInitials mode, the other item is the successive group of items whose name begins with the same letter(s). Figure 3 offers a preview of the Alternate and ByInitials modes.

Click here for larger image

Figure 3. Alternate and ByInitials mode in the custom DropDownList control (click image to enlarge)

The first snapshot shows the classic alternate rendering you know from grid controls. The second snapshot shows the ByInitials mode when only the first letter is considered. Finally, the third snapshot uses the first two letters of the item's value to alternate. The number of involved letters is controlled via the InitialSubStringSize property.

The ItemColoring Event

The DropDownList custom control is smart enough to fire an event when the style information is associated to a given item. The event is named ItemColoring and is defined as follows:

public event EventHandler<ItemColoringEventArgs> ItemColoring;

Note the use of generics in the event declaration, which saves a delegate definition (and a delegate type instantiation at runtime). The EventHandler<T> type is a generic delegate for events and accepts any event data type you specify.

public class ItemColoringEventArgs : EventArgs
{
   public ListItem Item;
   public int ItemIndex;
   public Color ItemBackColor;
   public Color ItemForeColor;
}

The Item and ItemIndex members indicate the item being styled and its 0-based position in the Items collection. The other two members are bidirectional and indicate the colors to use to style the item. The ItemColoring event is fired to give page authors a chance to decide how to style a given item.

void DropDownList1_ItemColoring(object sender, ItemColoringEventArgs e)
{
   if (e.ItemIndex == 3)
   {
      e.ItemBackColor = Color.Black;
      e.ItemForeColor = Color.LightGreen;
   }
}

ItemBackColor and ItemForeColor are set with the default background and foreground color of all items. In the page event handler, you perform any sort of custom check on the data and decide which colors to use.

Summary

Overall, writing ASP.NET custom controls is not hard, but neither is it easy. Generally speaking, the aspect that I find most complex is the definition of the programming interface of the control—the perfect mix of properties, methods, events, and design-time features to make it effective and pleasant for users to work with your control.

In this first article of a long series I tackled the simplest scenario—building a control from an existing control class. The final control is an enhanced DropDownList control that works exactly as its parent except that it additionally supports various forms of styling for its child items.

Let me hear your thoughts and be prepared to see articles appear here in the next few weeks on the building of brand new and composite controls.

 

About the author

Dino Esposito is a Solid Quality Learning mentor and the author of Programming Microsoft ASP.NET 2.0 (Microsoft Press, 2005). Based in Italy, Dino is a frequent speaker at industry events worldwide. Get in touch at cutting@microsoft.com or join the blog at http://weblogs.asp.net/despos.

Show:
© 2014 Microsoft