Introduction to custom server controls

The first custom control

This time I'll start from the top, with the using statements and namespace and class declaration to be sure you don't miss anything:

using System;
using System.Collections;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Guestbook.Engine;
namespace Guestbook
{
    /// <summary>
    /// This WebControl displays the form that allows users to sign the guestbook.
    /// </summary>
    public class GuestbookForm : WebControl, INamingContainer
    {

The important thing here is the derive directive and the interface implementation –
the class derives from System.Web.UI.WebControls.WebControl and implements the interface System.Web.UI.INamingContainer . The WebControl class contains several methods that we need in order for our control to work, and the implementation of INamingContainer interface is needed because we need to have unique IDs on the controls within GuestbookForm (due to the post back nature of the control).

Next thing on the todo list is to add a declaration block for all of the controls we're going to use, so let's do that:

TextBox        txtName    = new TextBox();
TextBox        txtEmail    = new TextBox();
TextBox        txtMessage    = new TextBox();
Label        lblName    = new Label();
Label        lblEmail    = new Label();
Label        lblMessage    = new Label();
Label        lblRating    = new Label();
DropDownList    listRating    = new DropDownList();
Button        btnSubmit    = new Button();
RequiredFieldValidator        validMessage    = new RequiredFieldValidator();
RegularExpressionValidator    validEmail    = new RegularExpressionValidator();
Table    table    = new Table();

The controls needs to be declared at class-level, because there are several methods that'll use them.

Now we're going to add a bunch of properties to our custom control, that way a page developer can control the custom control without needed to change the code. Let's craify this further by taking a look at the Label control:

<asp:label id=”myLabel” ... runat=”server” />

ID here is an example of what a property is and how it is used. Hope this made sense…

Well let's add the properties then:
public string NameLabel
{
    get { return lblName.Text; }
    set { lblName.Text = value; }
}
public string EmailLabel
{
    get { return lblEmail.Text; }
    set { lblEmail.Text = value; }
}
public string MessageLabel
{
    get { return lblMessage.Text; }
    set { lblMessage.Text = value; }
}
public string RatingLabel
{
    get { return lblRating.Text; }
    set { lblRating.Text = value; }
}
public string ButtonLabel
{
    get { return btnSubmit.Text; }
    set { btnSubmit.Text = value; }
}
public string MessageValidatorErrorMessage
{
    get { return validMessage.ErrorMessage; }
    set { validMessage.ErrorMessage = value; }
}
public string EmailValidatorErrorMessage
{
    get { return validEmail.ErrorMessage; }
    set { validEmail.ErrorMessage = value; }
}
public string EmailValidatorExpression
{
    get { return validEmail.ValidationExpression; }
    set { validEmail.ValidationExpression = value; }
}
public IEnumerable RatingListDataSource
{
    get { return (IEnumerable) listRating.DataSource; }
    set { listRating.DataSource = value; }
}

Properties are defined in this manner, by using get-set blocks, although both get and set is not necessary. The type of property must match the returned type in the get block – this is very important and the returned valued must then in many cases be converted or type-casted to the appropriate type, as seen in the last property, RatingListDataSource .

Now it's time to take a look at our control's methods, and we'll start off by the smallest one, and that is the method OnInit which we'll override:

protected override void OnInit(EventArgs e)
{
    base.OnInit(e);
    InitializeControls();
}

Ok, as you can see nothing exciting here besides the call to the method InitializeControls. Well let's take a look at that method then:

private void InitializeControls()
{
    // Set up the validator controls
    txtEmail.ID = "Email";
    txtMessage.ID = "Message";
    validEmail.ControlToValidate = txtEmail.ID;
    validMessage.ControlToValidate = txtMessage.ID;
    // Tweak the message textbox
    txtMessage.TextMode = TextBoxMode.MultiLine;
    txtMessage.Rows = 6;
    // Subcribe to the submit button's Click event
    btnSubmit.Click += new EventHandler(btnSubmit_Click);
    // Assign default values to the controls that are blank
    if (lblName.Text == string.Empty)
        lblName.Text = "Your Name: ";
    if (lblEmail.Text == string.Empty)
        lblEmail.Text = "Your E-mail address: ";
    if (lblMessage.Text == string.Empty)
        lblMessage.Text = "Your Message: ";
    if (lblRating.Text == string.Empty)
        lblRating.Text = "Your Rating: ";
    if (btnSubmit.Text == string.Empty)
        btnSubmit.Text = "Submit";
    if (listRating.DataSource == null)
        listRating.DataSource = new string[] { "Excellent", "Good", "OK", "Bad", "Worst ever!" };
    if (validMessage.ErrorMessage == string.Empty)
        validMessage.ErrorMessage = "Must be filled out!";
    if (validEmail.ErrorMessage == string.Empty)
        validEmail.ErrorMessage = "Incorrect e-mail address format!";
    if (validEmail.ValidationExpression == string.Empty)
        validEmail.ValidationExpression = @"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$";
    // DataBind the rating dropdownlist
    listRating.DataBind();
}

All this code is pretty trivial stuff, and detailed explanation shouldn't be needed, just make note of the properties' default values.
You may have also noticed that we declare a subscription to the button control's Click event here. Let us not delay, so we will take a look at that method now:

private void btnSubmit_Click(object sender, EventArgs e)
{
    // Try to insert an entry
    if (XmlEngine.InsertGuestbookEntry( txtName.Text , txtEmail.Text , txtMessage.Text , listRating.SelectedItem.Text ))
        Page.Response.Redirect("Default.aspx"); // If the entry was successfully inserted, reload the page
}

Pretty much all we do here is to try and insert the entry into XML file by using the method InsertGuestbookEntry in the XmlEngine class, and if this is successful we reload the page. (Note: this method redirects to Default.aspx, change this to whatever page you're going to have your GuestbookRepeater control).

We're almost done with this class, just two more methods.

First one up is another overridden method called CreateChildControls . CreateChildControls method is the method where a control's child controls are created, as the name implies. We override it so we can add the controls the way we want to. Basically all we do is to add our controls to other controls' ControlCollection , in a hierarchical manner. Enough talk, let's take a look at it:

protected override void CreateChildControls()
{
    // Create the table
    TableRow tr = new TableRow();
    TableCell td = new TableCell();
    // "Name" row
    td.Controls.Add(lblName);
    tr.Controls.Add(td);
    td = new TableCell();
    td.Controls.Add(txtName);
    tr.Controls.Add(td);
    td = new TableCell();
    tr.Controls.Add(td);
    table.Controls.Add(tr);
    // "E-mail" row
    tr = new TableRow();
    td = new TableCell();
    td.Controls.Add(lblEmail);
    tr.Controls.Add(td);
    td = new TableCell();
    td.Controls.Add(txtEmail);
    tr.Controls.Add(td);
    td = new TableCell();
    td.Controls.Add(validEmail);
    tr.Controls.Add(td);
    table.Controls.Add(tr);
    // "Message" row
    tr = new TableRow();
    td = new TableCell();
    td.VerticalAlign = VerticalAlign.Top;
    td.Controls.Add(lblMessage);
    tr.Controls.Add(td);
    td = new TableCell();
    td.Controls.Add(txtMessage);
    tr.Controls.Add(td);
    td = new TableCell();
    td.VerticalAlign = VerticalAlign.Top;
    td.Controls.Add(validMessage);
    tr.Controls.Add(td);
    table.Controls.Add(tr);
    // "Rating" row
    tr = new TableRow();
    td = new TableCell();
    td.Controls.Add(lblRating);
    tr.Controls.Add(td);
    td = new TableCell();
    td.Controls.Add(listRating);
    tr.Controls.Add(td);
    td = new TableCell();
    tr.Controls.Add(td);
    table.Controls.Add(tr);
    // Button row
    tr = new TableRow();
    td = new TableCell();
    td.ColumnSpan = 3;
    td.Controls.Add(btnSubmit);
    tr.Controls.Add(td);
    table.Controls.Add(tr);
    // Add the table to the control's ControlCollection
    Controls.Add(table);
}

The output of this is a table structure with cells and rows.

There's only one method left in this class now, so let's get right to it. Once again this method is an overridden method. This time it's called Render and handles the rendering of controls into HTML:

protected override void Render(HtmlTextWriter writer)
{
    // Renders the control's attributes
    AddAttributesToRender(writer);
    // Render the table
    table.RenderControl(writer);
}

This method is strictly speaking not needed in this context, but due to the complexity of our control hierarchy the entire GuestbookForm control will be encapsulated by tags. This isn't a problem, but I prefer to prevent it, and by overriding the Render method we do exactly that. The method AddAttributesToRender makes sure all of the controls are rendered with their attributes.

Ok that's the end of this class, but we have one more remaining. Although it isn't even close to as big as this one. So you'll soon be able to rest your eyes :)

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law.”