65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (19投票s)

2011 年 10 月 31 日

CPOL

6分钟阅读

viewsIcon

51384

downloadIcon

2213

HttpResponse.Filter 对 ASP.NET 页面的输出进行后处理,以便在 HTML 文档发送给客户端之前对其进行修改,类似于 PHP 中的输出缓冲。本示例将页面上关键词的实例包装在一个 HTML 元素中,以便对其应用高亮样式。

引言

毫无疑问,您已经看过许多网页,其中关键词搜索结果会以黄色高亮显示关键词,使读者可以轻松找到关键词在上下文中出现的位置。当然,有许多方法可以完成此任务。

本文讨论

  • (大部分)未文档化的 HttpResponse.Filter 属性的实现
  • 实现一个简单的搜索框,以高亮显示页面上的单词或短语
  • 使用带有 MatchEvaluator 委托的 Regex.Replace

背景

本周,当我着手实现关键词高亮时,我考虑了几种可能的方法

  1. 使用 JavaScript 进行客户端 DOM 操作
  2. 对我拥有编程访问权限的文本进行搜索和替换
  3. 一个 ASP.NET HTTP 模块或 HTTP 处理程序,编译为独立程序集并安装在 Web.config
  4. 操纵输出流,类似于 PHP 中的输出缓冲

我决定采用最后一种方法,因为它有可能独立于页面代码运行(与 #2 不同),不需要处理器密集型的客户端脚本(与 #1 不同),并且不需要任何服务器端配置(与 #3 不同)。

示例网站由一个网页组成,该网页显示查尔斯·狄更斯《远大前程》中的文本。页面右上角浮动着一个搜索框,您可以在其中输入单词或短语。它还提供了一些选项,例如区分大小写搜索、全词搜索以及使用正则表达式而不是字面文本进行搜索。

Screen shot of Great Expectations without highlight

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

Screen shot of Great Expectations with highlighting

术语

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

Using the Code

Screen shot of Great Expectations with highlighting

在文章前面,我承诺用一行代码为页面添加高亮。以下是代码上下文

/// <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 的一些属性。大多数属性都应该是自解释的,例如 MatchCaseMatchWholeWordUseRegex

IsHtml5 属性将“针”的实例包装在 <mark> 元素中,这是它的预期用途。如果为 false,则改为使用一个类设置为“highlight”的 div。为了更好地控制,可以显式设置 OpenTagCloseTag 属性的值。为了获得终极控制,您可以订阅 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(一个为每个找到的匹配项调用的委托)来实现这一点。这非常适合这种用法,因为如果 MatchWholeWordstrue,则包围“针”的字符将按类型替换,并且匹配项的大小写不会更改(即,使用 String.Replace 会将所有匹配项的大小写替换为“针”的大小写)。

如果 UseRegexfalse,则“针”只会用 Regex.Escape 进行转义,而不是使用其他搜索和替换方法。

我最初担心使用带有 MatchEvaluatorRegex 进行替换会非常慢,但替换《远大前程》(略超过一兆字节)中的常用词在我的 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 日:修改标题以更好地描述主题性质
© . All rights reserved.