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
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)
If Items.Count > 0 Then
writer.RenderBeginTag(HtmlTextWriterTag.Ul)
For Each s As String In Items
writer.RenderBeginTag(HtmlTextWriterTag.Li)
writer.Write(s)
writer.RenderEndTag()
writer.WriteLine()
Next
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)
If Items.Count > 0 Then
writer.RenderBeginTag(HtmlTextWriterTag.Ul)
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
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" />
<SCC:BulletedList ID="blSafeHtml" runat="server" EncodeHtml="true" />
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.