使用 Response.Filter 属性从 ASP.NET 输出中移除空白字符






3.23/5 (7投票s)
2005 年 2 月 7 日
3分钟阅读

112445

630
本文展示了如何移除每行中不必要的空白字符,以及一些换行符。我的代码可以正确处理嵌入在 HTML 页面中的脚本。
引言
本文演示了如何从 ASP.NET 输出中移除不必要的空白字符。我将介绍如何使用 HttpResponse.Filter
属性来实现这一效果。
背景
在处理一个 ASP.NET 站点时,我发现服务器发送到浏览器的页面在每行开头都包含很多空白字符。我查阅了 MSDN 来寻找解决方法,并找到了 HttpResponse.Filter
属性。HttpResponse
的工作方式很简单——所有 ASP.NET 输出都通过它。因此,我的任务看起来很简单——创建自己的 Stream 实现,并在 Write()
函数中修剪每一行。
第一版
第一个版本非常简单
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 */
}
此代码从输入流中读取行,修剪它们,然后仅当行不为空时才写入输出流。仅当行不以“>”字符结尾时才写入新行字符。
不幸的是,我很快意识到较大的页面(约 30K 或更多)是通过多次调用 Write()
来写入输出流的——在我的一页中,我发现一个文本输入框而不是一个复选框(我的代码将 <input type="checkbox">
替换为 <input type="\ncheckbox">
)。因此,我决定完全重写我的代码,并手动处理每个字符,以便正确处理这种情况。
当前版本
在当前版本中,我读取输入流中的所有字符,并逐行手动分析它们。第一行和最后一行需要特殊处理
- 在最后一行,我保存行末的空白字符(如果我的“最后一行”结束在实际行的中间,这些字符可能很重要);
- 在第一行,如果确实需要,则写入保存的空白字符。
我还更改了控制何时写入新行字符的逻辑——现在,当上一行不以“>”结尾且下一行不以“<”开头时,就会发出该字符。我在修剪空白字符并删除空行后进行检查。
这种方法对嵌入在页面中的脚本是安全的——它们的行仅被修剪,并且空行被删除。如果你将此脚本插入 HTML 页面
<script language="javascript">
<!--
function test()
{
alert('test');
}
//-->
</script>
我的代码将其更改为如下所示:
<script language="javascript"><!--
function test()
{
alert('test');
}
//--></script>
代码描述
bNewLine
和 bLastCharGT
变量用于决定何时在新行之前写入新行字符。在 arBlanks
数组中存储了 Write()
调用之间的空白字符(如果前一行末尾有)。此数组也可以是零大小的——在这种情况下,行中的最后一个字符是非空白字符,并且不以“\n”结尾。这是重要信息——在这种情况下,如果不先写新行字符(在下一次调用 Write
时有非空白字符),则不应发出新行字符(在第一行)。
此代码分析字符,寻找“\n”字符(它会分割行)。找到此字符后,您可以找到以下情况:
- 第一行——如果
arBlanks
不是 null,则写入其内容,并且当前行开头的所有空白字符也应写入输出流; - 带有非空白字符的任何行——此代码会写入它(不包括其开头和结尾的空白字符);
- 最后一行——如果此行中的最后一个字符不是“\n”,则此行末尾的所有空白字符都应保存到
arBlanks
数组中,供下次调用Write()
使用。
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 */
}
使用代码
必须为每个请求创建一个此类实例,并将其分配给当前的 Request.Filter
属性。您几乎可以在 HttpApplication
类生成的任何事件中执行此操作。但是,如果您有呈现不同于 HTML 代码(text/html
MIME 类型)的内容的页面,则应注意这一点。在这种情况下,您应该测试当前 MIME 类型,并在 HttpApplication.PostRequestHandlerExecute
事件处理程序中创建(或不创建)此过滤器。
private void Global_PostRequestHandlerExecute(object sender, System.EventArgs e)
{
if (Response.ContentType == "text/html")
Response.Filter = new TrimStream(Response.Filter);
}
关注点
HttpResponse.Filter
属性为我们提供了一个入口点,可以将我们的输出过滤器连接到 ASP.NET 输出流。此属性可用于连接任何过滤器组合,例如,我的过滤器和压缩过滤器。
Response.Filter = new TrimStream(new CompressStream(Response.Filter));
此外,这是 IHttpModule
类过滤器的一个有趣的替代方案。
历史
- 2005/2/7 - 第一个版本。