Click here to Skip to main content
15,867,838 members
Articles / Web Development / IIS
Article

Removing White Chars from ASP.NET Output using Response.Filter property

Rate me:
Please Sign up or sign in to vote.
3.23/5 (7 votes)
7 Feb 20053 min read 109.1K   630   30   21
This article shows how to remove unwanted white chars from each line, and some of the new line chars too. My code correctly handles scripts embedded in HTML pages.

Introduction

This article demonstrates how to remove unwanted white characters from ASP.NET output. I present how to use HttpResponse.Filter property to achieve this effect.

Background

During my work on an ASP.NET site, I realized that pages sent to the browser by the server contained many white characters at the beginning of each line. I browsed MSDN for something to fix this, and I found HttpResponse.Filter property. HttpResponse works in an easy way - all ASP.NET output goes through it. So my task looked easy - create my own Stream implementation and trim each written line in Write() function.

First version

First version was very simple:

C#
public class TrimStream : Stream
{
    private Stream stream;
    private StreamWriter streamWriter;
    
    public TrimStream(Stream stm)
    {
        stream = stm;
        streamWriter = new StreamWriter(stream, System.Text.Encoding.UTF8);
    }
    
    public override void Write(byte[] buffer, int offset, int count)
    {
        MemoryStream ms = new MemoryStream(buffer, offset, count, false);
        StreamReader sr = new StreamReader(ms, System.Text.Encoding.UTF8);
        bool bNewLine = false;
        string s;
        while ((s = sr.ReadLine()) != null)
        {
            s = s.Trim();
            if (s != "")
            {
                if (bNewLine)
                {
                    streamWriter.WriteLine();
                    bNewLine = false;
                }

                streamWriter.Write(s); 

                //new line chars should be emitted 
                //only when line doesn't end with '>'
                if (s[s.Length-1] != '>')
                    bNewLine = true;
            }
        }
        streamWriter.Flush();
    }
    
    /* other overrided Stream functions and properties goes here */
}

This code reads lines from the input stream, trims them, and writes to output stream only if the line is not empty. New line characters were written only when a line doesn't end with '>' char.

Unfortunately, I quickly realized that larger pages (> 30K or something like that) are written to output stream by multiple calls to Write() - on one of my pages, I found a text input box instead of a checkbox (my code replaced <input type="checkbox"> with <input type="\ncheckbox">). So I decided to completely rewrite my code, and manually handle each character in order to correctly handle this situation.

Current version

In the current version, I read all characters from the input stream, and manually analyze them line-by-line. Special handling is needed for the first and the last line:

  • on last line, I save white chars from the end of the line (they may be important if my "last line" ends somewhere in the middle of a real line);
  • on first line, I write saved white chars if they are really needed.

I also changed the logic that controls when a new line character is written - now it's emitted when neither previous line ends with '>' nor next line begins with '<'. I check this after trimming white chars and removing empty lines.

This approach is safe for scripts embedded in a page - their lines are only trimmed, and empty lines are removed. If you insert this script in an HTML page:

JavaScript
<script language="javascript">
<!--
function test()
{
    alert('test');
}
//-->
</script>

My code changes it as follows:

JavaScript
<script language="javascript"><!--
function test()
{
alert('test');
}
//--></script>

Code description

bNewLine and bLastCharGT variables are used for deciding when to write a new line character before the next line. In arBlanks array are stored white chars between calls to Write(), if any was found on end of last line. This array can be zero-sized too - in this case, last char in line was non-blank, and that doesn't end with '\n'. This is important information - in this case, a new line char shouldn't be emitted if in first line (on next call to Write are non-blank chars).

This code analyzes chars looking for '\n' char (it splits lines). When this char is found, you can find the following situations:

  • first line - if arBlanks is not null, its contents are written, and all white chars to the beginning of the current line should be written to the output stream too;
  • any line with non-blank chars - this code writes it (excluding white chars at the begin and end of it);
  • last line - if last char on this line is other than '\n', all white chars from end of this line should be saved to arBlanks array for next call to Write().
C#
public class TrimStream : Stream
{
    private Stream stream;
    private StreamWriter streamWriter;

    private Decoder dec;

