Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]

.pdf
Скачиваний:
107
Добавлен:
16.08.2013
Размер:
29.8 Mб
Скачать

928C H A P T E R 2 7 C U S TO M S E R V E R C O N T R O L S

The following example is designed for one of these scenarios. It shows how you can customize the rendering of a derived Label control for a specific type of content.

In Chapter 12, you learned about the Xml control, which allows you to display XML content in a page using an XSLT stylesheet. However, the Xml control doesn’t give you any way to show arbitrary XML. So, what should you do if you want to duplicate the Internet Explorer behavior, which shows a color-coded tree of XML tags? You could implement this approach using an XSLT stylesheet. However, another interesting choice is to create a custom Label control that’s designed for XML content. This Label control can apply the formatting you want automatically.

First, consider what happens if you try to display XML content without taking any extra steps? In this case, all the XML tags will be interpreted as meaningless HTML tags, and they won’t be shown. The display will simply show a jumbled block of text that represents all the content of all elements from start to finish. You can improve upon this situation slightly by using the HttpServerUtility.HtmlEncode() method, which replaces all special HTML characters with the equivalent character entities. However, the XML display you’ll create with this approach is still far from ideal. For one thing, all the whitespace will be collapsed, and all the line breaks will be ignored, leading to a long string of text that’s not easy to interpret. Figure 27-10 shows this approach with the DvdList.xml document used in Chapter 12.

Figure 27-10. Displaying XML data with HTML escaping

The custom XmlLabel control solves this problem by applying formatting to XML start and end tags. This functionality is wrapped into a static method called ConvertXmlTextToHtmlText(), which accepts a string with XML content and returns a string with formatted HTML content. This functionality is implemented as a static method rather than an instance method so that you can call it to format text for display in other controls.

The ConvertXmlTextToHtmlText() method uses a regular expression to find all the XML tags in the string. Here’s the expression you need:

<([^>]+)>

This expression matches the less-than sign (<) that starts the tag, followed by a sequence of one or more characters that aren’t greater-than signs (>). The match ends as soon as a greater-than (>) sign is found. This expression matches both start tags (such as <DvdList>) and end tags (such as </DvdList>).

C H A P T E R 2 7 C U S TO M S E R V E R C O N T R O L S

929

Tip You might think you could use a simpler regular expression such as <.+> to match a tag. The problem is that regular expressions use greedy matching, which means they often match as much as possible. As a result, an expression such as <.+> will match everything between the less-than (<) sign of the first tag to the greater-than sign (>) in the last tag at the end of document. In other words, you’ll end up with a single match that obscures other embedded matches. To prevent this behavior, you need to create a regular expression that explicitly specifies what characters you don’t want to match.

Once you have a match, the next step is to replace this text with the text you really want. The replacement expression is as follows:

<<b>$1></b>

This replacement uses the HTML entities for the less-than and greater-than signs (< and >), and it adds an HTML <b> tag to format the text in bold. The $1 is a back reference that refers to the bracketed text in the search expression. In this example, the bracketed text includes the full tag name—everything between the opening < and the closing >.

Once the tags are in bold, the last step is to replace the spaces in the string with the   character entity so that whitespace will be preserved. At the same time, it makes sense to replace all the line feeds with an HTML <br />.

Here’s the complete code for formatting the XML text:

public static string ConvertXmlTextToHtmlText(string inputText)

{

// Replace all start and end tags. string startPattern = @"<([^>]+)>"; Regex regEx = new Regex(startPattern);

string outputText = regEx.Replace(inputText, "<<b>$1></b>");

outputText = outputText.Replace(" ", " "); outputText = outputText.Replace("\r\n", "<br />"); return outputText;

}

The rest of the XmlLabel code is remarkably simple. It doesn’t add any new properties. Instead, it simply overrides the RenderContents() to ensure that the formatted text is rendered instead of the ordinary text:

protected override void RenderContents(HtmlTextWriter output)

{

string xmlText = XmlLabel.ConvertXmlTextToHtmlText(Text); output.Write(xmlText);

}

Note that this code doesn’t call the base implementation of RenderContents(). That’s because the goal of the XmlLabel control is to replace the rendering logic for the label text, not to supplement it.

