Introduction
When you need to dynamically control the HTML that is generated from your ASP.NET page or control, a popular technique to do that is to override the render function: protected override void Render(HtmlTextWriter writer)
. It is a function where you can get great power over your control. The function simply sends an HtmlTextWriter
, which is a stream where you can write anything and it will appear on the client screen, as we can see in the code below.
protected override void Render(HtmlTextWriter writer)
{
writer.Write("SayAnthing");
}
The Problem
Now, let us change the page data instead of replacing it. The attached sample contains two ASPX pages, both with the same HTML structure as follows:
<table> <tr> <td>
Hello ( <%Response.Write(@"Mr. XYZ"); %> $MSG$
</td> </tr> </table>
All we need to do is replace $MSG$ with a message "How R U?" by capturing the rendering event. The required result should be as in the following figure:
Let us write the code:
protected override void Render(HtmlTextWriter writer)
{
System.Text.StringBuilder oPageContents = new System.Text.StringBuilder();
System.IO.StringWriter oSW = new System.IO.StringWriter(oPageContents);
System.Web.UI.HtmlTextWriter oHtmlWriter = new System.Web.UI.HtmlTextWriter(oSW);
base.Render(oHtmlWriter);
oPageContents = oPageContents.Replace("$MSG$", "How R U ?");
System.IO.StringReader oSR = new System.IO.StringReader(oPageContents.ToString ());
writer.Write(oSR.ReadToEnd ());
}
Nothing strange about this code, and it can be found all over the internet. The idea behind it is that you replace the given HtmlTextWriter
that you can only write into, with another one that streams data into a StringBuilder
object, oPageContents
. Once the HTML is in the oPageContents
StringBuilder
, you can read and modify it easily. The next step is to write your contents in the actual HtmlTextWriter
that is passed as a parameter in the Render
function. It is pretty straightforward :) Wait, the result is not what you want??!!
The strange thing is that you don't get the desired result; instead, you get the following result:
I spent a couple of days searching why this happened. And finally, I found the answer. Response.Write()
does not send its contents through base.Render
that is used to render the control. Response.Write()
, in fact, the HtmlTextWriter
stream sends the data eventually to the Response
object.
So what happens here is that while base.Render()
is working, when Response.Write()
takes place, it sends data directly out, while the other data are all stored in our local StringBuilder
, and then we inject them in to the writer, but then it is too late, as the order of streaming output now has been corrupted.
Solution - HTML Streamer
HTMLStreamer
solves this issue by acting as a double streamer. It streams data to a StringBuilder
object as in our previous sample. At the same time, it streams data to a writer parameter of type HtmlTextWriter
. The data is submitted synchronized to the Response
object, while another copy is saved in the StringBuilder
object.
public class HTMLStreamer: System.IO.StringWriter
{
protected System.Web.UI.HtmlTextWriter mHtmlTextWriter;
public HTMLStreamer(System.Text.StringBuilder oStringBuilder,
System.Web.UI.HtmlTextWriter innerWriter)
: base(oStringBuilder)
{
mHtmlTextWriter = innerWriter;
}
public override void Write(string value)
{
if (value.IndexOf("$MSG$")!=-1)
{
value = value.Replace("$MSG$","How R U ?");
}
mHtmlTextWriter.Write(value); base.Write(value);
}
public override void Write(char[] buffer, int index, int count)
{
mHtmlTextWriter.Write(buffer, index, count);
base.Write(buffer, index, count);
}
public override void Write(char value)
{
mHtmlTextWriter.Write(value);
base.Write(value);
}
}
Now back to the Render()
function; it should be handled as follows:
protected override void Render(HtmlTextWriter writer)
{
System.Text.StringBuilder oPageContents = new System.Text.StringBuilder();
HTMLStreamer oSW = new HTMLStreamer(oPageContents, writer);
System.Web.UI.HtmlTextWriter oHtmlWriter = new System.Web.UI.HtmlTextWriter(oSW);
base.Render(oHtmlWriter);
}
Enhancements
A delegate is called in all Write()
functions of HTMLStreamer
so that it can be used by the page to change the contents without the need to write into the HTMLStreamer
itself and so it can be used as a separate DLL. If you have any idea on how to capture Response.Write()
, please let me know.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.