Simple Derived Controls
You know what they say about reinventing the wheelit's a waste of time! Because the .NET Framework is object-oriented, your time might better be spent using existing server controls instead of building your own.Let's take a common task as the basis of making our own derived control. Your application has a drop-down list filled with familiar choices loaded from a database, and this list will appear all over your application. You could accomplish this by putting the control in every page where you need it and then writing some code to populate it on every page. Another way you could do it is to write a class with a static method that returns a data source. The best way, though, is to create your own server control, derived from an existing control.Let's ignore for a moment all of the inner workings of a typical server control because you don't need to know anything about them to accomplish your assignment. If we look up System.Web.UI.WebControls.DropDownList in the .NET documentation, we can see a list of the control's members. It has a property called Items, which is of type ListItemCollection. By following the links in the documentation, we can see that this type contains, cleverly enough, ListItem objects, which make up the selections in the final HTML that is sent to the browser.That's enough to get started, so we can quickly whip up a shell of a class for now. We'll add to it as we go. Listing 9.1 shows our starting point. In it, we create a class that derives from DropDownList and has a constructor. Because this control will be a list of different content categories on our site, we'll call the class ContentCategoryDropDown. In this listing, we also show how the control will be declared in our page.
Listing 9.1. The start of our basic derived class and its declaration in a page
C#
VB.NET
namespace Testing
{
public class ContentCategoryDropDown : DropDownList
{
public ContentCategoryDropDown()
{
}
}
}
.aspx Page
Namespace Testing
Public Class ContentCategoryDropDown
Inherits DropDownList
Public Sub New()
End Sub
End Class
End Namespace
That's easy enough! In fact, the page in Listing 9.1 would generate a drop-down list just as if we used <asp:DropDownList /> control.
<%@ Page Language="C#" %>
<%@ Register Namespace="Testing" TagPrefix="MyControl" %>
<html>
<body>
<form id="form1" runat="server">
<MyControl:TestControl runat="server" />
...
Note that the Register directive in the [View full width]<%@ Register Namespace="Testing" TagPrefix="MyControl"

It's also important to note that your control, derived or original, must be contained in a namespace.Now that we've proven that we can create a derived control, let's make it do something. We'll add code to the constructor of our class to add ListItems to the Item property of the underlying DropDownList class. Listing 9.2 shows our new constructor with data access code.
Listing 9.2. The constructor that populates the DropDownList with data
C#[View full width]
VB.NET
public ContentCategoryDropDown()
{
this.Items.Add(new ListItem("Select a topic...","0"));
SqlConnection connection = new SqlConnection("myconnectionstring");
connection.Open();
SqlCommand command = new SqlCommand("SELECT ContentCategoryID, Name FROMContentCategories ORDER BY Name", connection);
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
this.Items.Add(new ListItem(reader.GetString(1), reader.GetInt32(0).ToString()));
}
reader.Close();
connection.Close();
}
Public Sub New()
Me.Items.Add(New ListItem("Select a topic...", "0"))
Dim connection As New SqlConnection("myconnectionstring")
connection.Open()
Dim command As New SqlCommand("SELECT ContentCategoryID, Name " & _
"FROM ContentCategories ORDER BY Name", connection)
Dim reader As SqlDataReader = command.ExecuteReader()
While reader.Read()
Me.Items.Add(New ListItem(reader.GetString(1), _
reader.GetInt32(0).ToString()))
End While
reader.Close()
connection.Close()
End Sub
Just as we did in Chapter 5, "Object-Oriented Programming Applied: A Custom Data Class," we're getting values out of our reader by using one of the DataReader's many Get... methods. This is a solid way to boost performance a little because it doesn't have to work as hard as it does when you specify a database column by name (i.e., reader["ColumnName"]). Most collections in .NET may be addressed by name or by index, as these reader methods do, and the index is faster because the framework doesn't need to compare the string you're specifying to each item in the collection.In some of the code samples you've seen so far in this book, we've called other members of a class from various methods by name. You can do the same thing in a derived class, even though you can't "see" the existing members of the base class. For example, if you typed Items followed by a period in Visual Studio for this example, Intellisense would give you a list of all of the members available to you. In our example, all of the members of DropDownList are available. To make it somewhat easier to read, we can also use the keywords this (C#) or Me (VB.NET) to reference the class we're working in.
Visual Studio may hide some members from you. To see them, open the Options dialog and deselect Hide Advanced Members from the Text Editor -> Basic -> General Options. |
Listing 9.3. Our control rendered to HTML at runtime
As we mentioned before, you could add a normal DropDownList to your page and then use the same data access code to DataBind() the results to the control. Although that's certainly acceptable, doing that in every single page would get tedious. This method makes it portable and easier to maintain.Now our derived drop-down list has data in it, but it doesn't do anything. Let's extend the functionality of the control to redirect to a new page when its selected index is changed. As you may already know, the DropDownList has a Boolean property called AutoPostBack. Let's add AutoPostBack = true to the constructor so that the property is true. That means when the user changes the selection, the page will postback.If we go back to the documentation, we can see that DropDownList has a protected method called OnSelectedIndexChanged(), which is wired up to the SelectedIndexChanged event. This seems like a logical place to introduce our own logic, which would forward the user to another page, based on the user's selection.Remember our discussion about inheritance? Because we're inheriting DropDownList as our base class, we can override it here and add our own code, as shown in Listing 9.4.
<select name="_ctl0">
<option selected="selected" value="0">Select a topic...</option>
<option value="19">Application</option>
<option value="12">Basics</option>
<option value="13">C#</option>
<option value="10">Configuration</option>
<option value="5">Controls</option>
<option value="3">Data</option>
<option value="18">HttpHandlers/Modules</option>
<option value="9">Mobile</option>
<option value="21">Pages/User Controls</option>
<option value="6">Security</option>
<option value="20">Session</option>
<option value="15">SQL Server</option>
<option value="8">Validation</option>
<option value="14">Visual Basic .Net</option>
<option value="7">Visual Studio</option>
<option value="4">Web Services</option>
<option value="23">Whidbey</option>
<option value="1">XML</option>
</select>
Listing 9.4. Overriding the OnSelectedIndexChanged() method
C#
VB.NET
protected override void OnSelectedIndexChanged(EventArgs e)
{
this.Page.Response.Redirect("newswire.aspx?filter=" + SelectedItem.Value);
}
The documentation shows that the method returns nothing and takes an EventArgs parameter, so we'll have to do the same thing in our derived method. All that's left to do is to add a redirect to the page that we want, based on the value of the new selected item.That's all there is to it. If we need to insert this new derived control into a dozen different pages, we only have to write the code once. Using the Register directive in Listing 9.1 and the tag between the form elements, this same control will appear, populated and ready to redirect users, on every page.
Protected Overrides Sub OnSelectedIndexChanged(e As EventArgs)
Me.Page.Response.Redirect("newswire.aspx?filter=" & _
SelectedItem.Value)
End Sub