Figure 27-11 shows what ordinary XML data looks like when displayed in the XmlLabel control. Of course, now that you have the basic framework in place, you could do a lot more to perfect this output, including color-coding and automatic indenting.

930 C H A P T E R 2 7 C U S TO M S E R V E R C O N T R O L S

Figure 27-11. Displaying formatted XML data

Tip You could use a similar technique to create a label that automatically converts mail addresses and URLs to links (wrapped by the <a> tag), formats multiple lines of text into a bulleted list, and so on.

Templated Controls

Up to this point, the controls you’ve seen have rendered themselves based on the logic and code within the control. The consumers of the control (the web pages that use it) do not have the ability to directly define the layout and style of the control’s content.

Templated controls and styles allow you to create controls and add functionality without needing to lock users into a fixed layout. With templates, the control consumer provides a set of HTML tags that define the information and formatting used by the control. The templated control uses one or more templates to render portions of its interface. As a result, templated controls can be much more flexible than ordinary controls.

ASP.NET includes several controls that support templates, including the Repeater, DataList, GridView, and FormView. In the following sections, you’ll learn how to support templates in your own controls.

Creating a Templated Control

It’s surprisingly easy to create a basic templated control. You start by creating a composite control. This control should derive from WebControl and implement the INamingContainer interface to make sure that every child control has a unique name.

The next step is to create one or mote template containers. A template container allows the user to specify the template declaratively in the .aspx portion of the web page. To support a template, you just need a control property that accepts an ITemplate object, as shown here:

C H A P T E R 2 7 C U S TO M S E R V E R C O N T R O L S

931

private ITemplate itemTemplate;

public ITemplate ItemTemplate

{

get {return itemTemplate;} set {itemTemplate = value;}

}

Note that the template isn’t stored in view state, because it’s always retrieved from the .aspx file, and it doesn’t change programmatically. That means you can store it in a private variable and recreate it with each postback.

The ITemplate interface defines a single method, InstantiateIn(), which creates an instance of a template inside an existing control. Essentially, when the InstantiateIn() method is called, ASP.NET parses the template and creates controls based on the tags and code in the template. These controls are then added to the control container that’s passed into the method. For example, if a template contains a single Label tag, then calling InstantiateIn() creates a Label control and adds it to the Controls collection of the specified container. Your control uses the InstantiateIn() method to render its templates.

The final ingredient is the CreateChildControls() method. This is the place where you create the template using the InstantiateIn() method and add it to the Controls collection.

To understand how this all works together, consider the following extremely simple templated control. It defines a single template and an additional property that lets the user choose how many times the template should be repeated in the web page. Overall, it works more or less the same as the simple Repeater control (without any support for data binding). Here’s the complete code:

public class SuperSimpleRepeater : WebControl, INamingContainer

{

public SuperSimpleRepeater() : base()

{

RepeatCount = 1;

}

public int RepeatCount

{

get {return (int)ViewState["RepeatCount"];} set {ViewState["RepeatCount"] = value;}

}

private ITemplate itemTemplate; public ITemplate ItemTemplate

{

get {return itemTemplate;} set {itemTemplate=value;}

}

protected override void CreateChildControls()

{

// Clear out the control collection before starting. Controls.Clear();

if ((RepeatCount > 0) && (itemTemplate!=null))

{

// Instantiate the template in a panel multiple times. for (int i = 0; i<RepeatCount; i++)

{

Panel container = new Panel();

932 C H A P T E R 2 7 C U S TO M S E R V E R C O N T R O L S

itemTemplate.InstantiateIn(container);

Controls.Add(container);

}

}

else

{

// Show an error message. Controls.Add(new LiteralControl(

"Specify the record count and an item template"));

}

}

}

To use this control, you need to provide a template for the ItemTemplate property. You can do this declaratively by adding the HTML and control tags in an <ItemTemplate> tag. Here’s an example:

<apress:SuperSimpleRepeater id="sample" runat="server" RepeatCount="10"> <ItemTemplate>

<div align="center">

<hr />Creating templated controls is <b>easy</b> and <i>fun</i>.<br /><hr /> </div>

</ItemTemplate>

</apress:SuperSimpleRepeater>

Figure 27-12 shows the rendered content, which copies the template HTML into the page ten times.

Figure 27-12. Repeating a template

This example has neglected one detail. All template controls should use the PersistChildren attribute, as shown here:

