Solved

ASP.NET: HTMLEncoding a DataGrid

Posted on 2002-07-05
11
3,540 Views
Last Modified: 2010-05-18
Can anyone help me with HTMLEncoding a DataGrid?

SUMMARY: HTMLEncoding DataGrid text (whether it requires encoding or not) for any linkbuttons makes them disappear.

As a DataGrid is only used for displaying data in a browser, I would have thought that each DataColumn would have a property for HTMLEncode where, if set to true, would HTMLEncode the data being bound to the grid, and, if set to false, it would display the data as is.

But, no, there is no such property...well, none that I could find anyway.

The solution that I have come up with so far goes part of the way:
1. It uses the DataGrid_ItemDataBound event. This event fires for every TableRow that is bound to the DataGrid.

2. I iterate through each of the cells in the TableRow HTMLEncoding the text as I go.

Now this solution works great for normal BoundColumns. However, for ButtonColumns (and probably HyperlinkColumns and maybe TemplateColumns), HTMLEncoding the text results in the cell/link/button disappearing altogether, leaving nothing for the user to click/push/see/whatever.

Can someone please help me with better solutions to the following workarounds? You will see from the attached code for this event that these workarounds are:

1. Funnily enough, the DataGrid Header & Footer also trigger this event. So the first thing I had to do was to filter out these calls. I would prefer it if I didn't have to do this though because, for a generic solution, there is likely to be a day when I want XML tag-style headers, e.g. "<ID>" instead of just "ID", and I would like these to display.

2. For the moment, my linkbuttons are just for the ID field in my TableGrid. And the ID field is numeric. Therefore, I know that HTMLEncoding the text will have no effect. I therefore do a quick check to see if HTMLEncoding would produce a different result. If it does, I change it. Again, like in 1, there will be a day when I want to display some XML or HTML as a linkbutton.

So, who's up for the challenge :-) ?

Regards

Robin

----------------------------
ATTACHED CODE - START
----------------------------
/// <summary>
/// Event that fires when a DataSet TableRow binds to the DataGrid.
/// It iterates through each cell in the TableRow, ensuring that all
/// the text being displayed is HTML Encoded.
/// <seealso cref="ms-help://MS.VSCC/MS.MSDNVS/cpref/html/frlrfSystemWebUIWebControlsDataGridClassItemDataBoundTopic.htm">DataGrid.ItemDataBound Event</seealso>
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void dgIssues_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e)
{
      DataGridItem item = (DataGridItem)e.Item;
      ListItemType itemType = item.ItemType;
      if
      (
            //Changing the text of the following items makes them disappear
            //completely :-( so filter them out here
            !(      (itemType == ListItemType.Header) ||
                  (itemType == ListItemType.Footer) ||
                  (itemType == ListItemType.Pager)
            )
      )
      {
            #region TODO [RDavis]
            ///TODO [RDavis]: Although this next foreach block actually checks
            ///whether HTML Encoding the DataGrid cell would cause a
            ///difference, this was only done as a stop-gap measure to stop
            ///the IssueID hyperlinks disappearing like the Header
            ///hyperlinks did before I performed the check above.
            ///Ideally, there should be some way to check what type of
            ///control is going to be displayed in the DataGrid cell, and
            ///amend its Text property regardless of whether it would be
            ///changed or not. This is because it seems that changing the
            ///Text value of what would be a linkbutton etc seems to make
            ///it lose the fact that it is going to be a linkbutton etc and
            ///so decides to be nothing instead (as it wasn't going to be
            ///plain Text anyway). Unfortunately, it seems (well, from what
            ///I could find anyway) that the control (and hence its type)
            ///are not accessible here. I expect that this is because the
            ///control hasn't yet been created as we're in the middle of a
            ///databind (and thus about to create the control).
            #endregion TODO [RDavis]
            TableCellCollection cells = (TableCellCollection)item.Cells;
            foreach (TableCell cell in cells)
            {
                  if (cell.Text != HttpUtility.HtmlEncode(cell.Text.ToString()))
                  {
                        cell.Text = HttpUtility.HtmlEncode(cell.Text.ToString());
                  }
            }//foreach
      }//if
}//dgIssues_ItemDataBound
----------------------------
ATTACHED CODE - END
----------------------------
0
Comment
Question by:Noggy
11 Comments
 
LVL 4

Expert Comment

by:CoolAss
ID: 7132696
Interesting problem... one possible solution might be:

if(e.Item.Cells[0].FindControl("linkbutton1") != null)
{
//Don't encode
}
else
{
//Encode
}

Where "linkbutton1" signifies a control that would be adversely affected by HTMLEncoding.
0
 
LVL 4

Author Comment

by:Noggy
ID: 7133517
Hi CoolAss,

Yes, it is an interesting problem, the problem also being that, when I looked at the Controls.Count property for the item, it said that it was 0. I can double-check this on Monday when I'm back in work but I am pretty sure that that was what it said.

