Introduction :
This article will address the use of the
HtmlTextWriter class and the role is plays in the construction of custom server
controls. As custom server control development is accomplished without a visual
designer, the HtmlTextWriter class provides a mechanism for precisely defining
the output of the custom control directly within the code and in absence of the
designer.
Whilst the HtmlTextWriter class has quite a few
moving parts, this discussion will focus upon a few of the more common and
frequently used parts of the class and will demonstrate how they may be used to
render custom controls. This is not a complete or thorough treatment of the
topic but rather just enough information to get someone started with no prior
experience with the class.
Purpose
For the purposes of this discussion, the
HtmlTextWriter class is used to output properly formed HTML at the insertion
point of the custom control into a web page. The HtmlTextWriter allows the
developer to code attributes, style attributes, and tags as well as providing
the means to render subordinate controls within a custom server or composite
control.
As far as usage goes, controls are generally
rendered by overriding the RenderContents subroutine; this article addresses the
use of the HtmlTextWriter class as it may be used to render a control in the
overridden RenderContents subroutine. This article does not address all aspects
of custom control development; it is strictly limited to the basics associated
with the HtmlTextWriter class.
HtmlTextWriter Class
The HtmlTextWriter class permits the developer to
generate page output in a non-browser specific way; that is to say, the burden
of developing alternative markup is typically removed by using the
HtmlTextWriter. The HtmlTextWriter class exposes properties, fields, and methods
that permit the developer to format HTML 4.0 compliant output. There is also an
XhtmlTextWriter class that performs a similar function for XHTML as used with
different device types but this discussion will be limited to the HtmlTextWriter
although what is said of one generally applies to other with the obvious
differences in the markup.
In the last paragraph I'd stated that the
HtmlTextWriter typically relieves the developer of the burden of rendering
alternative markup. The reason I used the word typically is because, like most
good things, the HtmlTextWriter class can be somewhat abused as a function of
its flexibility. This misuse of the class usually occurs through the Write
method which will render out just about anything into the HTML generated for the
page.
Because of this feature, the developer can
circumvent the intent of the class and write tags and content out directly to
the page at the insertion point. Right, wrong, it does not matter, if it is
wrapped up in a string and submitted to the Write method, it is headed for the
page source. There are several issues with this but the most important is likely
to be the fact that using this approach offers no real mechanism for catching an
error before rendering out the page.
For example, one could code something like this:
output.Write("<table><tr>")
output.Write("<td>Name: </td><td>Frank</td></tr>")
output.Write("<tr>")
output.Write("<td>Name: </td><td>Jessie</td></tr>")
output.Write("<tr>")
output.Write("<td>Name: </td><td>Wyatt</td></tr>")
output.Write("</table>")
Which will result in the display of this table:
As you can see, all that was done was to place HTML into a string and pass that
string directly output's Write method. Output is an instance of the
HtmlTextWriter. Since this example is quite simple it renders out onto the page
just fine and will render in Firefox or IE7 without any issues. The real problem
occurs with added complexity and the increased potential for errors. If I
introduce a single line error as so:
output.Write("<teble><tr>")
What happens is this:
As can
be seen in the screen shot, the result is no error, no warning, no table. The
markup supplied to the page is as follows:
<span
id="WebCustomControl1_1"><teble><tr><td>Name:
</td><td>Frank</td></tr><tr><td>Name: </td><td>Jessie</td></tr><tr><td>Name:
</td><td>Wyatt</td></tr></table></span>
In
contrast, to render out the same table using the HtmlTextWriter as I believe it
was intended to be used results in this code:
'start
the table
output.RenderBeginTag(HtmlTextWriterTag.Table)
'start the row
output.RenderBeginTag(HtmlTextWriterTag.Tr)
'add data
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("Name:
")
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("Freddie")
output.RenderEndTag()
'end the row
output.RenderEndTag()
'start the row
output.RenderBeginTag(HtmlTextWriterTag.Tr)
'add data
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("Name:
")
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("Jason")
output.RenderEndTag()
'end the row
output.RenderEndTag()
'start the row
output.RenderBeginTag(HtmlTextWriterTag.Tr)
'add data
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("Name:
")
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("Leatherface")
output.RenderEndTag()
'end the row
output.RenderEndTag()
'end the table
output.RenderEndTag()
As you
can see, there are more lines of code associated with this type of use of the
class, however, it is safer. In fact, if you were to try to introduce the same
error, you'd find that you will not get very far
In the
image above, you see that the error is highlighted and this error will prevent
building the control as indicated in the next figure.
It
may also be interesting to note that the source for the page is more properly
formed without any additional effort:
<span id="WebCustomControl1_1"><table>
<tr>
<td>Name: </td><td>Freddie</td>
</tr><tr>
<td>Name: </td><td>Jason</td>
</tr><tr>
<td>Name: </td><td>Leatherface</td>
</tr>
</table></span>
Note that the web control is placed into a span
as is the default behavior. The beginning tag can be overridden in the control
by overriding a read only property called TagKey. After overriding the TagKey
property with a table tag, the page source will be as follows: (Note the span is
gone and the control is started with the table tag)
<table id="WebCustomControl1_1">
<tr>
<td>Name: </td><td>Freddie</td>
</tr><tr>
<td>Name: </td><td>Jason</td>
</tr><tr>
<td>Name: </td><td>Leatherface</td>
</tr>
</table>
The code to override the TagKey to use the table
tag is as follows:
Protected
Overrides ReadOnly
Property TagKey()
As System.Web.UI.HtmlTextWriterTag
Get
Return HtmlTextWriterTag.Table
End Get
End Property
Of course after making this change, the code used
to render the control should be modified by dropping the table related beginning
and ending tags and just starting off with the first row tag. If you overrode
the TagKey property with a div tag, you would still need to add the beginning
and ending table tags. The page source for that would end up looking like this:
<div>
<div id="WebCustomControl1_1">
<table>
<tr>
<td>Name: </td><td>Freddie</td>
</tr><tr>
<td>Name: </td><td>Jason</td>
</tr><tr>
<td>Name: </td><td>Leatherface</td>
</tr>
</table>
</div>
</div>
Adding Attributes
It is possible to add attributes to the control's
HTML through the use of the HtmlTextWriter class. Attributes added with the
HtmlTextWriter need to be added in advance of a beginning tag. For example:
'start the table
output.AddAttribute(HtmlTextWriterAttribute.Border,
"2")
output.AddAttribute(HtmlTextWriterAttribute.Cellpadding,
"3")
output.RenderBeginTag(HtmlTextWriterTag.Table)
'start the row
output.RenderBeginTag(HtmlTextWriterTag.Tr)
'add data
output.AddAttribute(HtmlTextWriterAttribute.Align,
"left")
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("Name: ")
output.RenderEndTag() ...
In looking at the example note that, prior to the
beginning table tag, the HtmlTextWriter's AddAttribute method was called.
AddAttribute is looking for an HtmlTextWriterAttribute tag and a value to
associate with the tag. The example passes a tag (e.g.,
HtmlTextWriterAttribute.Border) and a value for the tag ("2"). It is possible to
create an error here, for example, one could to pass value of "A" in lieu of "2"
to the border.
Adding Styles
As with attributes, styles may be added using the
HtmlTextWriter class, and also as with attributes, the style attributes need to
be added in advance of the beginning tag for the targeted item. Style attributes
are added with the HtmlTextWriter's AddStyleAttribute method. This method
accepts two arguments as did the AddAttribute method. The first argument is the
style and the second argument is the value.
In the example below, the table has the font size
set to "Large" and the width of the table is set to 100%.
'start the table
output.AddStyleAttribute(HtmlTextWriterStyle.FontSize,
"Large")
output.AddStyleAttribute(HtmlTextWriterStyle.Width,
"100%")
output.AddAttribute(HtmlTextWriterAttribute.Alt,
"Its a table")
output.AddAttribute(HtmlTextWriterAttribute.Border,
"2")
output.AddAttribute(HtmlTextWriterAttribute.Cellpadding,
"3")
output.RenderBeginTag(HtmlTextWriterTag.Table)
'start the row
output.RenderBeginTag(HtmlTextWriterTag.Tr)
The page source shows the style attributes added
to the table:
<div id="WebCustomControl1_1">
<table alt="Its a table" border="2"
cellpadding="3"
style="font-size:Large;width:100%;">
<tr>
<td align="left">Name: </td><td
align="left">Freddie</td>
</tr><tr>
<td>Name: </td><td>Jason</td>
</tr><tr>
<td>Name: </td><td>Leatherface</td>
</tr>
</table>
</div>
Adding Controls
The HtmlTextWriter may also be used to add
subordinate controls to the custom control. The following examples show adding
different objects to the custom control through the use of object's
RenderControl method.
Adding an image:
output.AddAttribute(HtmlTextWriterAttribute.Align,
"center")
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.RenderBeginTag(HtmlTextWriterTag.Td)
Dim img As
New Image()
img.ImageUrl = SourceImg.ToString()
img.BorderStyle = WebControls.BorderStyle.Inset
img.BorderWidth = 2
img.RenderControl(output)
output.RenderEndTag()
output.RenderEndTag()
Adding a text box:
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.AddStyleAttribute(HtmlTextWriterStyle.FontFamily,
Me.Font.Name)
output.AddAttribute(HtmlTextWriterAttribute.Size, _
Me.Font.Size.ToString())
txtAmount.RenderControl(output)
output.RenderEndTag()
Adding a button:
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.AddStyleAttribute(HtmlTextWriterStyle.FontFamily,
Me.Font.Name)
output.AddAttribute(HtmlTextWriterAttribute.Size, _
Me.Font.Size.ToString())
btnSubmit.RenderControl(output)
output.RenderEndTag()
With the exception of the image in the first example, each of the objects would
likely be instantiated in the overridden CreateChildControls subroutine; from
there the control could have its properties set and could have event handlers
added to the instance. The controls are then also added to the control
collection. In this case, the RenderContents method merely places the existing
controls. Having said that, the image example provided first shows the creation
of the image within the RenderContents subroutine.
The
following example illustrates adding a creating button in the
CreateChildControls subroutine; in the example, the button is created, its text
property is set, and the click event handler is added to the button; the button
is then added to the control collection:
' creats the submit button
and assigns it a handler
btnSubmit =
New Button
btnSubmit.Text =
"Submit"
AddHandler btnSubmit.Click,
AddressOf btnSubmit_Click
Me.Controls.Add(btnSubmit)
Using
the same approach indicated in the examples, one could add other types of
subordinate controls to the custom control.
Summary
This article was intended to provide a brief
introduction to the HtmlTextWriter class in the context of using it to render
custom controls. This is not an all encompassing description of the class and it
does define all of the things that you can through the class. The intent was to
provide a sufficient introduction to the topic to allow one to use the class to
define an interface for a custom control.