    public TrimStream(Stream stm)
    {
        stream = stm;
        streamWriter = new StreamWriter(stream, System.Text.Encoding.UTF8);

        dec = Encoding.UTF8.GetDecoder();
    }

    /// <summary>
    /// Flag - write '\n' before next line
    /// </summary>
    private bool bNewLine = false;
    /// <summary>
    /// Flag - lash non-blank char in line was '>'
    /// </summary>
    private bool bLastCharGT = false;
    /// <summary>
    /// Array holding white chars from end of last line between Write() calls
    /// </summary>
    private char[] arBlanks = null;

    public override void Write(byte[] buffer, int offset, int count)
    {
        int nCharCnt = dec.GetCharCount(buffer, offset, count);
        char[] result = new char[nCharCnt];
        int nDecoded = dec.GetChars(buffer, offset, count, result, 0);
        
        if (nDecoded <= 0)
            return;

        int nFirstNonBlank = -1; //position of first non-black line char
        int nLastNonBlank = -1; //position of last non-black line char
        int nFirstLineChar = 0; //position of first line char (any)

        bool bFirstLine = true;

        for (int nPos=0; nPos<=nDecoded; ++nPos)
        {
            bool bLastLine = nPos == nDecoded;

            char c = (nPos < nDecoded) ? result[nPos] : '\n';
            if (c == '\n') //handle new line
            {
                //first line, and we have important 
                //white chars from previous Write() call
                if (bFirstLine && (arBlanks != null))
                {
                    //current line contains non-blank chars 
                    //- write white chars from previous call
                    if (nFirstNonBlank >=0)
                    {
                        if (arBlanks.Length > 0)
                            streamWriter.Write(arBlanks, 0, arBlanks.Length);

                        arBlanks = null;
                        nFirstNonBlank = 0;
                        bNewLine = false;
                    }
                }
                bFirstLine = false;

                //current line contains any non-white chars - write them
                if (nFirstNonBlank >= 0)
                {
                    if (bNewLine && (result[nFirstNonBlank] != '<'))
                    //write new line char ?
                        streamWriter.WriteLine();

                    //write current line (trimmed)
                    streamWriter.Write(result, nFirstNonBlank, 
                          nLastNonBlank - nFirstNonBlank + 1);

                    //setting variables...
                    if (!bLastLine)
                    {
                        nFirstNonBlank = -1;
                        nLastNonBlank = -1;
                        nFirstLineChar = nPos + 1;
                    }
                    bNewLine = !bLastCharGT;
                    bLastCharGT = false;
                }

                if (bLastLine)
                //last line - optionally remember white chars from its end
                {
                    if ((arBlanks == null) && (nFirstNonBlank < 0))
                    {
                        //empty line and we don't have any 
                        //white chars from previous call - nothing to do
                    }
                    else if (nLastNonBlank < nDecoded-1)
                    //there was white chars at end of this line
                    {
                        int nNumBlanks, nFirstBlank;
                        if (nLastNonBlank < 0)
                        {
                            nNumBlanks = nDecoded - nFirstLineChar;
                            nFirstBlank = nFirstLineChar;
                        }
                        else
                        {
                            nNumBlanks = nDecoded - nLastNonBlank - 1;
                            nFirstBlank = nLastNonBlank + 1;
                        }

                        if ((arBlanks == null) || (arBlanks.Length <= 0))
                        //create new array?
                        {
                            arBlanks = new char[nNumBlanks];
                            Array.Copy(result, nFirstBlank, 
                                       arBlanks, 0, nNumBlanks);
                        }
                        else //append at end of existsing array
                        {
                            char[] ar = new char[arBlanks.Length + nNumBlanks];
                            arBlanks.CopyTo(ar, 0);
                            Array.Copy(result, nFirstBlank, ar, 
                                       arBlanks.Length, nNumBlanks);
                            arBlanks = ar;
                        }
                    }
                    else
                    //line without white-chars at end 
                    //- mark this using zero-sized array
                    {
                        arBlanks = new char[0];
                    }

                    //set variable...
                    bNewLine = false;
                }
            }
            else if (!Char.IsWhiteSpace(c)) //handle non-white chars
            {
                if (nFirstNonBlank < 0)
                    nFirstNonBlank = nPos;
                nLastNonBlank = nPos;
                bLastCharGT = (c == '>');
            }
        }

        streamWriter.Flush();
    }