It seems to me that, this event is fired before the Controls are actually created. Of course, I expect that you could create your own controls at this time. However, I have used the Property Builder for the DataGrid and would like to keep this consistent i.e. if I start creating DataColumns dynamically at runtime, I may as well do it for all the DataColumns and ignore the Property Builder (which would be a shame).
0
 
LVL 4

Expert Comment

by:CoolAss
ID: 7133990
Well, by the time we get to ItemDataBound, all the child controls should have been long since created. I don't know why it says they don't exist yet.

Another possible solution might be to use a custom template (by implementing ITemplate).

This custom template could vary depending on whether or not you want to HTMLEncode the cell.
0
 
LVL 2

Expert Comment

by:Check
ID: 7138740
I had this same sort of problem myself a while back.  I fixed it by doing a "replace" in the stored procedure or sql command that pulled the data in the first place.

ex: select replace(my_field_name,'&','%26') from my_table

Probably not the most elegant solution but it was a quick fix for my situation.

Check
0
What Security Threats Are You Missing?

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

 
LVL 4

Author Comment

by:Noggy
ID: 7138843
Hi Check,

Yes, indeed, that is one solution. It is a solution that I had already considered but had rejected as we are developing a generic solution that can have different UI channels (not just the Web via http).

Changing the methods in the Data Access Layer would tie us to the Web (though, of course, we could create duplicate methods: one HTMLEncoded and one not - but even this is dangerous for a generic solution).

No, as I said previously, as the DataGrid is purely for UI representation, I was quite surprised that there isn't an HTMLEncode property on it. With today's emphasis on XML, Web Services etc, much data in databases will have XML-style tags all over the place.

I am raising this "bug" with Microsoft Premier Services too so will let you know what they come up with.

**********************************
CoolAss - I didn't get chance to look at it again today but I agree with you that the controls should have been created long ago. I was surprised to find that their count was zero. I will do a double-check though when I get chance (hopefully tomorrow) and let you know.

I will also look into the Custom Template, though my gut feeling is that this is a sort of workaround solution rather than an elegant solution. But when have we ever had the luxury of an elegant solution with new IDEs :-) ? Roll on the service packs I say ;-) .
0
 
LVL 4

Accepted Solution

by:
Noggy earned 0 total points
ID: 7146079
I've found the solution :-) ...well, adapted the code from a source elsewhere. FYI CoolAss, the Controls collection was actually there. I was being stupid in that I had forgotten that there was an invisible BoundColumn at the start of my table. D'oh!!


Anyhow, the reason ButtonColumn and HyperLinkColumn behave differently during ItemDataBound event processing is that their <td></td> elements dont contain text but rather an <input type="submit"> and a <a> element respectively. It seems that reading the Text property is safe, and returns , but assigning to the Text property replaces whatever is between the <td> and </td> tags. You can assign any valid HTML to Text at this stage and it will be rendered correctly. As Text is , assigning it to itself effectively clears the <td></td> element to nothing, as you found.

In the case of ButtonColumns, encoding must be applied not to TableCell.Text, but rather to the value attribute of the <input type=submit> element inside the TableCell. Similar for HyperLinkColumns, except this time the text inside the <a> and the href attribute of the <a>.

Here is the code that I arrived at from the code that I was given:

private void dgIssues_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e)
{
      WebMethod wm = new WebMethod();
      wm.DataGrid_ItemDataBound_HTMLEncode((DataGridItem) e.Item);
}//dgIssues_ItemDataBound

