Office 服务

将服务器端的 Word 文档合入 SharePoint 2010

Ankush Bhatia

下载代码示例

业务应用程序开发人员必须经常创建解决方案,以便让所在组织的日常活动实现自动化。这些活动通常涉及处理和操作多个文档中的数据,例如从多个源文档中提取和合并数据;将数据合并到电子邮件中;在文档中搜索和替换内容;在工作簿中重新计算数据;从演示文稿中提取图像等等,诸如此类,不胜枚举。

Microsoft Office 提供了一个功能丰富的 API,开发人员可以使用它来自动化上述各种重复性的任务,从而使这些任务变得更简单。这些解决方案普通桌面用户用起来很顺利,因此开发人员又将它们提升到了一个新的高度:将这些解决方案部署到服务器。这样就形成一个中心点,集中为多个用户解决所有此类重复性工作,而无需人为干预。

将用于完成重复性 Office 任务的解决方案从桌面移植到服务器看起来很简单,实际上并不那么容易。

Microsoft 的 Office 应用程序套件旨在用于桌面计算机环境,即用户登录到一台计算机并坐在它前面。因为安全、性能和可靠性等原因,Office 应用程序不是用于服务器端方案的最佳工具。服务器环境中的 Office 应用程序可能需要人工干预,这对于服务器端解决方案来说并不理想。因此 Microsoft 建议避免使用此类解决方案,如 Microsoft 支持文章“Considerations for server-side Automation of Office”中所述。

然而,从 Office 2007 开始,Office 自动化故事发生了巨大变化。在 Office 2007 中,Microsoft 为要在服务器上开发基于 Office 解决方案的开发人员推出了 Office OpenXML 和 Excel Services。

对于 Office 2010 和 SharePoint 2010,Microsoft 提供了一组名为“应用程序服务”的新组件。这为开发人员提供了用于 Office 自动化解决方案的丰富工具。应用程序服务包括 Excel Services、Word Automation Services、InfoPath Forms Services、PerformancePoint Services 和 Visio Services。您可以在 msdn.microsoft.com/library/ee559367(v=office.14) 了解有关这些服务的详细信息。

在本文中,我们将向您介绍如何使用 Office OpenXML、Word Automation Services 和 SharePoint 建立一个简单的应用程序,将各个独立的状态报告合入一个文档。

状态报告工作流

假设您是一家服务公司的开发人员,公司里的许多项目由不同的团队管理。每周,每个项目经理都会使用一个通用的模板创建周状态报告,并上载到内部 SharePoint 存储库。现在,您的团队经理想要一个包含所有这些周状态报告的合并报告,并且您被选中执行这个要求。

不过,您是幸运的。正如前文所述,现在您的工作变得轻松多了,因为您可以使用 OpenXML 和 Word Automation Services 完成这个要求,所需的工作量大为减少。有了这些技术,您能够开发出前所未有的强大、稳定的解决方案。

让我们先看看这个解决方案。图 1 显示了建议的工作流。在流程的开始,各位项目经理填写状态报告并上载到服务器的 SharePoint 中。然后,团队经理就可以开始合并所有存储在服务器上的报告并生成一个合并的报告。

图 1 生成状态报告的工作流

构建模板

要实现这个解决方案,首先要为所有项目经理提供一个通用模板,用于填写周状态报告。当他们填写完数据后,会把报告上载到 SharePoint 存储库中。这样,星期一的早上,团队经理就能够登录到 SharePoint 网站,启动用于执行以下任务的逻辑:

  1. 阅读所有的状态报告文档。
  2. 将它们合并为一个报告。
  3. 将报告保存到存储库中以便用户访问。

状态报告模板如图 2 中所示(让我们称它为 WeeklyStatusReport.dotx)。正如您所看到的,模板中包含以下字段:标题、日期、项目经理姓名、里程碑和相关数据,还有用于输入有关工作成就、未来计划和问题的文本字段。在本例中,为了简单起见,我们使用了文本字段和日期选取器控件,但您也可以轻松使用下拉列表、复选框或其他各种控件来简化数据输入。

图 2 周状态报告模板

文档库

下一步就是创建自定义文档库,用于存储基于此模板生成的周状态报告。

在 SharePoint 导航窗格中,单击“库”,然后单击“创建”创建一个新库。在“创建”对话框中,按“库”进行筛选,选择“文档库”并输入库的名称(我们使用了“WSR Library”)。单击“创建”。

现在,您需要为新库创建内容类型。依次单击“网站操作”、“网站设置”,在“库”部分,单击“网站内容类型”。单击“创建”,然后输入内容类型的名称(我们使用了“Weekly Status Report”)。

在“从以下列表中选择父内容类型”列表中,选择“文档内容类型”。在“父内容类型”列表中,选择“文档”并单击“确定”。

