只需一行代码实现关键词高亮:ASP.NET 中 HttpResponse.Filter 的应用,修改输出流






4.87/5 (19投票s)
HttpResponse.Filter 对 ASP.NET 页面的输出进行后处理,以便在 HTML 文档发送给客户端之前对其进行修改,类似于 PHP 中的输出缓冲。本示例将页面上关键词的实例包装在一个 HTML 元素中,以便对其应用高亮样式。
引言
毫无疑问,您已经看过许多网页,其中关键词搜索结果会以黄色高亮显示关键词,使读者可以轻松找到关键词在上下文中出现的位置。当然,有许多方法可以完成此任务。
本文讨论
- (大部分)未文档化的
HttpResponse.Filter
属性的实现 - 实现一个简单的搜索框,以高亮显示页面上的单词或短语
- 使用带有
MatchEvaluator
委托的Regex.Replace
背景
本周,当我着手实现关键词高亮时,我考虑了几种可能的方法
- 使用 JavaScript 进行客户端 DOM 操作
- 对我拥有编程访问权限的文本进行搜索和替换
- 一个 ASP.NET HTTP 模块或 HTTP 处理程序,编译为独立程序集并安装在 Web.config 中
- 操纵输出流,类似于 PHP 中的输出缓冲
我决定采用最后一种方法,因为它有可能独立于页面代码运行(与 #2 不同),不需要处理器密集型的客户端脚本(与 #1 不同),并且不需要任何服务器端配置(与 #3 不同)。
示例网站由一个网页组成,该网页显示查尔斯·狄更斯《远大前程》中的文本。页面右上角浮动着一个搜索框,您可以在其中输入单词或短语。它还提供了一些选项,例如区分大小写搜索、全词搜索以及使用正则表达式而不是字面文本进行搜索。

当在搜索框中输入单词或短语并单击按钮时,页面会再次显示,并在整个文档中高亮显示搜索词。

术语
为了清晰起见,我将搜索词或关键词称为“针”。同样,我将正在搜索的文本称为“干草堆”。此命名约定也贯穿整个代码以保持一致性。
Using the Code

在文章前面,我承诺用一行代码为页面添加高亮。以下是代码上下文
/// <summary>
/// Handles the Load event of the Page control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.
/// </param>
protected void Page_Load(object sender, EventArgs e)
{
// Add some content from a resource.
Content.Text = Properties.Resources.Great_Expectations__by_Charles_Dickens;
if(IsPostBack)
{
// Implement a highlighter with one line of code:
Response.Filter = new HighlightFilter(Response, Needle.Text) // The magic line.
{
IsHtml5 = false,
MatchCase = MatchCase.Checked,
MatchWholeWords = MatchWholeWords.Checked,
UseRegex = UseRegularExpressions.Checked
};
// Don't try to highlight the search box.
Needle.Text = string.Empty;
}
}
如您所见,当 Web 表单回发时,“针”从 Needle.Text
中检索。在代码隐藏中,我们构造了一个 HighlightFilter
,将 HttpResponse
对象和“针”传递给它。
我还使用对象初始化器设置了 HighlightFilter
的一些属性。大多数属性都应该是自解释的,例如 MatchCase
、MatchWholeWord
和 UseRegex
。
IsHtml5
属性将“针”的实例包装在 <mark>
元素中,这是它的预期用途。如果为 false
,则改为使用一个类设置为“highlight
”的 div
。为了更好地控制,可以显式设置 OpenTag
和 CloseTag
属性的值。为了获得终极控制,您可以订阅 Highlighting
事件并使用提供的 Needle
修改提供的 Haystack
,甚至完全继承 HighlightFilter
。
当然,这种后处理的实用性不应局限于高亮。使用 Filter
类,可以订阅 Filtering
事件以修改输出流,或者继承 Filter
并重写受保护的 OnFilter
方法。有许多应用,包括
- 混淆
- 精简
- 更改密封类的输出
- 翻译(例如 RSS -> HTML)
- 插入通用代码(例如反向母版页)
如果您发现其他用途,请通过评论分享。
工作原理
我需要以某种方式拦截输出流,即 Page.Response.OutputStream
。
一番搜索后,我找到了 HttpResponse
类的 Filter
属性。该属性的文档留下了很多想象空间。该属性被分配一个过滤写入的 Stream
,示例中提到了一个神奇的(即未文档化的)UpperCaseFilterStream
,它将属性本身作为参数传递给构造函数,然后,瞧!嗯……(如果我费心找到并解压 Samples.AspNet.CS.Controls
,也许我就能解决这个问题了。)
我创建了 Filter
类,它将 HttpResponse
对象作为构造函数的参数。该类本身继承自 Stream
,但 abstract
类的实现只调用 HttpResponse
对象的 OutputStream
流的方法和属性,除了 Write(byte[] buffer, int offset, int count)
。重写的 Write
方法使用响应的 ContentEncoding
将缓冲区解码为 string
,应用过滤器,然后重新编码并将缓冲区写入 OutputStream
。
Filter
类本身不做任何有用的事情,但它的潜力是无限的。要使其过滤某些内容,需要继承它并重写 OnFilter
,或者实例化它并订阅 Filtering
事件,该事件会传递一个包含要操作的缓冲字符串的 FilterEventArgs
对象。
例如,为了实现“针”高亮,HighlightFilter
继承自 Filter
,重写了 OnFilter
并添加了一些属性和 Highlighting
事件。
新的 OnFilter
方法使用 Regex.Replace
替换干草堆中“针”的实例。它通过调用接受 MatchEvaluator
(一个为每个找到的匹配项调用的委托)来实现这一点。这非常适合这种用法,因为如果 MatchWholeWords
为 true
,则包围“针”的字符将按类型替换,并且匹配项的大小写不会更改(即,使用 String.Replace
会将所有匹配项的大小写替换为“针”的大小写)。
如果 UseRegex
为 false
,则“针”只会用 Regex.Escape
进行转义,而不是使用其他搜索和替换方法。
我最初担心使用带有 MatchEvaluator
的 Regex
进行替换会非常慢,但替换《远大前程》(略超过一兆字节)中的常用词在我的 Core i7-2600K 上只需几毫秒,希望在典型的网络服务器上不会慢太多。有趣的是,启用“全词匹配”会将其增加到几秒。
关注点
在我的第一次尝试中,我从 MemoryStream
派生了一个新类并将其分配给 Filter
属性。我重写了 Write
方法并通过将关键词实例包装在新元素中进行操作,该元素可以分配 CSS 样式。
检查 stream
的内容表明它运行得很好,并且该类调用 base.Write
来完成任务,但这导致客户端收到的字节数为零。示例应用程序建议可能需要单独写入字节。相反,我使用我的类来包装输出 stream
。
致谢
感谢 古腾堡计划 免费分发《远大前程》以及其他 36,000 多部作品;当然还有 查尔斯·狄更斯 (1812-1870) 本人。
历史
- 2011 年 10 月 31 日:版本 1.0.0.x
- 2013 年 1 月 3 日:修改标题以更好地描述主题性质