/// <summary>
/// Method that HTML Encodes an entire DataGrid.
/// It iterates through each cell in the TableRow, ensuring that all
/// the text being displayed is HTML Encoded, irrespective of whether
/// they are just plain text, buttons, hyperlinks, multiple controls etc..
/// <seealso cref="ms-help://MS.VSCC/MS.MSDNVS/cpref/html/frlrfSystemWebUIWebControlsDataGridClassItemDataBoundTopic.htm">DataGrid.ItemDataBound Event</seealso>
/// </summary>
/// <param name="item">
/// The DataGridItem that is currently being bound in the calling Web
/// Page's DataGrid.ItemDataBound Event.
/// </param>
/// <remarks>
/// This method should be called from the
/// <c>DataGrid_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e)</c>
/// event in the respective Web View Codebehind.
/// </remarks>
/// <example>
///            We want to HTMLEncode a complete DataGrid (all columns and all
///            rows that may/do contain characters that will require encoding
///            for display in HTML) called dgIssues.
///            Use the following code for the ItemDataBound Event:
///            <code>
///                  private void dgIssues_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e)
///                  {
///                        WebMethod wm = new WebMethod();
///                        wm.DataGrid_ItemDataBound_HTMLEncode((DataGridItem) e.Item);
///                  }//dgIssues_ItemDataBound
///            </code>
/// </example>
public void DataGrid_ItemDataBound_HTMLEncode(DataGridItem item)
{
      bool doHTMLEncode = false;
      switch (item.ItemType)
      {                              
            #region DataBound
                  //The following case statements are in ascending TableItemStyle order.
                  //See ms-help://MS.VSCC/MS.MSDNVS/cpref/html/frlrfsystemwebuiwebcontrolsdatagridclassitemstyletopic.htm for details.
            case ListItemType.Item:
            {
                  doHTMLEncode = false;
                  break;
            }//ListItemType.Item
            case ListItemType.AlternatingItem:
            {
                  doHTMLEncode = true;
                  break;
            }//ListItemType.AlternatingItem
            case ListItemType.SelectedItem:
            {
                  doHTMLEncode = true;
                  break;
            }//ListItemType.SelectedItem
            case ListItemType.EditItem:
            {
                  //These should not be prone to this as TextBoxes aren't.
                  doHTMLEncode = false;
                  break;
            }//ListItemType.EditItem
            #endregion DataBound
            #region Non-DataBound
                  //The remainder are the other ListItemTypes that are non-Data-bound.
            case ListItemType.Header:
            {
                  //We might have specified Headers like "<ID>".
                  doHTMLEncode = true;
                  break;
            }//ListItemType.Header
            case ListItemType.Footer:
            {
                  //Similarly for the Footer as with the Header.
                  doHTMLEncode = true;
                  break;
            }//ListItemType.Footer
            case ListItemType.Pager:
            {
                  //With just numbers or buttons, none is required.
                  //However, for buttons, this is not strictly true as you
                  //need to specify the text on the buttons. But the Property
                  //Builder for the DataGrid hints in its defaults that these
                  //need to be HTMLencoded anyway.
                  doHTMLEncode = false;
                  break;
            }//ListItemType.Pager
            case ListItemType.Separator:
            {
                  doHTMLEncode = false;
                  break;
            }//ListItemType.Separator
            #endregion Non-DataBound
            default:
            {
                  //This will never be executed as all ItemTypes are listed above.
                  break;
            }//default
      }//switch

      if (doHTMLEncode)
      {
            ///Encode the cells dependent on the type of content
            ///within (e.g. BoundColumn, Hyperlink), taking into account
            ///that there may be more than one (or even zero) control in
            ///each cell.
            TableCellCollection cells = (TableCellCollection)item.Cells;
            foreach (TableCell cell in cells)
            {
                  if (cell.Controls.Count != 0)
                  {
                        foreach (Control ctrl in cell.Controls)
                        {
                              if (ctrl is Button)
                              {
                                    Button btn = (Button) ctrl;
                                    btn.Text = HttpUtility.HtmlEncode(btn.Text);
                              }//if
                              else if (ctrl is HyperLink)
                              {
                                    HyperLink hyp = (HyperLink) ctrl;
                                    hyp.Text = HttpUtility.HtmlEncode(hyp.Text);
                                    hyp.NavigateUrl = HttpUtility.UrlEncode(hyp.Text);
                              }//else if
                              else if (ctrl is LinkButton)
                              {
                                    LinkButton lb = (LinkButton) ctrl;
                                    lb.Text = HttpUtility.HtmlEncode(lb.Text);
                              }//else if
                        }//foreach
                  }//if
                  else
                  {
                        //The cell is a BoundColumn.
                        cell.Text = HttpUtility.HtmlEncode(cell.Text);
                  }//else
            }//foreach
      }//if
}//DataGrid_ItemDataBound_HTMLEncode


This should help you out, Check. However, I hope that you don't mind when I say that I intend to give CoolAss some token points as he was closest to the ball.

Let me know what you both think.
0
 
LVL 96

Expert Comment

by:Bob Learned
ID: 9393205
No comment has been added lately, so it's time to clean up this TA.
I will leave a recommendation in the Cleanup topic area that this question is:

Answered by CoolAss

Please leave any comments here within the next seven days.

PLEASE DO NOT ACCEPT THIS COMMENT AS AN ANSWER!

TheLearnedOne
EE Cleanup Volunteer
0
 
LVL 4

Author Comment

by:Noggy
ID: 9402095
Well, CoolAss was the closest to the mark but I ended up answering the question myself anyway (as you will see from my code listing). But, yeah, give the points to CoolAss. I didn't want to accept CoolAss's comments as an answer as my "answer" was more conclusive.
0
 
LVL 4

Author Comment

by:Noggy
ID: 9442004
Thanks, SerCouWisMOD :-).
0

Featured Post

IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

Suggested Solutions

Today I had a very interesting conundrum that had to get solved quickly. Needless to say, it wasn't resolved quickly because when we needed it we were very rushed, but as soon as the conference call was over and I took a step back I saw the correct …
A long time ago (May 2011), I have written an article showing you how to create a DLL using Visual Studio 2005 to be hosted in SQL Server 2005. That was valid at that time and it is still valid if you are still using these versions. You can still re…
This demo shows you how to set up the containerized NetScaler CPX with NetScaler Management and Analytics System in a non-routable Mesos/Marathon environment for use with Micro-Services applications.
Polish reports in Access so they look terrific. Take yourself to another level. Equations, Back Color, Alternate Back Color. Write easy VBA Code. Tighten space to use less pages. Launch report from a menu, considering criteria only when it is filled…

760 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

20 Experts available now in Live!

Get 1:1 Help Now