A Better BulletedList

Published: 13 Dec 2006
By: Speednet .

Enhanced version of the ASP.NET 2.0 BulletedList control to allow embedded HTML tags.

Introduction

ASP.NET 2.0 introduced a plethora of new controls aimed at making life easier for developers. The other day I was using one of those new controls, the BulletedList, for the first time, to help me generate and write an unordered list to the page at runtime.

I could find very little real-world usage information about the new BulletedList control on the web, but fortunately it's a very easy control to master.  It basically contains a collection of ListItem controls, one per bulleted item, and it can render each ListItem as plain text, a hyperlink to a URL, or as a hyperlink to initiate a PostBack.  The DisplayMode property of the BulletedList control specifies how each of the ListItems will render.

The page I was constructing used the BulletedList to build a simple plain-text list, using code similar to this:

Source (.aspx file)

<%@ Page Inherits="Speednet.Features" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" 
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Cool New Features</title>
  <style>
    h1 {font-size: 1.4em;}
  </style>
</head>
<body>
  <form runat="server">
    <h1>Cool New Features</h1>
    <asp:BulletedList ID="FeaturesList" 
	DisplayMode="Text" runat="server" />         
  </form>
</body>
</html>

Code-behind file (VB version)

Namespace Speednet

  Partial Class Features : Inherits System.Web.UI.Page

    Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) _
Handles MyBase.Load Dim feature() As String = {"Performance", _ "Security", _ "Usability"} Dim desc() As String = {"Page load times improved by 50%", _ "Now uses 128-bit SSL security", _ "Fewer mouse clicks to accomplish the same task"} For x As Integer = 0 To 2 FeaturesList.Items.Add(String.Format("<strong>{0}</strong> - {1}", _ feature(x), desc(x))) Next End Sub End Class End Namespace

