One of the features of my product Mister Lister is that it creates a list of the records in the database. I do this with an ASP.NET datagrid control. Since users can select which columns should display in the list, the columns are dynamically generated at run-time. Up until now, I had been doing this with a bound column.
However, while Graham was doing some testing yesterday, he discovered that he could generate errors with certain key combinations. As it turns out, I had left the ValidateRequest attribute set to True, and ASP.NET was warning me that some of the data looked a little like script and was potentially dangerous.
At first, I looked at ways of using a regular expression to trap this within a validation control instead of generating an error. But I'm no regular expression expert, and the examples I found were too limiting on regular input (in my opinion). I realized that the problem isn't really when I save the data, it is when I redisplay it to users. I just need to make sure that I am using HTMLEncode when displaying the data in certain controls like labels or literals.
No problem, I thought. I only do this on the list page. However, this meant that I couldn't dynamically generate my datagrid with bound columns, because I couldn't format the data with a bound column. So, I needed to change to a dynamically generated template column.
Most of the examples I found for this were in C#, or didn't seem to work properly. (I even had trouble with the example in MSDN.) So, I've included my code for doing this here. Hopefully this will help some other person who is trying to do this in VB.NET.
First, I created a separate class that implements ITemplate. In my case, I used a constructor that would pass in the column name and column type, since I need to do different formatting depending on the type of data.
By implementing the ITemplate interface, you need to override this method. Within ITemplate, I used AddHandler to hook the DataBinding method of the container to subroutine within the class to format the data. Here's what this new class looks like:
Public Class DataGridTemplate
Private columnName As String
Private ColumnType As String
Private WithEvents lbl As Label
'Sub New - Constructor
Public Sub New(ByVal ColName As String, ByVal ColType As String)
Me.columnName = ColName
Me.ColumnType = ColType
'Sub InstantiateIn - From ITemplate
Private Sub InstantiateIn(ByVal container As Control) Implements ITemplate.InstantiateIn
lbl = New Label
AddHandler container.DataBinding, AddressOf BindDataCtrl
'Sub BindDataCtrl - Data binding event
Private Sub BindDataCtrl(ByVal sender As Object, ByVal e As EventArgs) Handles lbl.DataBinding
Dim container As DataGridItem = CType(lbl.NamingContainer, DataGridItem)
Dim str As String = (CType(container.DataItem, DataRowView))(columnName).ToString()
Select Case ColumnType
If str <> "" Then
lbl.Text = Format(CDate(str), "MM/dd/yyyy")
lbl.Text = ""
lbl.Text = HttpContext.Current.Server.HtmlEncode(str)
In my code for binding the datagrid, I create a new instance of this class for every column I want to add. Here's a snippet:
'For each column the the display fields...
For i = 0 To mdvDisplayFields.Count - 1
dr = mdvDisplayFields.Item(i).Row
tc = New TemplateColumn
tc.ItemTemplate = New DataGridTemplate(dr.Item("ColumnName"), dv.Table.Columns(CStr(dr.Item("ColumnName"))).DataType.ToString)
tc.HeaderText = CStr(dr.Item("Caption"))
tc.HeaderStyle.Width = System.Web.UI.WebControls.Unit.Pixel(CInt(dr.Item("ListWidth")))
That's it! Of course, if I needed to do more than just HTMLEncode the text (like if I wanted to add controls, etc.), then this could be more complicated. But this should get you started!