65.9K
CodeProject is changing. Read more.
Home

Problem with Response.Write - Changing Dynamic Content Without Corruption

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.45/5 (9 votes)

May 15, 2008

CPOL

2 min read

viewsIcon

54368

downloadIcon

165

Changing HTML content of an ASP.NET page using a new technique that handles Response.Write().

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:

GoodResult.PNG

Let us write the code:

protected override void Render(HtmlTextWriter writer) 
{
    // Create your HTMLTestWriter
   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);
    
    // Give the page a fake streamer to capture HTML contents.
    base.Render(oHtmlWriter); 
    oPageContents = oPageContents.Replace("$MSG$", "How R U ?");
    
    // Write data back into the original stream
    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:

BadResult.PNG

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.

Diagram1.png

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) 
    { 
        // Change content dynamically here.
        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) 
{ 
    // Create your string builder.
    System.Text.StringBuilder oPageContents = new System.Text.StringBuilder();
   
    // Create HTMLStreamer and put both writer & oPageContents into it.
    HTMLStreamer oSW = new HTMLStreamer(oPageContents, writer); 
   
    // Create HTMLTextWriter that contains the double-stream class "HTMLStreamer"
    System.Web.UI.HtmlTextWriter oHtmlWriter = new System.Web.UI.HtmlTextWriter(oSW);
   
    // Get HTML contents.
    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.

Problem with Response.Write - Changing Dynamic Content Without Corruption - CodeProject