在“设置”下面,选择“高级设置”,然后选中“上载新文档模板”单选按钮并单击“浏览”。找到报告模板 (WeeklyStatusReport.dotx) 并将其上载到库中。

接下来,转至“WSR Library”并选择“库设置”。在“常规设置”下,选择“高级设置”。为“是否允许管理内容类型”选择“是”,然后单击“确定”。

您会在库设置页面中看到“内容类型”的列表。选择“从现有网站内容类型添加”链接。在可用的网站内容类型列表中选择先前创建的内容类型。在这个示例中,选择“Weekly Status Report”。单击“添加”,然后单击“确定”。

再次从内容类型列表中单击“文档”并选择“删除此内容类型”。在警告消息框中选择“确定”。

现在当您在“WSR Library”中选择“新文档”时,应当可以看到您的内容类型了,如图 3 中所示。


图 3 选择自定义内容类型

这时,您可以继续并向文档库添加一些状态报告。

创建 Web 部件

接下来,您需要让团队经理能够启动合并逻辑。您可以通过文档库默认视图底部的按钮来执行此操作。

这涉及两个步骤。首先,使用 Visual Studio 2010 创建一个可视化 Web 部件。接下来,使用 SharePoint Designer 2010 将该 Web 部件添加到文档库中。

要创建自定义 Web 部件,在 Visual Studio 2010 中使用可视化 Web 部件项目模板启动一个新项目。为项目指定一个名称,例如 DocumentMerge,然后单击“确定”。

在“SharePoint 自定义向导”页面中,选择您的 Web 应用程序(您的文档库所在的 SharePoint 网站的 URL),然后单击“完成”。

创建项目后,打开 VisualWebPart1.cs 文件并使用以下代码修改 CreateChildControls 方法:

protected override void CreateChildControls() {
  Control control = Page.LoadControl(_ascxPath);
  Controls.Add(control);
  base.CreateChildControls();
  Button btnSubmit = new Button();
  btnSubmit.Text = "Merge Reports";
  btnSubmit.Click += new EventHandler(OnSubmitClick);
  Controls.Add(btnSubmit);
}

还要为按钮单击添加一个事件处理程序:

void OnSubmitClick(object sender, EventArgs e) {
  // TODO : Put code to merge documents here
}

此时,您就可以生成和部署您的项目了。后文中我们会将实现添加到 OnSubmitClick 处理程序。

下一步是将 Web 部件添加到文档库中。在 SharePoint Designer 2010 中,打开 SharePoint 网站。单击“所有文件”|“WSR Library”|“表单”,然后单击 AllItems.aspx 对其进行编辑。

单击页面的底部。单击“插入”|“Web 部件”,然后选择“更多 Web 部件”。在搜索框中,键入 VisualWebPart(您刚刚创建并部署的 Web 部件的名称),并单击“确定”(请参见图 4)。图 5 显示了 Web 部件已经就绪的页面。保存页面,并关闭 SharePoint Designer。

图 4 插入 Web 部件

图 5 页面中的 Web 部件已就绪

合并报告

现在,让我们添加用于合并文档库中上载文档的逻辑。为简单起见,此代码将会把所有上载到此文件夹的文档合并到一个文件中。在实际工作中,可能只是合并选中的文档,或只合并在指定时间段内上载的文档。您还可以将合并完的文档保存到其他位置或其他库中。现在是在 Visual Studio 2010 中将实现添加到 VisualWebPart 项目的 OnSubmitClick 处理程序中的时候了。

在 Web 部件的 OnSubmitClick 处理程序中,您需要提供用于读取上载到文档库中的报告、生成空 OpenXML 文档,以及将报告合并为一个新文档的逻辑。

首先,您需要读取当前库中的所有文档。您可以遍历当前 SPContext 的 SPListItemCollection,通过 SPFile.OpenBinary API 将每个文件都读取到一个字节数组中:

 

SPListItemCollection files = SPContext.Current.List.Items;
  foreach (SPListItem item in files) {
    SPFile inputFile = item.File;
    byte[] byteArray = 
      inputFile.OpenBinary();

    // process each byte array 
  }

接下来,生成空的 OpenXML 文档。这需要使用 MemoryStream 在内存中生成文档,因为 OpenXML SDK 不允许您将文档保存为 URI。相反,MemoryStream 对象能够将文档作为一个新文件转储到库中。图 6 显示了用于创建文件的代码。

图 6 为合并的报告创建新文件

// String containing the blank document part for our new DOCX
string strEmptyMainPart = 
  "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>" +
  "<w:document xmlns:w='https://schemas.openxmlformats.org/wordprocessingml/2006/main'>" +
  "<w:body><w:p><w:r><w:t></w:t></w:r></w:p></w:body></w:document>";

// In-memory stream for our consolidated DOCX.
MemoryStream memOut = new MemoryStream();

