AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=359&pId=-1
Building Composite Server Controls
page
by Justin Lovell
Feedback
Average Rating: 
Views (Total / Last 10 Days): 35837/ 111

Introduction
First Article – The Basics of Building Server Controls
Second Article – Reusing and Creating Server Controls (Previous article)

Download the source code demonstrated in this article.

Ever wanted to merge two controls together to form one control? The need may arise in the future that you will have to pull multiple controls together to form one, unified server control. These “unified” server controls are given a name – called composite server controls.

Composite server controls are a collection controls to create one unified control; however, do not confuse this term that two control's functionality and behaviour are being combined/merged under one server control. Instead, rather think that composite controls as multiple controls are working together, for each other – to create more complex functionality. But what criterion must be met before a control is declared composite? It must be able to:

  • Contain other server controls as children – no matter how those server controls get there (you will see the different ways on how the children controls are added to the server controls in this article).

  • Hook up the children controls so they provide some unique functionality. In example: a TextBox control, a Button control and a Repeater control in a user control to provide some type of search functionality. The TextBox control will contain the search query and the Button control will fire a Click event to indicate the search must begin. Then, the search results will then be relayed in the Repeater control via data binding.

To give a few examples of composite controls, take a look at the DataGrid control, user controls and the ASP.NET page. The file based controls (the user control and ASP.NET page) are composite controls because they can hold an abundance of controls to create an user interface. They also hook up each others event to provide some type of functionality or meaning to the control – that is what a composite control is about: hooking up loose functionality from other controls.

The DataGrid is a composite control because it adds other controls inside itself when it is data bounded to a data source. The DataGrid also has the ability to create functionality of the controls it can add. For example, if a DataGrid's content can be edited (enabled by the page developer), the DataGrid uses a Button control (lets call it the edit button) to listen for the edit button's Click event. When the DataGrid hears the edit button click event, it will then issue an Ok/Cancel buttons (also Button controls) and the DataGrid will then listen to those buttons for further notifications. On top of the Ok/Cancel buttons being issued, the columns are converted to editable regions such as text boxes (this is default behaviour although this can be changed).

When the DataGrid hears that the Cancel button was clicked, then the DataGrid exits out of edit mode. If the DataGrid hears that the Ok button was clicked, it would then raise an event to detect that a row has been “edited” by the user. The event will change the DataGrid's internal DataSet (which is, by default, saved to view state to be re-used for every post back for that page). Furthermore, it will alert the code watching over the DataGrid that there has been changes (from the user) and it is up to the listeners to make any additional changes – normally, to a database.

Should I compose controls with user controls?
Composition of controls is easier to do with user controls. I do take the following into consideration when I decide whether or not to use a server control to compose a collection of controls:
  • The composition will be unique to that project and will be a specialized exception.

  • The composition will be used under one application domain.

  • It is not planned to be extended. User controls are not easy to extend at all.

For me, all of the above must evaluate to “true”. Essentially, it is a weighing decision of which path to go – the only advice that I can give you is to plan ahead. But before I go on, I am going to hold this thought with you, the decision also totally depends on what type of programmer you are (I am going to discuss the major differences in composition between user controls and server controls).

The major difference is if you are inclined and attracted to raw source code (some people use the terminology, code-behind). So if you are more attracted to the following code:

protected Image image = new Image();
protected Button button = new Button();
protected override void CreateChildControls() {
   Controls.Add(image);
   Controls.Add(button);
}

Over this type of source code:

<asp:Image runat="server" />
<asp:Button runat="server" />

Then composing server controls is for you. Another consideration is to decide how pressed you are for time – server controls take generally “longer” to create and is easier to extend while user controls are “shorter” to create and is harder to extend. Again, this is where the weight scale comes in the picture.

More Guidelines and Trends
In the very first article, I gave a few general trends and guidelines to follow. I also mentioned that I would give more of these when we touch on the appropriate subject... and here is the opportunity to give you some more guidelines and trends because we are at the appropiate subject:

Expose critical controls as protected properties (in the class level)

This is for any developer that may want to extend your composite control. This is to give developers that may want to extend your control a little bit further and they may need access to one of the controls in the composition. For example:

protected Image image = new Image();
protected Button button = new Button();
protected override void CreateChildControls() {
   Controls.Add(image);
   Controls.Add(button);
}

Ensure that you call the EnsureChildControls method at the critical point

I did not intend the pun at all. However, to this is to ensure that your CreateChildControls method is called once (provided that the EnsureChildControls method was used before). The EnsureChildControls method just checks if it has not called the CreateChildControls method before. If the ChildControlsCreated property is evaluated to false (when the EnsureChildControls method is called), it then calls the CreateChildControls method and sets the ChildControlsCreated property to true. Otherwise, the CreateChildControls method will not be executed.

