Creating a BulletedList control to replace the asp:BulletedList
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 <a href="somepage.htm">click me</a> 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.