Searcharoo 2007 (中等信任和 Office 2007 索引)
移除二进制序列化以解决中等信任问题;为 ASP.NET/C# 免费搜索引擎索引 OpenXML 文档格式
背景
本文接续之前的四篇 Searcharoo 示例
Searcharoo 1 介绍了构建一个简单的搜索引擎,该搜索引擎可以抓取文件系统。开发了一个基本的设计和对象模型来支持简单的单字搜索,结果在一个基础的查询/结果页面中显示。
Searcharoo 2 侧重于添加一个“爬虫”来查找要索引的数据,通过跟踪Web 链接(通过 HTTP 下载文件并解析 HTML)。还讨论了如何将多个搜索词的结果组合成一组“匹配”。
Searcharoo 3 为目录实现了“保存到磁盘”功能,以便在 IIS 应用程序重启后可以重新加载,而无需每次都生成。它还抓取了 FRAMESETs 并为索引器添加了停用词、常用词和词干提取。
Searcharoo 4 为非文本文件类型(例如 Word、PDF 和 PowerPoint)添加了 IFilter 支持,改进了 robots.txt 支持,添加了一个远程索引控制台应用程序,并进行了大量代码清理(重构!)。
版本 5 简介
本文比大多数文章要短,只涵盖两个主题
- 允许 Searcharoo 在 ASP.NET 应用程序仅限于中等信任的网站上运行。v4 中的远程索引控制台应用程序旨在解决此问题 - 但仅仅远程构建目录是不够的,因为在中等信任下无法反序列化文件。与其建议人们尝试更改服务器上的信任级别或进行自定义(这很困难!),不如更改了文件格式(改为 XML),使其可以在中等信任下工作。
- 扩展 v4 中引入的
Document
对象层次结构,以索引 Office 2007(OpenXML)文件类型。我最近从同事那里收到一个*.docx 文件,由于我暂时不打算升级到 Office 2007,因此研究如何在不安装应用程序/IFilter
的情况下索引/搜索该文件似乎是个好主意。
ASP.NET 有“信任问题”
当 Searcharoo v4 在中等信任下运行时,您会遇到以下错误之一:
如果 Search.aspx
找不到目录文件并触发 SearchSpider.aspx
,则会发生WebPermission 拒绝(在中等信任下默认不允许访问网站或 Web 服务)。
[SecurityException: Request for the permission of type 'System.Net.WebPermission, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed.] System.Security.CodeAccessSecurityEngine.Check(Object demand, StackCrawlMark& stackMark, Boolean isPermSet) +0 System.Security.CodeAccessPermission.Demand() +59 System.Net.HttpWebRequest..ctor(Uri uri, ServicePoint servicePoint) +166 System.Net.HttpRequestCreator.Create(Uri Uri) +26 System.Net.WebRequest.Create(Uri requestUri, Boolean useUriBase) +373 System.Net.WebRequest.Create(String requestUriString) +81 Searcharoo.Indexer.RobotsTxt..ctor(Uri startPageUri, String userAgent) +250 Searcharoo.Indexer.Spider.BuildCatalog(Uri startPageUri) +116
如果 Search.aspx
找到一个二进制序列化的目录文件并尝试反序列化它,则会发生SecurityPermission 拒绝(在中等信任下不允许二进制序列化)。
[SecurityException: Request for the permission of type 'System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed.] System.Runtime.Serialization.Formatters.Binary.ObjectReader.CheckSecurity(ParseRecord pr) +1644388 System.Runtime.Serialization.Formatters.Binary.ObjectReader.ParseObject(ParseRecord pr) +363 System.Runtime.Serialization.Formatters.Binary.ObjectReader.Parse(ParseRecord pr) +64 System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryObjectWithMapTyped record) +1050 System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryHeaderEnum binaryHeaderEnum) +62 System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run() +144 System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage) +183 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage) +190 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream) +12 Searcharoo.Common.Catalog.Load() +461
这两个错误的组合 - 无法创建新目录,也无法加载现有目录文件(即使它是在其他地方生成的)- 意味着 Searcharoo v4 在中等信任下无法工作。有两种选择可以解决这个问题:
- 使用自定义代码访问安全策略更新您的服务器,以允许 Searcharoo 代码执行这些功能。如果您的网站是共享托管,并且您需要说服 ISP 为您进行更改,这可能会非常困难。
- 修改 Searcharoo,使其至少不会出现其中一个错误。
我们将选择 #2,因为它更容易!在 v4 中,我们曾就二进制序列化为何是好主意,而 XML 序列化为何是坏主意进行了长时间的讨论:在本文中,我们将通过修复 XML 输出中的问题来扭转这一局面,以便我们可以使用 Indexer Console Application 远程构建它,然后将其上传到中等信任网站。XML 序列化数据即使在中等信任下也可以反序列化,因此可以加载和搜索。
关于选项 #2:XML 重制
原始(v4)XML 目录格式
早在 v4 中,XML 序列化的 Catalog 对象就被认为臃肿、效率低下且(如实现的那样)无法反序列化。它看起来像这样:

请记住,每个 Word
对象都包含一个 File
对象集合(Hashtable
),指示该 Word
出现在哪个/哪些 File
中。这在内存中是有效的,因为 Word._FileCollection
中的 File
对象是引用 - 每个索引文件只有一个 File
“对象”。
由此产生的 XML 问题在于,File
对象引用被“展平”(每次引用时都重复)。从上面的片段中可以看到,文档 https://:3359/content/Kilimanjaro.pdf 被表示了两次。对于每个 File
中的EVERY WORD,都会发生这种重复,在 Catalog
文件中产生大量冗余数据。
我们需要一种更简洁的方式来表示 Word
和 File
之间的关系:用数据库术语来说,就是一个“外键”。
新的(v5)XML 格式
这个“外键”将由一个新对象 CatalogWordFile
来表示,它将充当 Word
对象(我们不再序列化它们)的“代理”。Word
对象将继续作为 Catalog 的基础,但当我们通过 XML 序列化加载和保存它时,我们将使用属性来忽略 Word
,并将 File
和 CatalogWordFile
视为两个由“外键”连接的“数据库表”:FileId
。

现在 File
对象被序列化一次,并且它们的 FileId
是它们在序列化 XML 中的隐式顺序(当然,从零开始)。上面提到的内容 - https://:3359/content/Kilimanjaro.pdf - 在新的 XML 中仅出现一次,显示为 FileId=2
(如下)。

![]() |
在同一个 XML 文件中,各个 CatalogWordFile 对象仅引用 FileId ,从而使 XML 比使用 Word 对象时小得多。重复原始(v4)XML Catalog 示例,您可以看到上面框出的两个单词再次显示在这里,只有 FileId 而不是整个序列化的 File 对象。 |
请注意,显示的标记仍然包含一些完整的元素名称;在实际代码中,元素名称被覆盖以使用属性进一步减小 XML 文件大小: 开发过程中使用的测试数据在二进制序列化时创建了一个178 KB的文件。这相当于旧格式的1.1 MB XML 文件。 使用新的、改进的 XML 格式,文件缩小到194 KB;在应用 |
![]() |
XML 序列化幕后
所以这就是我们需要的 XML 格式 - 我们如何获得它?不幸的是,仅仅用 CatalogWordFile[]
替换 Word[]
并不是我们让它工作的全部;FileId
需要在 CatalogWordFile
和 File
数组之间保持“同步”,但我们并不知道 XmlSerializer 会以何种顺序访问属性(也不知道它们是否会被多次访问)。为了避免不必要地填充内部 CatalogWordFile
集合,我们在属性访问器中使用 pre/post 方法按需创建它。
两个属性声明如下(下方);PrepareForSerialization()
完成了将 Word
对象组成的 _Index
Hashtable “展平”为带有 FileId
的 CatalogWordFile
代理的工作,它在两个 get
访问器中都被调用,以确保它们返回“同步”集合。
![]() |
![]() |
PostDeserialization()
方法会等待两个 File
和 WordFile
set
访问器都被调用(因为我们需要两个集合才能通过我们的“外键”重建原始 _Index
),然后遍历数据并调用 Add()
方法,就像 Spider 在索引时构建 Catalog 一样。

如果您查看 Catalog.Load()
代码,您还会注意到 XmlSerialization 使用了 Kelvin
通用序列化助手(另一个 CodeProject 文章)。
Catalog c1 = Kelvin<Catalog>.FromXmlFile(xmlFileName);
最后一点:我们并没有移除二进制序列化功能,而是保留了这两种方法,通过新的 web.config
/app.config
设置(针对您的网站和 Indexer Console 应用程序)进行控制。
<appSettings>
<add key="Searcharoo_InMediumTrust" value="True" />
</appSettings>
如果设置为 True
,Catalog 将保存为 XML 文件;如果设置为 False
,则保存为 *.dat 文件。不要忘记更新其他 .config 文件设置以匹配您的环境 - 包括将在本文剩余部分讨论的 DownloadDocument
类中使用的 Searcharoo_VirtualRoot, Searcharoo_CatalogFilepath
和 Searcharoo_TempFilepath
。
关于信任与代码访问安全
- 了解 .NET Framework 2.0 中的代码访问安全有哪些新功能
- 代码访问安全
- 代码访问安全基础
- 如何:使用中等信任
- 安全实践:ASP.NET 2.0 安全实践概览
- 使用 ASP.NET 2.0 和 IIS 6.0 设计和部署安全 Web 应用
- 安全实践:ASP.NET 2.0 安全实践概览
Office 2007 文件格式
文章的其余部分讨论了索引新的 Office 2007 文件格式。
Microsoft Word Docx 文件“结构”
这篇关于 OpenXML 入门的博客讨论了如何使用Open XML 文件格式。它解释了 OpenXML 文档的基本结构:它们实际上是一系列相关的 XML(和其他)文件,'隐藏' 在一个单一的ZIP 文件中,该文件的扩展名为 Office 2007 文件扩展名,如docs, xlsx, pptx
等)。
Microsoft Word 2007 文件在 ZIP “内部”是这样的:
您可以阅读参考文献中关于格式的详细信息,但我们关心的关键文件是 document.xml
部分。为了搜索它,我们需要执行以下步骤:
- 从 Web 链接下载 OpenXML 文件/ZIP 存档
- 从 ZIP 存档中提取我们需要的文件
- 了解一些 XML 格式,以便我们可以提取我们想要索引的纯文本,并忽略所有格式和其他数据。
步骤 1:继承 Document 以共享下载代码
v4 文章介绍了 FilterDocument
如何需要下载文件以进行 IFilter
处理(而以前是将下载内容加载到 MemoryStream
中进行解析)。新的 Office 2007 类也需要相同的行为,因此 SaveDownloadedFile
方法被提升到一个它们都可以实现的基类。

步骤 2:解压缩
.NET 3.0 中的System.IO.Packaging
API 提供了访问 ZIP 存档的内置功能(有些人可能会说,这是专门为了促进 Office 2007/OpenXML 互操作性而设计的)。但是,为了保持 Searcharoo 的可访问性,我们暂时不升级到 3.0;幸运的是,.NET 2.0 中的 System.IO.Compression
命名空间包含了构建ZipFile 实现所需的构建块,该实现可以读取/写入 ZIP 文件(因此也可以读取/写入 OpenXML 文档)。
使用 ZipFile
访问数据流进行处理很简单:
using (ZipFile zip = ZipFile.Read(filename))
{
using (MemoryStream streamroot = new MemoryStream())
{
MemoryStream stream = new MemoryStream();
zip.Extract(@"word/document.xml", streamroot);
stream.Seek(0, SeekOrigin.Begin); // important!
// TODO: code here to process Xml from the stream
}
}
步骤 3:提取文本
事实证明,Word 2007 的 OpenXML 格式在处理格式和内容方面非常类似于 HTML:document.xml
中的所有文档结构和格式都包含在 XML 属性中,而相关的纯文本位于每个元素的 InnerXml 中。就我们而言,我们将假设这些就是我们想要索引的所有文本(需要进一步研究以确定是否包含页眉/页脚/表格/引用,并且需要更多工作来检测和索引其他嵌入的 Office 文档)。
DocxDocument 的 3 个简单步骤
新的 Docx 文件索引器继承了大部分功能,这些功能来自抽象的 Document
和 DownloadDocument
类。我们真正需要做的就是重写 GetResponse()
方法来提取文件内容,并设置 WordsOnly
属性,该属性用于生成 Catalog
。

这种模式可以轻松地应用于 PowerPoint 2007(.pptx
文件)和 Excel 2007(.xlsx
文件)- 请参阅 XlsxDocument
和 PptxDocument
代码,了解了获取这些文件类型中所有文本所需的额外工作(遍历工作表/幻灯片)。
最后,除非我们更新 DocumentFactory
以识别新的“处理”MIME 类型,以及哪个 MIME 类型/文件扩展名映射到哪个类,否则我们的新类将永远不会被实例化。
关于 Open XML Office 格式的更多信息
Erika 的博客是Office 2003 和 2007:MSDN 技术文章、操作方法内容和代码示例的绝佳来源。其他链接包括:- 介绍 Office(2007)Open XML 文件格式
- Office 开发者门户中的 XML
- 如何:操作 Office Open XML 格式文档
- 演练:Word 2007 XML 格式
- Office Open XML 格式:检索 Excel 2007 单元格值
- Office Open XML 格式:检索 PowerPoint 2007 幻灯片列表
总结
这些对 Searcharoo 的添加非常小,发布它们主要是为了帮助任何希望在中等信任下使用该代码的人。许多用户可能已安装 Office 2007(或服务器上相应的 IFilter
),并且可能甚至不需要额外的 Document
子类 - 如果是这种情况,只需从 DocumentFactory
中删除新的 case
语句,然后让现有的 FilterDocument
来指导 Indexer。