    /* other overrided Stream functions and properties goes here */
}

Using the code

An instance of this class must be created on each request and assigned to the current Request.Filter property. You can do it in almost any event generated by the HttpApplication class. But if you have pages that renders something different than html code (text/html MIME type), you should take care of this. In this case, you should test the current MIME type and create (or not) this filter in HttpApplication.PostRequestHandlerExecute event handler:

C#
private void Global_PostRequestHandlerExecute(object sender, System.EventArgs e)
{
    if (Response.ContentType == "text/html")
        Response.Filter = new TrimStream(Response.Filter);
}

Points of Interest

HttpResponse.Filter property gives us an entry point to connect our output filters to an ASP.NET output stream. This property can be used to connect any combination of filters, e.g., my filter and compression filter:

C#
Response.Filter = new TrimStream(new CompressStream(Response.Filter));

Also, this is an interesting alternative to IHttpModule-based filters.

History

  • 2/7/2005 - First version.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 2 Pin
Member 860510229-Apr-12 7:19
Member 860510229-Apr-12 7:19 
QuestionMultiple write problem Pin
skrblyk2-Apr-12 3:31
skrblyk2-Apr-12 3:31 
QuestionThis trick will prevent ASP.net AJAX from function properly Pin
Vinix Wu1-Apr-09 23:20
Vinix Wu1-Apr-09 23:20 
AnswerRe: This trick will prevent ASP.net AJAX from function properly Pin
Magomed Abdurakhmanov23-May-09 9:26
Magomed Abdurakhmanov23-May-09 9:26 
GeneralDiamond like � Character Pin
qurban ali26-Aug-08 2:59
qurban ali26-Aug-08 2:59 
GeneralDiamond like � Character Pin
qurban ali26-Aug-08 2:57
qurban ali26-Aug-08 2:57 
Questionany way around the multiple Write problem? Pin
travislaborde24-Jan-06 16:03
travislaborde24-Jan-06 16:03 
AnswerRe: any way around the multiple Write problem? Pin
Daniel Fruzynski24-Jan-06 21:08
Daniel Fruzynski24-Jan-06 21:08 
GeneralRe: any way around the multiple Write problem? Pin
travislaborde25-Jan-06 0:52
travislaborde25-Jan-06 0:52 
GeneralRe: any way around the multiple Write problem? Pin
Daniel Fruzynski25-Jan-06 3:36
Daniel Fruzynski25-Jan-06 3:36 
AnswerRe: any way around the multiple Write problem? Pin
travislaborde26-Jan-06 2:46
travislaborde26-Jan-06 2:46 
Thanks, Daniel. That's exactly what I've done.

It seems to be working great so far.

Travis


Travis
QuestionWhat about Performance? Pin
AgeKay7-Feb-05 11:04
AgeKay7-Feb-05 11:04 
AnswerRe: What about Performance? Pin
Daniel Fruzynski8-Feb-05 8:56
Daniel Fruzynski8-Feb-05 8:56 
GeneralRe: What about Performance? Pin
DanielHac8-Feb-05 14:27
DanielHac8-Feb-05 14:27 
GeneralRe: What about Performance? Pin
User 2573288-Feb-05 23:31
User 2573288-Feb-05 23:31 
GeneralRe: What about Performance? Pin
DanielHac9-Feb-05 1:19
DanielHac9-Feb-05 1:19 
GeneralRe: What about Performance? Pin
User 2573289-Feb-05 7:59
User 2573289-Feb-05 7:59 
GeneralRe: What about Performance? Pin
DanielHac9-Feb-05 8:10
DanielHac9-Feb-05 8:10 
GeneralRe: What about Performance? Pin
14-Feb-05 20:38
suss14-Feb-05 20:38 
GeneralRe: What about Performance? Pin
Member 24214814-Feb-05 20:41
Member 24214814-Feb-05 20:41 
GeneralRe: What about Performance? Pin
Jeffster14-Sep-07 10:28
Jeffster14-Sep-07 10:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.