[PersistChildren(true)]

public class SuperSimpleRepeater : WebControl, INamingContainer { ... }

C H A P T E R 2 7 C U S TO M S E R V E R C O N T R O L S

933

This tag indicates that all child elements in the control tag should be interpreted as properties. As a result, if you add an <ItemTemplate> tag inside the <SuperSimpleRepeater> tag, the ASP.NET parser will assume the <ItemTemplate> tag defines the content for the SuperSimpleRepeater.ItemTemplate property. If your control derives from WebControl, this is already the default behavior, so you don’t need to take this step. However, it’s still a good practice to include this attribute to explicitly indicate how the control deals with nested tags.

If you apply the PersistChildren with an argument of false, the ASP.NET parser assumes that any nested tags are child controls. It then creates the corresponding control object and passes it your control by calling the AddParsedSubObject() method. The default implementation of this method simply adds the child control to the Controls collection of the current control, although you can change this behavior by overriding this method.

Using Customized Templates

As you can see, creating a basic templated control isn’t difficult and doesn’t require much code. However, the previous example still lacks a few key features. For one thing, it doesn’t allow you to access any information from the templated items. It would be much more useful if there were a way to access some basic information about each item. Using this information, you could write databinding expressions in your template, as you can with templated controls such as the Repeater and DataList.

To support this technique, you need to create a custom control class to use as a template container. This control needs to include properties that provide the information in which you’re interested. The following example shows a custom template container that provides two properties: an item number representing the index of the template in the series and the total number of items:

public class SimpleRepeaterItem : WebControl, INamingContainer

{

int index; public int Index

{

get {return index;}

}

int total; public int Total

{

get {return total;}

}

public SimpleRepeaterItem(int itemIndex, int totalCount)

{

index = itemIndex; total = totalCount;

}

}

Note that because this control acts as a template container, it needs to implement the INamingContainer interface.

Now you need to adjust the CreateChildControls() method so that it creates instances of the SimpleRepeaterItem control, instead of an ordinary Panel control. Each instance of the SimpleRepeaterItem will then hold a single instance of the item template.

But before you get to this point, it’s worth making the templated control a little more sophisticated. The next example adds a header and footer template and an alternating item template. With these four templates, the programmer will have much more control over the layout of the content. Here’s the template code you need:

934C H A P T E R 2 7 C U S TO M S E R V E R C O N T R O L S

private ITemplate itemTemplate; [TemplateContainer(typeof(SimpleRepeaterItem))] public ITemplate ItemTemplate

{

get {return itemTemplate;} set {itemTemplate=value;}

}

private ITemplate alternatingItemTemplate; [TemplateContainer(typeof(SimpleRepeaterItem))] public ITemplate AlternatingItemTemplate

{

get {return alternatingItemTemplate;} set {alternatingItemTemplate=value;}

}

private ITemplate headerTemplate; [TemplateContainer(typeof(SimpleRepeaterItem))] public ITemplate HeaderTemplate

{

get {return headerTemplate;} set {headerTemplate=value;}

}

private ITemplate footerTemplate; [TemplateContainer(typeof(SimpleRepeaterItem))] public ITemplate FooterTemplate

{

get {return footerTemplate;} set {footerTemplate=value;}

}

Note that each template property uses the TemplateContainer attribute to indicate what type of container your control will use when it instantiates the template.

Now you can revise the CreateChildControls() method. The CreateChildControls() will create instances of the SimpleRepeaterItem container and pass the current index and the total item count as constructor arguments. Then, it will add the SimpleRepeaterItem as a child control of the SuperSimpleRepeater.

protected override void CreateChildControls()

{

Controls.Clear();

if ((RepeatCount > 0) && (itemTemplate!=null))

{

//Start by outputing the header template (if supplied). if(headerTemplate != null)

{

SimpleRepeaterItem headerContainer =

new SimpleRepeaterItem(0, RepeatCount); headerTemplate.InstantiateIn(headerContainer); Controls.Add(headerContainer);

}

//Output the content the specified number of times. for (int i = 0; i<RepeatCount; i++)

{

C H A P T E R 2 7 C U S TO M S E R V E R C O N T R O L S

935

SimpleRepeaterItem container =

new SimpleRepeaterItem(i+1, RepeatCount);

if ((i%2 == 0) && (alternatingItemTemplate != null))

{

//This is an alternating item and there is an

//alternating template. alternatingItemTemplate.InstantiateIn(container);

}

else

{

itemTemplate.InstantiateIn(container);

}

Controls.Add(container);

}

//Once all of the items have been rendered,

//add the footer template if specified.

if (footerTemplate != null)

{

SimpleRepeaterItem footerContainer =

new SimpleRepeaterItem(RepeatCount, RepeatCount); footerTemplate.InstantiateIn(footerContainer); Controls.Add(footerContainer);

}

}

else

{

// Show an error message. Controls.Add(new LiteralControl(

"Specify the record count and an item template"));

}

}

