Improve company productivity with a Business Account.Sign Up

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 3759
  • Last Modified:

ASP.NET: HTMLEncoding a DataGrid

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
Noggy
Asked:
Noggy
1 Solution
 
CoolAssCommented:
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
 
NoggyAuthor Commented:
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
 
CoolAssCommented:
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
Free Tool: Port Scanner

Check which ports are open to the outside world. Helps make sure that your firewall rules are working as intended.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

 
CheckCommented:
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
 
NoggyAuthor Commented:
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
 
NoggyAuthor Commented:
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
 
Bob LearnedCommented:
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
 
NoggyAuthor Commented:
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
 
NoggyAuthor Commented:
Thanks, SerCouWisMOD :-).
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Free Tool: ZipGrep

ZipGrep is a utility that can list and search zip (.war, .ear, .jar, etc) archives for text patterns, without the need to extract the archive's contents.

One of a set of tools we're offering as a way to say thank you for being a part of the community.

Tackle projects and never again get stuck behind a technical roadblock.
Join Now