WindowsDevCenter.com
oreilly.comSafari Books Online.Conferences.

advertisement


AddThis Social Bookmark Button

Programming ASP.NET: Custom and User Controls, Part 2
Pages: 1, 2, 3, 4, 5

Creating the BookInquiryList composite control

Each of the BookCounter objects is contained within the Controls collection of the BookInquiryList. This control has no properties or state. Its only method is Render, as shown in C# in Example 14-22 and in VB.NET in Example 14-23.

Example 14-22: BookInquiryList source in C#


[ControlBuilderAttribute(typeof(BookCounterBuilder)),ParseChildren(false)]
public class BookInquiryList : System.Web.UI.WebControls.WebControl, INamingContainer
{
 
   protected override void Render(HtmlTextWriter output)
   {
      int totalInquiries = 0;
      BookCounter current;
 
      // Write the header
      output.Write("<Table border='1' width='90%' cellpadding='1'" +
         "cellspacing='1' align = 'center' >");
      output.Write("<TR><TD colspan = '2' align='center'>");
      output.Write("<B> Inquiries </B></TD></TR>");
 
      // if you have no contained controls, write the default msg.
      if (Controls.Count == 0)
      {
         output.Write("<TR><TD colspan = '2'> align='center'");
         output.Write("<B> No books listed </B></TD></TR>");         
      }
      // otherwise render each of the contained controls
      else
      {
         // iterate over the controls colelction and
         // display the book name for each
         // then tell each contained control to render itself
         for (int i = 0; i < Controls.Count; i++)
         {
            current = (BookCounter) Controls[i];
            totalInquiries += current.Count;
            output.Write("<TR><TD align='left'>" +
               current.BookName + "</TD>");
            output.RenderBeginTag("TD");
            current.RenderControl(output);
            output.RenderEndTag(  );  // end td
            output.Write("</tr>");
         }
         output.Write("<TR><TD colspan='2' align='center'> " +
            " Total Inquiries: " +
            totalInquiries + "</TD></TR>");
      }
      output.Write("</TABLE>");
   }
}

Example 14-23: BookInquiryList source in VB.NET


Imports System.ComponentModel
Imports System.Web.UI

<ControlBuilder(GetType(BookCounterBuilder)), ParseChildren(False)> _
Public Class BookInquiryList
   Inherits System.Web.UI.WebControls.WebControl
   Implements INamingContainer

   Protected Overrides Sub Render(ByVal output As HtmlTextWriter)

      Dim totalInquiries As Integer = 0
 
      ' Write the header
      output.Write("<Table border='1' width='90%' cellpadding='1'" & _
         "cellspacing='1' align = 'center' >")
      output.Write("<TR><TD colspan = '2' align='center'>")
      output.Write("<B> Inquiries </B></TD></TR>")
 
      ' if you have no contained controls, write the default msg.
      If Controls.Count = 0 Then
          output.Write("<TR><TD colspan = '2'> align='center'")
          output.Write("<B> No books listed </B></TD></TR>")
          ' otherwise render each of the contained controls
      Else
         ' iterate over the controls colelction and
         ' display the book name for each
         ' then tell each contained control to render itself
         Dim current As BookCounter
 
         For Each current In Controls
            totalInquiries += current.Count
            output.Write("<TR><TD align='left'>" & _
               current.BookName + "</TD>")
            output.RenderBeginTag("TD")
            current.RenderControl(output)
            output.RenderEndTag()            ' end td
            output.Write("</tr>")
         Next
         Dim strTotalInquiries As String
         strTotalInquiries = totalInquiries.ToString
         output.Write("<TR><TD colspan='2' align='center'> " & _
            " Total Inquiries: " & _
            CStr(strTotalInquiries) & "</TD></TR>")
      End If
      output.Write("</TABLE>")
   End Sub
 
End Class
 
Friend Class BookCounterBuilder
   Inherits ControlBuilder
 
   Public Overrides Function GetChildControlType( _
         ByVal tagName As String, ByVal attributes As IDictionary) As Type
      If tagName = "BookCounter" Then
         Dim x As BookCounter
         Return x.GetType
      Else
         Return Nothing
      End If
   End Function
 
   Public Overrides Sub AppendLiteralString(ByVal s As String)
   End Sub
 
End Class

ControlBuilder and ParseChildren attributes

The BookCounter class must be associated with the BookInquiryClass so ASP.NET can translate the elements in the .aspx page into the appropriate code. This is accomplished using the ControlBuilder attribute:

[ControlBuilderAttribute(typeof(BookCounterBuilder)),ParseChildren(false)]

The argument to the ControlBuilderAttribute is a Type object that you obtain by passing in BookCounterBuilder, a class you will define to return the type of the BookCounter class given a tag named BookCounter. The code for the BookCounterBuilder is shown in C# in Example 14-24 and in VB.NET in Example 14-25.

Example 14-24: C# version of BookCounterBuilder