// Output document's OpenXML object
WordprocessingDocument outputDoc = 
  WordprocessingDocument.Create(memOut, 
  DocumentFormat.OpenXml.WordprocessingDocumentType.Document);

MainDocumentPart mainPart = outputDoc.AddMainDocumentPart();

Stream partStream = mainPart.GetStream();
UTF8Encoding encoder = new UTF8Encoding();

// Add blank main part string to the newly created document
Byte[] buffer = encoder.GetBytes(strEmptyMainPart);
partStream.Write(buffer, 0, buffer.Length);

// Save the document in memory
mainPart.Document.Save();

请注意,您需要在引用中添加 DocumentFormat.OpenXml.dll 和 WindowsBase.dll,并将相应的 using 语句添加到代码中:

using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;

接下来要实现的逻辑是将合并文档作为一个新文档保存到库中。这需要做一些工作,但您可以使用 SharePoint 托管客户端对象模型简化这些工作。您需要将两个引用添加到项目中:Microsoft.SharePoint.Client.dll 和 Microsoft.SharePoint.Client.Runtime.dll。这两个引用可以在以下文件夹中找到:

%ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\ISAPI

使用以下代码在 SharePoint 库中创建新文档:

ClientContext clientContext = 
  new ClientContext(SPContext.Current.Site.Url);
ClientOM.File.SaveBinaryDirect(clientContext, 
  outputPath, memOut, true);

要让这些指令生效,您需要在源文件中使用以下 using 语句:

using Microsoft.SharePoint.Client;
using ClientOM = Microsoft.SharePoint.Client;

使文件变得可搜索

到目前为止,您已经具备了当用户单击“合并报告”按钮,即可在服务器上生成全功能合并文档的逻辑。

然而,有一点需要注意:生成的文档与 SharePoint 爬网机制并不兼容,因为它包含 OpenXML altChunk 标记。使用前文介绍的代码将报告合并到空白文档就会产生这个问题。在 Word 中打开文档时,altChunk 会被原始内容替换。

通过 SharePoint 2010 中新的 Word Automation Services,可以使用 ConversionJob 类以编程方式执行这个任务。这个类是 Microsoft.Office.Word.Server.dll 程序集的一部分,因此请在项目中手动添加对此程序集的引用。添加完这个引用后,可以使用图 7 中的代码来执行 altChunk 的转换。

图 7 在合并的文档中转换 altChunk

string docPath = string.Format(@"{0}{1}", 
  SPContext.Current.Site.Url.Replace(@"\\", ""), 
  outputPath);
            
ConversionJobSettings JobSettings = 
  new ConversionJobSettings();
JobSettings.OutputFormat = SaveFormat.Document;
JobSettings.OutputSaveBehavior = 
  SaveBehavior.AlwaysOverwrite;

ConversionJob ConvJob = new ConversionJob(
  "Word Automation Services", JobSettings);
ConvJob.UserToken = SPContext.Current.Site.UserToken;
ConvJob.AddFile(docPath, docPath);
ConvJob.Start();

请参见本文随附的下载代码,以获得解决方案的更多详细信息,您可以将这个解决方案作为自己的报告系统的基础。

最后步骤

为了测试此代码,我们修改了 SharePoint 服务器的配置,在收到运行请求的一分钟后运行 Automation Service。默认情况下,这个时间间隔设置为五分钟,但我们不想等那么久才开始进行转换。

如果您想更改此设置,可以在“SharePoint 管理中心”中进行设置,即在“应用程序管理”|“管理服务应用程序”|“Word Automation Services”下,将“转换吞吐量”下面的开始转换“频率”设置为一分钟。

在最终生成的报告中,您创建的所有周状态报告一个接一个合并成了一个新文档。

就是这样。在以后的文章中,我们将更深入讨论关于文档内容在服务器端的合并。我们将为您展示同样是使用 Office 2010、SharePoint 2010 和 Visual Studio 2010,如何在服务器端实施邮件合并。在那之前,祝您编程愉快。

有关 Office 2010 和 SharePoint 2010 的详细信息,请参见 Office 和 SharePoint 开发人员中心。有关 Office OpenXML 的信息,可访问 msdn.microsoft.com/library/bb448854;有关 Word Automation Services 的信息,可访问 msdn.microsoft.com/library/ee558278(v=office.14)

Manvir SinghAnkush Bhatia* 属于 Microsoft 产品支持服务 (PSS) 部门的 Visual Studio 开发人员支持团队,帮助客户解决关于 Office 客户端应用程序的编程问题。您可以通过 manvir.singh@microsoft.commanvirsingh.net 与 Singh 联系。您可以通过 ankush.bhatia@microsoft.com 或 <abhatia.wordpress.com> 与 Bhatia 联系。*

衷心感谢以下技术专家审阅本文:Eric White