65.9K
CodeProject is changing. Read more.
Home

Creating a BulletedList control to replace the asp:BulletedList

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.67/5 (8 votes)

Jul 11, 2008

CPOL

4 min read

viewsIcon

47181

downloadIcon

150

I needed a simple control to display a bulleted list based on a strongly typed list of strings.

Introduction

I wanted to display a bulleted list of items (<li> tags rendered within a <ul> tag) on a web page, based on a strongly typed list of string objects which I had gathered from some data source.

I noticed that there is a BulletedList control available in .NET, so I tried it. I set the DataSource to my List(Of String) and called DataBind().

It worked — sort of. The bulleted list displayed the content of each string in my list, but it insisted on HTML-encoding every item. As I had hyperlinks within the list (<a href="somepage.htm">click me</a>), it was rendering to the output stream as &lt;a href="somepage.htm"&gt;click me&lt;/a&gt; such that I did not get a hyperlink in the page. Frustrating. Thanks for that auto-encoding, Microsoft!

Background

I spent a little while looking into the BulletedList control and Googling this problem, but it seemed to be just a "feature" of the BulletedList which wasn't controllable (I might be wrong!). Instead of using the asp:BulletedList, I wrote a Repeater to render out the items, but it looked messy in the .aspx page, and I wasn't really happy with it — I wanted a control as I wanted to use it in a few places — a Repeater might have been OK for just one instance, but not several.

I threw my hands up at another short-sighted "feature" of the framework, and went out to enjoy a meatball sub and oatmeal and raisin cookie, with a diet coke on the side.

In the sandwich shop, whilst watching the July rain driving at the pavements outside, I decided to just write a control myself to do what I wanted — I thought I had quite a basic requirement here, and decided that in the time I spent Googling for the solution, I could have solved it myself. Now, all I had to do was get back across the road to the office, in a short sleeved shirt, in the pouring rain. I love Manchester.

Developing the Code

My first step was to create a .vb class file, which I called BulletedList.vb. I put the class inside a namespace to make it easy to register it later on, and made it inherit from Control.

Next, I created a property called "Items" which provides read/write access to the list of items I wanted to render. Remember, I wanted to use a strongly typed list of strings. I save the items within the ViewState of the control.

        Public Property Items() As List(Of String)
            Get
                If IsNothing(ViewState("Items")) Then
                    ' Instantiate the list of items to a new list; which will allow us to do ".Items.Add" from elsewhere in code without 
                    ' having to worry about null values checks.
                    ViewState("Items") = New list(Of String)
                End If
                Return DirectCast(ViewState("Items"), List(Of String))
            End Get
            Set(ByVal value As List(Of String))
                ViewState("Items") = value
            End Set
        End Property

So, I can now hold a list of the strings I want to display on the page. The next thing is to tell the control how it should render them. I simply overrode the "Render" method of the control, and provided some custom logic:

        Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
            ' Only output something if there are items in the collection:
            If Items.Count > 0 Then
                ' Output start tag <ul>
                writer.RenderBeginTag(HtmlTextWriterTag.Ul)

                ' Output all the items
                For Each s As String In Items
                    writer.RenderBeginTag(HtmlTextWriterTag.Li)
                   
                    writer.Write(s)
                    writer.RenderEndTag()

                    writer.WriteLine()
                Next

                ' Output end tag </ul>

                writer.RenderEndTag()
            End If

            MyBase.Render(writer)
        End Sub

As you can see, it is reasonably straightforward. If there are any items in the list, then I create the opening <ul> tag, and then write out each item contained within <li> tags. Once all the items have been written, I close the <ul> tag, and call the base Render method (it doesn't actually do anything, it's just there for completeness).

HTML Encoding the Text

I appreciate the sentiment of HTML-encoding the text from a security point of view; that HTML data retrieved from data stores (especially where that data has come from an unknown source) should always be HTML-encoded before it is displayed out onto web pages to help prevent injection attacks.

For this reason, I provided another property, EncodeHtml, to allow the user of the control to decide whether they want the output to be encoded before it gets rendered. The property is a simple boolean value saved in the ViewState:

        Public Property EncodeHtml() As Boolean
            Get
                If IsNothing(ViewState("EncodeHtml")) Then
                    Return False
                Else
                    Return DirectCast(ViewState("EncodeHtml"), Boolean)
                End If
            End Get
            Set(ByVal value As Boolean)
                ViewState("EncodeHtml") = value
            End Set
        End Property

I then amended the Render method to take account of this property, and HTML-encode the text if desired. This concludes the control's implementation.

        Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
            ' Only output something if there are items in the collection:
            If Items.Count > 0 Then
                ' Output start tag <ul>
                writer.RenderBeginTag(HtmlTextWriterTag.Ul)

                ' Output all the items
                For Each s As String In Items
                    writer.RenderBeginTag(HtmlTextWriterTag.Li)
                    If EncodeHtml Then
                        writer.Write(HttpContext.Current.Server.HtmlEncode(s))
                    Else
                        writer.Write(s)
                    End If
                    writer.RenderEndTag()

                    writer.WriteLine()
                Next

                ' Output end tag </ul>

                writer.RenderEndTag()
            End If

            MyBase.Render(writer)
        End Sub

Using the Code

Using the code is simple. First, register the control at the top of the .aspx file:

<%@ Register TagPrefix="SCC" Namespace="SCC.WebUserControls" %>

Next, add the control into the .aspx markup where you want the bulleted list to appear:

<SCC:BulletedList ID="blHyperlinks" runat="server" /> <!-- Render all items "as-is" -->

<SCC:BulletedList ID="blSafeHtml" runat="server" EncodeHtml="true" /> <!-- Encode HTML -->

Finally, in Page_Load or similar, bind the control to your desired list of strings:

Dim Hyperlinks As New List(Of String)
Me.blHyperlinks.Items = Hyperlinks

and that's it. "Should just work".

The downloadable file contains a fully XML-commented code listing for your perusal. Any comments welcome (particularly, if you can tell me whether the asp:BulletedList can be configured to not HTML-encode the text that you bind to it from a strongly typed list of strings!).

History

  • 1.0 (Original, 14 July 2008): Original version.
  • 1.1 (15 July 2008): Fixed a null-reference when the control rendered, if no items were in the list.