internal class BookCounterBuilder : ControlBuilder
{
   public override Type GetChildControlType(
      string tagName, IDictionary attributes)
   {
      if (tagName == "BookCounter")
         return typeof(BookCounter);
      else
         return null;
   }
 
   public override void AppendLiteralString(string s)
   {
   }
}

Example 14-25: VB.NET version of BookCounterBuilder


Friend Class BookCounterBuilder
    Inherits ControlBuilder
 
    Public Overrides Function GetChildControlType(_
        ByVal tagName As String, ByVal attributes As Idictionary) As Type
      If tagName = "BookCounter" Then
        Dim x As BookCounter
        Return x.GetType
      Else
        Return Nothing
      End If
    End Function
 
    Public Overrides Sub AppendLiteralString(ByVal s As String)
    End Sub
 
End Class

ASP.NET will use this BookCounterBuilder, which derives from ControlBuilder, to determine the type of the object indicated by the BookCounter tag. Through this association, each of the BookCounter objects will be instantiated and added to the Controls collection of the BookInquiryClass.

The second attribute, ParseChildren, must be set to false to tell ASP.NET that you have handled the children attributes and no further parsing is required. A value of false indicates that the nested child attributes are not properties of the outer object, but rather are child controls.

Render

The only method of the BookInquiryClass is the override of Render. The purpose of Render is to draw the table shown earlier in Figure 14-15, using the data managed by each of the BookCounter child controls.

The BookInquiryClass provides a count of the total number of inquiries, as shown in Figure 14-16.


Figure 14-16. Total inquiries displayed

The code tallies inquiries by initializing an integer variable, totalInquiries, to zero and then iterating over each control in turn, asking the control for its Count property. The statement is the same in C# and VB.NET, except for the closing semicolon in C#:

totalInquiries += current.Count;

The Count property of the control delegates to the CountedButton's count property, as you can see if you step through this code in a debugger, as illustrated in Figure 14-17.


Figure 14-17. Stepping into BookCounter.Count

Rendering the output

That same loop renders each of the child controls by iterating over each of the controls. In C#, this is done using:

for (int i = 0; i < Controls.Count; i++)
{
   current = (BookCounter) Controls[i];
   totalInquiries += current.Count;
   output.Write("<TR><TD align='left'>" +
      current.BookName + "</TD>");
   output.RenderBeginTag("TD");
   current.RenderControl(output);
   output.RenderEndTag(  );  // end td
   output.Write("</tr>");
}

In VB.NET, the code is:

For Each current in Controls
   totalInquiries += current.Count
   output.Write("<TR><TD align='left'>" & _
      current.BookName + "</TD>")
   output.RenderBeginTag("TD")
   current.RenderControl(output)
   output.RenderEndTag(  )            ' end td
   output.Write("</tr>")
Next

The local BookCounter object, current, is assigned to each object in the Controls collection in succession:

for (int i = 0; i < Controls.Count; i++)
{
   current = (BookCounter) Controls[i];

With that object, you are able to get the Count, as described previously:

totalInquiries += current.Count;

and then you proceed to render the object. The HtmlTextWriter is used first to create a row and to display the name of the book, using the BookName property of the current BookCounter object:

output.Write("<TR><TD align='left'>" +
   current.BookName + "</TD>");

You then render a TD tag, and within that tag you tell the BookCounter object to render itself. Finally, you render an ending TD tag using RenderEndTag, and an ending row tag using the Write method of the HTMLTextWriter:

output.RenderBeginTag("TD");
 current.RenderControl(output);
 output.RenderEndTag(  );  // end td
 output.Write("</tr>");

When you tell the contained control to render itself:

current.RenderControl(output);

the Render method of BookCounter is called. Since you have not overridden this method, the Render method of the base class is called, which tells each contained object to render itself. The only contained object is CountedButton. Since you have not overridden Render in CountedButton, the base Render method in Button is called, and the button is rendered.

Assignment of Responsibilities

This simple example of a composite control is interesting because the various responsibilities are spread among the participating objects. The BookInquiryList object assumes all responsibility for laying out the control, creating the table, and deciding what will be rendered where. However, it delegates responsibility for rendering the button object to the individual contained controls.

Similarly, the BookInquiryList is responsible for the total number of inquiries--because that information transcends what any individual BookCounter object might know. However, the responsibility for the count held by each BookCounter is delegated to the BookCounter itself. As far as the BookInquiryList is concerned, it gets that information directly from the BookCounter's Count property. It turns out, however, that BookCounter in turn delegates that responsibility to the CountedButton.

Rendering the summary

Once all of the child controls have been rendered, the BookInquiryList creates a new row to display the total inquiries:

output.Write("<TR><TD colspan='2' align='center'> " +
   " Total Inquiries: " + 
   totalInquiries + "</TD></TR>");

Programming ASP.NET

Related Reading

Programming ASP.NET
By JesseŠLiberty, DanŠHurwitz

Back to the .NET DevCenter.