It is best to call this as late as possible; but before the Render event of the page life cycle. It is also best to set default "values" on the controls that are being composed on the constructor of the server control containing the composition. Just before you have to do your processing, call the EnsureChildControls.

Do not render strings – compose controls

I have seen many control developers using strings to build the mark-up for their controls... and this is what essentially, composition is all about – removing the need for the output to be contained in repetive strings. Using other server controls in place is more ideal because those controls may be optimized to deliver “better” mark-up for specific browsers.

Besides that, it is a lot easier to work with objects (server controls in this case) than with raw strings; although, performance will be slightly slower (server controls vs. strings), it is a small price to pay for easier maintenance on the server control.

Who to inherit from for the composing control?

This has to do with the same discussion in the previous article Reusing and Creating Server Controls. The exact page where this was discussed was on page.

What if the composition gets a little bit too big for the controls to handle?

Sometimes, we all need an unique name to a control. Because of possible clashes with other sibling controls, you may implement the INamingContainer interface. This interface does not contain any members but advises ASP.NET to make a new naming namespace for the control and for all the child controls. Sometimes, it is necessary to implement this but not all the time it is needed.

Create interactive controls before rendering and display controls during rendering

If you want to create a control to render a “template” for the web browser, it would be best if the control is not added to the control tree at all. For example, you may just want to add a simple table output (and template it out as the following code):

protected override void Render(HtmlTextWriter writer) {
   Table table = new Table();
   table.Width = Unit.Percentage(85);
   for (int j = 0; j < 3; j++) {
      TableRow row = new TableRow();
      for (int i = 0; i < 3; i++) {
         TableCell cell = new TableCell();
         cell.Text = String.Format("Row: {0}; Column: {1}",
            j.ToString(), i.ToString());
         row.Cells.Add(cell);
      }
      table.Rows.Add(row);
   }
   table.Rows[2].Cells[1].Text = "Something different";
   writer.Write("<h3>Hello before the table.</h3>");
   table.RenderControl(writer);
   writer.Write("<h3>Hello after the table.</h3>");
}

However, if you need functionality (interaction) from the composition (the “templated” output), add the controls to the Control tree. The reason for this is for the control to become “alive” in the page life cycle, it has to exist in a "live" tree that "hooks" up with the page control tree. This will include interaction for the post-backs and data binding.

Note: I am using “templating the composition” and “templated composition” as loose terminology - please read the page titled "An Ending Note".