This has one additional caveat. For data binding to work with the new SuperSimpleRepeater control, you need to call the DataBind() method of the header, footer, and item containers. To make sure this critical step takes place, you need to override the DataBind() method. By default, the DataBind() method binds all the child controls in the Controls collection. However, your overridden implementation needs to call EnsureChildControls() first to make sure all the template containers have been created before the control is bound. Here’s the code you need:

public override void DataBind()

{

//Make sure the template containers have been created. EnsureChildControls();

//Bind all the child controls.

base.DataBind();

}

You can now test the new SuperSimpleRepeater with the following control tag and templates:

<apress:SuperSimpleRepeater2 id="sample" runat="server" RepeatCount="10"> <HeaderTemplate>

<h2 style="Color:Red">Super Simple Repeater Strikes Again!</h2> Now showing <%# Container.Total %> Items for your viewing pleasure.

</HeaderTemplate>

<ItemTemplate>

936 C H A P T E R 2 7 C U S TO M S E R V E R C O N T R O L S

<div align="center">

<hr />Item <%# Container.Index %> of <%# Container.Total%><br /><hr /> </div>

</ItemTemplate>

<AlternatingItemTemplate>

<div align="center" style="border-right: fuchsia double; border-top: fuchsia double; border-left: fuchsia double; border-bottom: fuchsia double">

Item <%# Container.Index %> of <%# Container.Total%> </div>

</AlternatingItemTemplate>

<FooterTemplate>

<i>This presentation of the Simple Repeater Control brought to you by the letter <b>W</b></i>

</FooterTemplate>

</apress:SuperSimpleRepeater2>

Note how the <ItemTemplate> and <AlternatingItemTemplate> sections use data binding expressions that refer to Container. These expressions are evaluated against the properties of the container object, which in this example is an instance of the SimpleRepeaterItem class. All your web page needs to do is call the SuperSimpleReader.DataBind() method when the page loads. You can call SuperSimpleReader.DataBind() directly, or you can call it indirectly through the Page.DataBind() method, as shown here:

private void Page_Load(object sender, System.EventArgs e)

{

Page.DataBind();

}

Figure 27-13 shows the new repeater control in action. Odd items (1, 3, 5, and so on) use the normal item template, while even items (2, 4, 6, and so on) use the alternative item template with the double border.

Tip As you saw with templated controls such as the Repeater and DataList, it is common practice to extend the container control to provide a DataItem property. When a data item is read from the data source, the data item is passed to the container, which then exposes it and allows the web page to bind to it. In this way, the templated control becomes ultimately flexible, because it doesn’t need to know anything about the type or structure of the data it’s displaying.

Styles

In the templated examples that you’ve seen so far, it’s up to the web page to supply HTML elements for the template and the style attributes that tailor their appearance. Many templated controls simplify this process through style objects. In ASP.NET, the System.Web.UI.WebControls.Style class represents the complete collection of style information including colors, fonts, alignment, borders, and spacing. Using this class, you can easily add style support to your templated controls.

For example, consider the SuperSimpleRepeater presented in the previous example, which uses four templates (item, alternating item, header, and footer). Using the Style class, you can define four corresponding style properties, one for each template.

C H A P T E R 2 7 C U S TO M S E R V E R C O N T R O L S

937

Figure 27-13. Repeating more advanced templates

Here’s an example of the style property for the header:

private Style headerStyle public Style HeaderStyle

{

get

{

if (headerStyle == null)

{

headerStyle = new Style();

}

return headerStyle;

}

}

Note In this example, the style information is not persisted in view state. This approach reduces the overall page size. However, it also means that if you change style information programmatically, it will be reset after every postback.