Code-behind file (C# version)

namespace Speednet {

  public partial class Features : System.Web.UI.Page {

    protected void Page_Load(object sender, EventArgs e) {
      string[] feature = {"Performance", "Security", "Usability"};
      string[] desc = {"Page load times improved by 50%", 
            "Now uses 128-bit SSL security", 
            "Fewer mouse clicks to accomplish the same task"};

      for (int x = 0; x < 3; x++) {
        FeaturesList.Items.Add(String.Format("<strong>{0}</strong> - {1}", _
		feature[x], desc[x]));
      }
    }
  }
}

Author's Note:  The code-behind files above use arrays to store the features and descriptions for the unordered list, for readability purposes.  In production code, it is much better to use the new ASP.NET 2.0 generic classes for collections; in this case the Dictionary<TKey, TValue> class would be best.

To my surprise, the output made the bold text in my bullets look very bad:

Web page screen capture with literal STRONG tags displayed, instead of being rendered as bold text

All the angle brackets in the <strong> tags were being HTML encoded to &lt; and &gt;, which is normally a nice safety feature, but in this case I wanted to retain the angle brackets as-written so the page could see the HTML tags and render the bold text.

After I realized that the BulletedList control HTML encodes all plain text in each ListItem, I figured out fairly quickly that there was no option to output un-encoded (raw) text.  A Google search on the topic only turned up one person who posted a usenet question about the same problem, without any replies.

So I did what any responsible .NET coder would do: I built my own control.

RichTextBulletedList Control

The RichTextBulletedList control uses the very common technique of inheriting all the properties and methods of the .NET control (in this case from the BulletedList control), and overriding the Render() method to control the output.

The RichTextBulletedList's Render() method calls the base class's Render() method, but instead of allowing the base method to write directly to the page, a new HtmlTextWriter is created and is passed to the base method, rather than passing the page's HtmlTextWriter.

Once the output from the base class's Render() method is captured, it is then modified to restore angle brackets, as well as quote characters.  (In addition to the angle brackets, the base class also changes quotes (") into &quot;, so we need to take care of that too, so attribute values in the HTML tags can be recognized as such.) Then, the modified output is passed to the page.

Below is the complete source listing for the RichTextBulletedList control.  To create the control, create a new class file in the site's App_Code folder, and replace the default code in the new class file with the following:

RichTextBulletedList.vb (VB version)

Imports System.IO
Imports System.Text.RegularExpressions

Namespace Speednet

  Public Class RichTextBulletedList : Inherits BulletedList

    Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
      Dim sb As New StringBuilder()
      Dim sw As New StringWriter(sb)
      Dim htmlWriter As New HtmlTextWriter(sw)
      Dim strRendered As String

      MyBase.Render(htmlWriter)
      strRendered = Regex.Replace(sb.ToString(), "(?<!&lt;)&lt;(?!&lt;)", "<")
      strRendered = Regex.Replace(strRendered, "(?<!&gt;)&gt;(?!&gt;)", ">")
      strRendered = Regex.Replace(strRendered, "(?<!&quot;)&quot;(?!&quot;)", _
"""") ' Should be all on one line - split for readability strRendered = strRendered.Replace("&lt;&lt;", "&lt;") .Replace("&gt;&gt;", "&gt;") .Replace("&quot;&quot;", "&quot;") writer.Write(strRendered) End Sub End Class End Namespace

RichTextBulletedList.cs (C# version)

using System.IO;
using System.Text.RegularExpressions;

namespace Speednet {

  public class RichTextBulletedList : BulletedList {

    protected override void Render(System.Web.UI.HtmlTextWriter writer) {
      StringBuilder sb = new StringBuilder();
      StringWriter sw = new StringWriter(sb);
      HtmlTextWriter htmlWriter = new HtmlTextWriter(sw);
      String rendered;

      base.Render(htmlWriter);
      rendered = Regex.Replace(sb.ToString(), "(?<!&lt;)&lt;(?!&lt;)", "<");
      rendered = Regex.Replace(rendered, "(?<!&gt;)&gt;(?!&gt;)", ">");
      rendered = Regex.Replace(rendered, "(?<!&quot;)&quot;(?!&quot;)", "\"");

      rendered = rendered.Replace("&lt;&lt;", "&lt;")
          .Replace("&gt;&gt;", "&gt;")
          .Replace("&quot;&quot;", "&quot;");

      writer.Write(rendered);
    }
  }
}

Then, make two small changes to the example page, inserting a <%@ Register %> tag and changing the BulletedList tag to a RichTextBulletedList tag.  Here is the new page source:

Source (.aspx file)

<%@ Page Inherits="Speednet.Features" %>
<%@ Register TagPrefix="Speednet" Namespace="Speednet" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" 
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Cool New Features</title>
  <style>
    h1 {font-size: 1.4em;}
  </style>
</head>
<body>
  <form runat="server">
    <h1>Cool New Features</h1>
    <Speednet:RichTextBulletedList ID="FeaturesList" 
	DisplayMode="Text" runat="server" />         
  </form>
</body>
</html>

Now when the page is opened in a web browser we see the desired appearance, with the <strong> tags properly rendered as bold text.

Web page screen capture with STRONG tags being properly rendered as bold text

Other Thoughts

You may have noticed an odd line of code in the RichTextBulletedList source listing:

VB

// Should be all on one line - split for readability
strRendered = strRendered.Replace("&lt;&lt;", "&lt;")
              .Replace("&gt;&gt;", "&gt;")
              .Replace("&quot;&quot;", "&quot;")

C#

' Should be all on one line - split for readability

rendered = rendered.Replace("&lt;&lt;", "&lt;") .Replace("&gt;&gt;", "&gt;") .Replace("&quot;&quot;", "&quot;");

This is actually a fairly important line of code, because it retains the original BulletedList's ability to display HTML encoded angle brackets and quotes, even in the same line of text where some of the angle brackets and quotes have been un-encoded.

To keep an angle bracket or quote character HTML encoded (not interpreted as an HTML tag), just double-up the characters.  For example, to show the plain text string, "In all cases 1 < 2", you would create a ListItem with the text, "In all cases 1 << 2".

For creating good, reusable controls, the concept of adding functionality without eliminating existing functionality is important, and this shows that concept in a small way.

Summary

The RichTextBulletedList control is one of those nice, small additions to any ASP.NET toolkit to quickly solve a specific problem.

Rather than re-inventing the wheel by creating a completely new control, we built new functionality on top of the existing ASP.NET 2.0 BulletedList control, and to the greatest extend possible, we were very careful to preserve all of the existing functionality and behavior.

For developers who have not yet ventured into the realm of modifying existing controls, this can provide a simple example, not only to show that it can be fairly simple, but also to demonstrate some of the reusable code concepts you'll want to follow.

The new control's code also demonstrates how to capture and modify the output of a base class's Render() method, and through this example stresses the importance of using the base class's Render() method, rather than completely replacing it.

You can find an example of the RichTextBulletedList in action here.  In the lower-right portion of the page, the unordered list with bold and non-bold text is rendered using the RichTextBulletedList control.

Author Bio

Todd Northrop is the owner and creator of Lottery Post, the Internet's largest online lottery community.  He founded his company, Speednet Group, to perform consulting, web development, and web hosting.  Todd has specialized in software development using Microsoft products since around the time Visual Basic 3.0 made its debut, and has modeled and programmed databases from the time Microsoft acquired SQL Server from Sybase.  He originally created Lottery Post 7 years ago using classic ASP and SQL Server 2000, and is now in the process of rearchitecting the site using ASP.NET 2.0 with ASP.NET AJAX.  It will be a multi-year effort by the time it's complete, with the nice side-effect of requiring the creation of cool new techniques and features that he can write about.

About Speednet .

Sorry, no bio is available

View complete profile

Top Articles in this category

JavaScript with ASP.NET 2.0 Pages - Part 1
ASP.NET 2.0 has made quite a few enhancements over ASP.NET 1.x in terms of handling common client-side tasks. It has also created new classes, properties and method of working with JavaScript code. This article explores the enhancements and the various ways of injecting JavaScript programmatically into ASP.NET 2.0 pages.

ASP.NET ComboBox
The ASP.NET ComboBox is an attempt to try and enhance some of the features of the Normal ASP.NET DropDownList.

Upload multiple files using the HtmlInputFile control
In this article, Haissam Abdul Malak will explain how to upload multiple files using several file upload controls. This article will demonstrates how to create a webform with three HtmlInputFile controls which will allow the user to upload three files at a time.

JavaScript with ASP.NET 2.0 Pages - Part 2
ASP.NET provides a number of ways of working with client-side script. This article explores the usage and drawbacks of ASP.NET script callbacks, and briefly presents a bird's view of ASP.NET AJAX.

Using WebParts in ASP.Net 2.0
This article describes various aspects of using webparts in asp.net 2.0.

Top
 
 
 

Discussion


Subject Author Date
placeholder HtmlDecode Wesley Bakker 2/2/2007 9:43 AM
RE: HtmlDecode Speednet . 2/2/2007 10:25 AM
placeholder RE: HtmlDecode Speednet . 2/2/2007 10:26 AM
RE: HtmlDecode Speednet . 2/2/2007 10:27 AM
placeholder RenderBulletText Richard Deeming 2/3/2007 4:21 PM
Re: RenderBulletText Speednet . 2/3/2007 4:35 PM
placeholder lottery site Alberto Nigris 2/4/2007 6:17 AM
Re: lottery site Speednet . 2/4/2007 7:43 AM
placeholder Re: lottery site Speednet . 2/4/2007 7:45 AM
Nice Update UncleJohnsBand . 1/6/2007 6:30 PM

Please login to rate or to leave a comment.

Product Spotlight