Code Example – Interactive Composition
You might have noticed that I did not mention to create composition server controls from the CreateChildControls method. There is no strict rule or enforcement with this because of the “templated” composition that may occur (we discussed it on the last line. However, we are going to look at an example with the CreateChildControls method:

The example will be based on a search control. The control will contain two controls – a TextBox control and a Button control. We are going to do the simple UI and simple functionality first before we will add bells and whistles.

public class InteractiveSearchBox : Table, INamingContainer {
   protected TextBox SearchText = new TextBox();
   protected Button SearchButton = new Button();
   protected override HtmlTextWriterTag TagKey {
      get { return HtmlTextWriterTag.Table; }
   }
   protected override void CreateChildControls() {
      TableRow row = new TableRow();
      TableCell textBoxCell = new TableCell();
      TableCell buttonCell = new TableCell();
      buttonCell.Width = Unit.Pixel(1);
      SearchTextBox.Width = Unit.Percentage(100);
      textBoxCell.Controls.Add(SearchTextBox);
      buttonCell.Controls.Add(SearchButton);
      row.Cells.Add(textBoxCell);
      row.Cells.Add(buttonCell);
      Rows.Add(row);
   }
   protected override void OnInit(EventArgs e) {
      SearchTextBox.ID = "searchText";
      SearchButton.ID = "searchButton";
      SearchButton.Text = "Search";
      base.OnInit(e);
      EnsureChildControls();
   }
   public InteractiveSearchBox() {
      Width = Unit.Percentage(100);
   }
}

Ah – running the code will output a table with a width of 100%, by default. The width can be changed from the Width property on the server control that is composing the controls. The table cells are then placed into one row; which the tow then added to the TableRowCollection which is accessible from the Rows property. But what is distinct about table cells is that it contains a text box (which will fill most of the area) and a search button.

One other essential thing that was added was the INamingContainer interface. This will be required when the same type of control becomes siblings to each other. For example, if I have search boxed for manufacturers and clients in the same PlaceHolder control, the names of the text boxes and the buttons will conflict (ASP.NET will throw an error). One interface, with no members, is what is required to make the control into a container namespace for its child controls - avoiding this conflict of names.

We are going to add two more functional features before we can declare this as a search box control. Those two features are:

  • I property exposing the text that was typed into the text box.

  • Add an event listener so we can fire our own event that we expose.

Lets do the rest of the code:

public event EventHandler Search;
public string SearchText {
   get { return SearchTextBox.Text; }
}
protected virtual void OnSearch(EventArgs e) {
   if (Search != null)
      Search(this, e);
}

That code was pretty standard (it was highlighted before in the article series). We will now do our own firing for our own event by listening to the button's Click event. We will have to do one modification to the constructor to hook up the other “unknown method”:

private void SearchButton_Click(object sender, EventArgs e) {
   // call our custom event
   OnSearch(e);
}
public InteractiveSearchBox() {
   // the other code over here
   SearchButton.Click += new EventHandler(SearchButton_Click);
}

All of this code can be accomplished with “templating” the output as well; however, complications will occur with sibling controls. For this reason, “templating” is used to give a common output – as shown on the next page.

Code Example - “Template” Composition
“Templating” the output is a simple task with one thing in mind – to give common output. The short-fall of “templating” your output is the following:
  • You may run into naming problems with siblings.

  • Functionality cannot be used. The controls are never added into the page's life stream; therefore, no interaction may occur.

The example that I am going to demonstrate with “templated” output is a score board for a soccer match. The example will be a very simple one with no fancies – this article is only here to demonstrate the technique, not a how-to example.

The score board will have four properties – two integers for the score; and, two strings for the names of the sides (namely, home team and visiting team). This is the basic definition that our server control will take:

public class TemplatedScoreBoard : Control {
   private string pHomeName;
   private string pVisitorName;
   private int pHomeScore = 0;
   private int pVisitorScore = 0;
   public string HomeName {
      get { return pHomeName; }
      set { pHomeName = value; }
   }
   public string VisitorName {
      get { return pVisitorName; }
      set { pVisitorName = value; }
   }
   public int HomeScore {
      get { return pHomeScore; }
      set {
         if (value < 0)
            throw new ArgumentOutOfRangeException();
         else
            pHomeScore = value;
      }
   }
   public int VisitorScore {
      get { return pVisitorScore; }
      set {
         if (value < 0)
            throw new ArgumentOutOfRangeException();
         else
            pVisitorScore = value;
      }
   }
}

And here is the code that makes up the “templated” output:

protected override void Render(HtmlTextWriter writer) {
   Table table = new Table();
   TableRow headerRow = new TableRow();
   TableCell headerCell = new TableCell();
   TableRow homeTeamRow = new TableRow();
   TableCell homeTeamNameCell = new TableCell();
   TableCell homeTeamScoreCell = new TableCell();
   TableRow visitorTeamRow = new TableRow();
   TableCell visitorTeamNameCell = new TableCell();
   TableCell visitorTeamScoreCell = new TableCell();
   table.Width = Unit.Percentage(100);
   headerCell.ColumnSpan = 2;
   headerCell.HorizontalAlign = HorizontalAlign.Center;
   headerCell.Text = "Score Board";
   headerCell.Style.Add("font-size", "22px");
   headerCell.Style.Add("font-weight", "bold");
   headerRow.Cells.Add(headerCell);
   table.Rows.Add(headerRow);
   homeTeamNameCell.Text = HomeName;
   homeTeamScoreCell.Text = HomeScore.ToString();
   homeTeamScoreCell.Width = Unit.Percentage(15);
   homeTeamRow.Cells.Add(homeTeamNameCell);
   homeTeamRow.Cells.Add(homeTeamScoreCell);
   table.Rows.Add(homeTeamRow);
   visitorTeamNameCell.Text = VisitorName;
   visitorTeamScoreCell.Text = VisitorScore.ToString();
   visitorTeamScoreCell.Width = Unit.Percentage(15);
   visitorTeamRow.Cells.Add(visitorTeamNameCell);
   visitorTeamRow.Cells.Add(visitorTeamScoreCell);
   table.Rows.Add(visitorTeamRow);
   table.RenderControl(writer);
}

Note that the Table control is never, ever added to the Controls collection – that is why the Table control does not exist in the page life cycle.

An Ending Note
Hopefully, I have explained in detail on how to go about the two different techniques for composite controls. You can mix the two techniques together for more complex controls as well. There is one other technique which will be used in the next article; and it is about real templates in server controls.

As I mentioned before, I used the “templated output” as a very loose term in this article; however, the term does stick in some way because it describes the context in which it was used to describe what the code effectively does. Please do not get confused between the two articles.

A side note on the article series: I will be changing the schedule of the articles as described in this blog post. Essentially, I will try to publish a new article every second Monday from here on. That is why this article was “late” in a sense.

Again, if you have any comments and/or suggestions about the way I present the articles, you may give the feedback via the feedback link provided on this page.


Product Spotlight
Product Spotlight 

©Copyright 1998-2020 ASPAlliance.com  |  Page Processed at 2020-07-04 8:20:20 PM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search