Having a custom control display properly is a challenge in itself.
Getting your custom control to behave the way you want it to is only half the work. Once you get to the visual side of things you have to create the logic that generates the actual HTML shown in the browser. If you want the control to display properly, this can be a tedious task, especially if you want it to render properly in different browsers.
A control's Render method will render a control in the last step of a control's life cycle, just before the control is disposed. You can override the Render method to create custom output. If you create a new Web Custom Control in Visual Studio .NET (VS.NET), the skeleton code already overrides this method, so all you have to do is fill in the blanks. The only parameter for the Render method is an HtmlTextWriter object. This is a specific TextWriter object that writes HTML to the output stream. The HtmlTextWriter object provides a set of Write methods that you can use to create your output. The most basic methods are Write and WriteLine, part of all TextWriter objects. So if you would want to render your control showing the Text property in red, you could use these methods as shown in Listing 1. Listing 1 would yield the following HTML:
<p>
<font color="red">My Text</font>
</p>
You can see how error prone this code is, as it requires you to create all the tags by hand. The HtmlTextWriter object provides several methods to make this easier. Methods such as WriteBeginTag, WriteEndTag, and WriteAttribute will write tags and attributes for you, so you don't have to worry about quotes and such. Listing 2 shows how you can use these methods to create the same HTML as before.
By default ASP.NET renders HTML 3.2 for all non-Microsoft browsers, even for Netscape 6 that does support HTML 4.0 and CSS properly.
You may have noticed that the HTML rendered with Listing 1 and Listing 2 is HTML 3.2 without CSS styles. My samples use a <font> tag to change the font color. Although this works, it is considered better practice these days to use CSS. You could, of course, write style tags yourself, but again there's a method that can help you: WriteStyleAttribute. This method write the values for the Style attribute, so in order to add styles to a tag, you actually still have to write the attribute itself, as shown in Listing 3. This latest listing renders the HTML below, which is equivalent to the HTML 3.2 shown earlier (for browsers supporting CSS).
<p style="color:red">My Text</p>
With the different Write methods you can create any HTML or non-HTML output you desire.
The next step is differentiating between different browsers, so you can actually create different HTML for different browsers. To check the type of the requesting browser you can use the HttpBrowserCapabilities object. This object, accessible through Page.Request.Browser, contains all sorts of properties about the client browser. You can check the browser's name and version, whether it supports frames, tables, Java-applets, etc. Strangely absent from this list is whether or not the browser supports CSS (and if so what version), even though that information is stored in machine.config where all browser capabilities are listed. To get around this you have two options, each equally undesirable. The first is using your own knowledge of browsers to decide whether or not you should use CSS, for example:
HttpBrowserCapabilities client;
client = Page.Request.Browser;
if(client.MajorVersion > 3 &&
(client.Browser == "IE" ||
client.Browser == "Netscape"))
{
//Render CSS
} else
//Render HTML 3.2
}
Obviously the above code can never cover all browsers, so you may end up rendering HTML 3.2 to a browser supporting CSS. If your styles are more complex, for instance labels with background colors, the HTML 3.2 experience will be less than perfect.
Rendering tags is much easier and quicker than writing everything by hand. You are, however, subject to the quirks of ASP.NET's rendering mechanism.
You second option is checking the type of the HtmlTextWriter passed to the Render method, which can be either HtmlTextWriter or Html32TextWriter. The former indicates that a browser supports HTML 4.0 with CSS, and the latter indicates that the browser only supports HTML 3.2 without CSS. You could therefore limit your browser check to the following code:
if(output.GetType().ToString()== "HtmlTextWriter")
{
//Render CSS
} else
//Render HTML 3.2
}
The downside to checking the type of the passed HtmlTextWriter is that virtually all non-Microsoft browsers, including Netscape versions that do support CSS, pass Html32TextWriter to the Render method. So even a Netscape 6 browser would receive HTML 3.2, even though it supports CSS perfectly. The only way to get around this is by changing machine.config to add:
tagwriter=System.Web.UI.HtmlTextWriter
to each browser that supports CSS. This is risky though, because this may not only impact CSS support. So test if your application still works properly after changing machine.config.
Rendering Instead of Writing
Although what you've seen so far will certainly get the job done, it is not the most appealing way of creating output. It is tedious and even with methods such as WriteBeginTag, still error prone. The alternative is to let ASP.NET render tags with attributes and formatting. Rendering tags is much easier and quicker than writing everything by hand. You are, however, subject to the quirks of ASP.NET's rendering mechanism. With rendering you don't have control over every character written to the output, but ASP.NET renders HTML 3.2 or HTML 4.0 with CSS, depending on the HtmlTextWriter passed to the Render method. This has the same drawback as checking the HtmlTextWriter type, but you can again change this by changing machine.config. Listing 4 shows code that renders the text property in red, and sets Verdana as the font. If you passed Html32TextWriter to the Render method, Listing 4 would result in the following HTML:
<p><font face="Verdana"
color="Red">Netscape</font></p>
Otherwise it results in HTML using CSS as shown below:
<p style="color:Red;font-family:Verdana">
My Text</p>
Because you don't have to deal with the browser differences, this saves you a lot of work. Also notice that you no longer write tags explicitly, but that you tell the HtmlTextWriter to render a certain tag from a known set of tags. When rendering end tags, ASP.NET automatically renders the end tag corresponding to the last rendered begin tag, making sure that you never nest tags improperly. Also, notice that all formatting you want to apply to a tag needs to be added to the HtmlTextWriter before you render the begin tag.
Using the Style Class
Up until now you've had to hard code all the formatting in the samples. You can remedy this situation by adding public properties to the control that enables the user of the control to set color, font, and so on. If you were to add a property for every type of formatting you could apply to a control, you'd have to create quite a few properties, and make sure that they are all rendered when rendering tags. There's a better solution: You can save yourself a lot of work by using the Style class in the System.Web.UI.WebControls namespace. The Style class contains all properties that you find in a Label control: background color, text color, font, and borders, just to name a few. In fact, when you use a control such as the Calendar control, the separate styles you can set, for instance SelectedDayStyle, are based on the Style class. There are several derived classes such as the TableStyle class that implement additional formatting properties that apply to certain HTML tags. You can use each of these to add your own styles to a control by adding properties with type Style, as shown in Listing 5. (Note: The Style property in Listing 5 is not added to the ViewState. How you can add custom styles to the ViewState is beyond the scope of this article.) In Listing 5 the AddAttributesToRender method on MyStyle is called to add all the formatting to the tag that's being rendered. The added formatting is rendered by the HtmlTextWriter as best it can. This means that background color for text will not be shown when rendering HTML 3.2. For most complex controls you shouldn't try to get around that within a control. The sidebar "Browser Dependent Templates," offers a solution that is better in most cases.
When rendering end tags, ASP.NET automatically renders the end tag corresponding to the last rendered begin tag, making sure that you never nest tags improperly.
If you've inherited your control from an existing control, or from System.Web.UI.WebControl, your control most likely already provides the Style class to change the overall formatting of the control. This is known as the ControlStyle, but control users can just refer to the items in the Style class directly, so they can use:
Font-Names="Verdana,Arial"
instead of specifying a specific style such as the SelectedDayStyle, like this:
SelectedDayStyle-Font-Names="Verdana,Arial"
Within the Render method you can apply this style to a tag by using:
this.ControlStyle.AddAttributesToRender(output);
You can also override the RenderContents method, instead of the Render method. By doing so you ensure that ASP.NET writes the outer tag and formatting automatically. All you have to do in RenderContents is render everything inside the main tags.
Composite Controls
When you build composite controls, the Render method takes on a different form. Since the control tree is already built once you enter the Render method, all you can do is provide the surrounding formatting, and apply formatting to the controls already in the tree. You still need to use the child control's rendering capabilities to render the control. Not doing so makes the child control invisible to the user and probably useless within the control. For the formatting surrounding the controls you can use the same methods as before?you can format the child controls either explicitly or by setting properties on the controls, or by applying a style. To apply a style, you again need a Style object. You can use the ControlStyle if you just want to propagate the main style to the child controls, or one defined in a public Style property. Listing 6 shows the body code for a composite control that shows a TextBox with a caption. The Caption takes the formatting of the control itself, but you can format the TextBox through a separate style. Notice that I defined the TextBox globally because multiple methods need to access it?first when the code adds the control to the control tree, and later to apply the style and render the control. You could also apply the style in the CreateChildControls method, but that would cause the style of the child control to be stored in the ViewState, in addition to the Style object that has been applied. This would result in a larger ViewState for no reason whatsoever. The ApplyStyle method copies all properties from the originating style to the ControlStyle of the child control, overriding any existing style properties already set on the child control. If you only want to override blank properties, you should use the MergeStyle method instead.
Conclusion
You can apply formatting to controls in several ways. The best way differs per control, but in most cases you will want to keep the outer tags of the control as defined by the parent control. This means you should override the RenderContents method instead of the Render method. When you work with composite controls, keep in mind that you can tweak the formatting of the child controls during the Render phase. In addition, you can add additional HTML during this phase so you can actually create a sparse control tree surrounded by HTML generated in the Render or RenderContents method. This is more efficient than adding more controls to the control tree.