HttpRequest.Filter 属性为我们提供了一个过滤和转换器,可以在进行页面处理之前对 Http 输入流数据进行更改。如果指定了该属性,所有 Http 的请求数据都会通过你所指定的过滤器,这意味着可以在数据到达页面之前进行筛选或转换,并且对于所有的页面,只需做一个统一的处理,而不必每个页面都去增加代码。常见的应用有非法字符过滤、繁简转换等。
其实所谓的过滤器,说白了就是一个字符流,而过滤或转换,就是对字符流中的数据进行相应的修改。所以要实现自己的过滤器,必须先继承 Stream 类,具体的实现可以参见 MSDN à System.Web.HttpRequest à Filter 属性的样例。
这里需要指出 MSDN 样例的 Read() 方法,数据的过滤及转换必须在该方法中实现,但 MSDN 的样例有误导读者之嫌。MSDN 的样例代码如下:
- public override int Read(byte[] buffer, int offset, int count)
- {
- int c = _sink.Read(buffer, offset, count);
- for (int i = 0; i < count; i++)
- {
- if (buffer[offset+i] >= 'a' && buffer[offset+i] <= 'z')
- buffer[offset+i] -= ('a'-'A');
- }
- return c;
- }
这个实现存在一个很大的问题,对于 Http 请求,发回到服务器的数据(POST方式)类似于:
__VIEWSTATE=%2FwEPDwUKLTc0NjAyOTQ3NQ9kFgICAw9kFgICAw8QDxYGHg1EYXRhVGV4dEZpZWxkBQV2YWx1ZR4ORGF0YVZhbHVlRmllbGQFA2tleR4LXyFEYXRhQm91bmRnZBAVBAFhAWIBYwFkFQQBMQEyATMBNBQrAwRnZ2dnZGRkIDCyaL%2FY%2FW7ReT14C10c%2FAyHzzA%3D&txtInput=ddssaa&ddlXml=1&__EVENTVALIDATION=%2FwEWBgLpsvzICwKT8abSBwKErvPrCQKFrvPrCQKGrvPrCQKHrvPrCcf8Jy04aj7zl8ulALFDIJBFFLcM
可以看出这些数据由多组名值对组成,通过 ’&’ 分隔,我使用红色将名称标识了出来,等号后的编码字符串为名称对应的值。在通常的代码中,我们可以使用Request.Form[名称] 获取到相应的值(已解码),其实页面控件中的 ID 也会在这里表示为名称。这样一来,按照 MSDN 的样例,必然会将名称替换为其它的值,到后期页面代码处理时,就会因控件 ID 与请求数据中的名称不一致而报错。
在实际应用中,一般需要转换或过滤的都是控件中的值,所以只要提取控件的值进行修改就可以了,最简单的方法是用正则表达式,一个简单的示例:
- public override int Read(byte[] buffer, int offset, int count)
- {
- int len = _sink.Read(buffer, offset, count);
- if (len == 0)
- {
- Array.Clear(buffer, 0, count);
- return len;
- }
- System.Text.Encoding curEncoding = HttpContext.Current.Request.ContentEncoding;
- string strBuff = curEncoding.GetString(buffer); //__VIEWSTATE 和 __EVENTVALIDATION 由 ASP.NET 使用,它们分别置于头部和尾部,所以不用考虑。
- System.Text.RegularExpressions.Regex re = new System.Text.RegularExpressions.Regex(@"&/w+=([/w@/-.*+]+)&");
- strBuff = re.Replace(strBuff, new System.Text.RegularExpressions.MatchEvaluator(ReplaceMatch));
- Array.Clear(buffer, 0, count);
- byte[] newBuff = curEncoding.GetBytes(strBuff);
- newBuff.CopyTo(buffer, 0);
- return len;
- }
注意返回值,表示读取到的字符串长度,这个值不能修改,并且过滤或转换后字符数据流的长度必须和原始数据一致(注意:是编码后的字符长度,如’<’,编码后为’%3C’,是三个字长,所以不能替换为’A’),否则会报错。
用于实际替换的方法(上面程序 re.Replace 中所使用的委托)
- private string ReplaceMatch(System.Text.RegularExpressions.Match match)
- {
- string prefixStr = match.ToString().Split('=')[0];
- string subMatchStr = HttpContext.Current.Server.UrlDecode(match.Groups[1].ToString());
- System.Text.RegularExpressions.Regex re = new System.Text.RegularExpressions.Regex(@"string");
- subMatchStr = HttpContext.Current.Server.UrlEncode(re.Replace(subMatchStr, "ABCDEF"));//注意:替换前后的字符数编码后必须保持一致
- return prefixStr + "=" + subMatchStr + "&";
- }
过滤器的使用很简单,在 Global.asax 中,增加
- public void Application_BeginRequest(object sender, EventArgs e)
- {
- Request.Filter = new inputFilter(Request.Filter);
- }
inputFilter 是自定义的过滤器。
BTW,如果仅是对输入流进行检测,若发现非法字符,则转向错误页面,只需在Application_BeginRequest 事件中检测 Request.Form 集合中的值就可以了,没必要用过滤器这么麻烦。