为 Microsoft Report Viewer 控件添加 DOC、RTF 和 OOXML 导出格式:使用私有反射更轻松地实现






4.85/5 (11投票s)
通过遵循本文概述的步骤,您将能够在本地模式下使用 Report Viewer 生成 Microsoft Word 格式(DOC、RTF、WordprocessingML 和 OOXML)的报表。
概述
本文是《为 Microsoft Report Viewer 控件添加 DOC、RTF 和 OOXML 导出格式》的续篇。在该系列的第一部分中,我提出了一种允许为 Microsoft Report Viewer 控件添加 Microsoft Word 导出格式的技术。这项技术需要修改 Report Viewer 的 .NET 程序集以及一个要与该控件集成的自定义呈现扩展。该文章受到了广泛评论,其中一位读者(Bravo Niner)建议通过引入私有反射而不是修改 IL 代码来大大简化该过程。我非常感谢他提出的这个绝妙主意,因为它确实可以用更少的精力实现相同的目标。我在文章末尾添加了一个简短的描述,但后来决定为此专门写一篇文章,因为它绝对值得。
但是,让我们先回顾一下我们目前拥有的以及我们最终想要拥有的。接下来的几个部分与第一篇文章几乎相同,因此,如果您已经阅读过并且熟悉该主题,请跳过此部分,直接跳转到“实现它”部分。
引言
Microsoft Report Viewer 2005 控件默认不支持导出为 Microsoft Word 格式,但通过遵循本文概述的步骤,您将能够在本地模式下使用 Report Viewer 生成 Microsoft Word 格式(DOC、RTF、WordprocessingML 和 OOXML)的报表。
我最近在为一个客户的项目工作时遇到了这个问题。该项目最初依赖于使用 Microsoft Report Viewer,但后来,客户意识到他们需要 DOC 格式的报表。我发现使用 Microsoft SQL Server 2005 Reporting Services 和第三方工具 Aspose.Words for Reporting Services 可以做到这一点,但客户坚持不应使用 SQL Server;甚至免费的 SQL Server Express Edition 也被客户拒绝了。
在评估 Aspose.Words for Reporting Services 的 SQL Server Reporting Services 时,我非常喜欢以 Microsoft Word 文档形式导出的报表,并对 Report Viewer 无法做到这一点感到非常失望。出于好奇,我进行了一些研究,并想与大家分享我的发现。
理解它
Microsoft Report Viewer 是一个强大的 .NET 控件,允许将 RDL 和 RDLC 报表嵌入到 WinForms 和 ASP.NET 应用程序中。它使用户能够查看报表并将其导出为 PDF 或 HTML 等不同格式。该控件包含在 Microsoft Visual Studio 2005 中,也可以从 Microsoft 免费下载。
Report Viewer 可以使用内置引擎(称为本地模式)独立生成报表,也可以显示由 Microsoft SQL Server 2005 Reporting Services 报表服务器(远程模式)生成的报表。
在远程模式下工作时,Report Viewer 能够导出到其连接到的报表服务器上安装的所有格式。这意味着,控件下拉列表中可用的导出格式列表与报表管理器下拉列表中显示的列表完全相同。远程模式下可用的默认呈现格式包括:Excel、MHTML、PDF、TIFF、XML 和 CSV。重要的是,通过在 SQL Server 上安装自定义呈现扩展,可以扩展导出格式的列表。市面上有非常有用的第三方呈现扩展,允许将报表导出为 Microsoft Word 格式(DOC、RTF、OOXML),例如 Aspose.Words for Reporting Services。
但是,在本地模式下工作时,导出格式的列表仅限于几种格式,并且无法扩展,因为导出是硬编码到 Report Viewer 程序集中的。这一事实是一个严重的缺点,因为许多用户希望在使用 Report Viewer 的本地模式时能够将报表导出为 DOC、RTF、OOXML 等格式。
本文介绍了一种克服此限制的方法,并使 Report Viewer 能够生成 Microsoft Word 文档格式的报表。我们的目标是将一个自定义呈现扩展添加到标准的 Microsoft Report Viewer 控件中。
免责声明
请自行承担阅读和使用的风险。作者仅表达个人观点,不认可或推广本文描述的步骤。
调查它
要开始,您需要有 Microsoft Report Viewer 2005 安装在您的计算机上。您可以在安装 Microsoft Visual Studio 2005 时选择它作为一项功能,或者从 Microsoft 网站下载 Microsoft Report Viewer 2005 Redistributable。
让我们来研究一下它是如何工作的以及我们最终想要得到什么。Report Viewer 控件包含安装在全局程序集缓存 (GAC) 中的多个程序集。以下是列表:
- Microsoft.ReportViewer.Common - 包含 WinForms 和 ASP.NET 控件的通用类。
- Microsoft.ReportViewer.ProcessingObjectModel - 包含负责本地报表处理的类(类似于 Reporting Services 使用的类)。
- Microsoft.ReportViewer.WinForms - 包含 WinForms 控件的特定类。
- Microsoft.ReportViewer.Design - 包含 WinForms 控件的设计器类。
- Microsoft.ReportViewer.WebForms - 包含 ASP.NET 控件的特定类。
- Microsoft.ReportViewer.WebDesign - 包含 ASP.NET 控件的设计器类。
我们要做的第一步是从 GAC 中提取程序集。这似乎很简单,但不幸的是,我们无法使用 Windows Explorer 来完成。查看 GAC 文件夹时,它会调用一个特殊的 Shell 扩展,该扩展会阻止程序集进行简单的复制/粘贴。因此,我们将使用命令行来完成此任务。
假设您的工作目录是 *C:\Work*,请从控制台运行以下命令:
copy c:\windows\assembly\gac_msil\microsoft.reportviewer.common\
8.0.0.0__b03f5f7f11d50a3a\microsoft.reportviewer.common.dll c:\work
copy c:\windows\assembly\gac_msil\microsoft.reportviewer.processingobjectmodel\
8.0.0.0__b03f5f7f11d50a3a\microsoft.reportviewer.processingobjectmodel.dll c:\work
copy c:\windows\assembly\gac_msil\microsoft.reportviewer.winforms\
8.0.0.0__b03f5f7f11d50a3a\microsoft.reportviewer.winforms.dll c:\work
copy c:\windows\assembly\gac_msil\microsoft.reportviewer.design\
8.0.0.0__b03f5f7f11d50a3a\microsoft.reportviewer.design.dll c:\work
copy c:\windows\assembly\gac_msil\microsoft.reportviewer.webforms\
8.0.0.0__b03f5f7f11d50a3a\microsoft.reportviewer.webforms.dll c:\work
copy c:\windows\assembly\gac_msil\microsoft.reportviewer.webdesign\
8.0.0.0__b03f5f7f11d50a3a\microsoft.reportviewer.webdesign.dll c:\work
现在,您可以在 *C:\Work* 文件夹中看到六个 DLL 文件。
接下来,下载 Lutz Roeder 的出色工具 Reflector(除非您已在机器上拥有它……几乎可以肯定您已经拥有了),然后打开 *Microsoft.ReportViewer.Common.dll* 程序集。找到 Microsoft.Reporting.ControlService.ListRenderingExtensions
方法。反编译的方法如下所示:
public override IEnumerable<LocalRenderingExtensionInfo> ListRenderingExtensions()
{
if (this.m_renderingExtensions == null)
{
List<LocalRenderingExtensionInfo> list = new List<LocalRenderingExtensionInfo>();
Html40RenderingExtension extension = new Html40RenderingExtension();
list.Add(new LocalRenderingExtensionInfo("HTML4.0", extension.LocalizedName, false,
typeof(Html40RenderingExtension), false));
Microsoft.ReportingServices.Rendering.ExcelRenderer.ExcelRenderer renderer =
new Microsoft.ReportingServices.Rendering.ExcelRenderer.ExcelRenderer();
list.Add(new LocalRenderingExtensionInfo("Excel", renderer.LocalizedName, true,
typeof(Microsoft.ReportingServices.Rendering.ExcelRenderer.ExcelRenderer), true));
RemoteGdiReport report = new RemoteGdiReport();
list.Add(new LocalRenderingExtensionInfo("RGDI", report.LocalizedName, false,
typeof(RemoteGdiReport), false));
ImageReport report2 = new ImageReport();
list.Add(new LocalRenderingExtensionInfo("IMAGE", report2.LocalizedName, false,
typeof(ImageReport), true));
PdfReport report3 = new PdfReport();
list.Add(new LocalRenderingExtensionInfo("PDF", report3.LocalizedName, true,
typeof(PdfReport), true));
this.m_renderingExtensions = list;
}
return this.m_renderingExtensions;
}
正如您所见,该方法返回一个 LocalRenderingExtensionInfo
对象的通用列表,每个对象都包含有关特定呈现扩展的信息。您可以看到导出格式的列表是硬编码的,并且不能像在报表服务器上那样使用配置文件添加新格式。
您还可以注意到,每个呈现扩展类都派生自 RenderingExtensionBase
类,该类又实现了 IExtension
和 IRenderingExtension
接口。这是一个有趣的事实,因为这些接口和呈现扩展类看起来与功能齐全且可扩展的 Microsoft SQL Server 2005 Reporting Services 中使用的非常相似。
让我们假设 Microsoft 实际上在服务器和查看器控件上使用了相同的代码来处理呈现扩展,但只是打包方式不同。他们使得在服务器上添加新的自定义呈现扩展成为可能,但通过硬编码导出格式列表而禁止在客户端这样做。我们可以无休止地争论 Microsoft 这样做的原因,但这超出了本文的范围。
就我们的目的而言,了解呈现扩展在 Report Viewer 中位于 *Microsoft.ReportViewer.XXX* 程序集中,而在 Reporting Server 上位于相应的 *Microsoft.ReportingServices.XXX* 程序集中就足够了。
我们理论的推论是,如果我们以某种方式将一个在 Reporting Server 上工作的自定义呈现扩展添加到 Report Viewer 的 ListRenderingExtensions
方法中,它就会起作用,Report Viewer 将能够生成更多格式的报表。
我们将采用一个流行的商业产品 Aspose.Words for Reporting Services,该产品允许 Microsoft SQL Server Reporting Services 将报表导出为 Microsoft Word 文档格式(DOC、DOCX、RTF 和 WordprocessingML)。我们将 Aspose.Words for Reporting Services 添加到 Report Viewer 的呈现扩展列表中,以便控件中也可以使用导出为 Microsoft Word 格式的功能。
您需要从其 下载页面下载 Aspose.Words for Reporting Services 的评估版本,并将 *\Bin\SSRS2005\Aspose.Words.ReportingServices.dll* 程序集放置在 *C:\Work* 中。再次使用 Reflector,您会注意到几乎整个程序集都被混淆了,除了四个公共类,每个类代表一种特定格式的呈现扩展。
这正是我们需要的。我们的最终目标是使用现有模式将有关这些扩展的信息添加到 ListRenderingExtensions
方法返回的列表中。要添加的代码应如下所示:
DocRenderer docRenderer = new DocRenderer ();
list.Add(new LocalRenderingExtensionInfo("AWDOC",
docRenderer.LocalizedName, false, typeof(DocRenderer), false));
DocxRenderer docxRenderer = new DocxRenderer ();
list.Add(new LocalRenderingExtensionInfo("AWDOCX",
docxRenderer.LocalizedName, false, typeof(DocxRenderer), false));
RtfRenderer rtfRenderer = new RtfRenderer ();
list.Add(new LocalRenderingExtensionInfo("AWRTF",
rtfRenderer.LocalizedName, false, typeof(RtfRenderer), false));
WordMLRenderer wordMLRenderer = new WordMLRenderer ();
list.Add(new LocalRenderingExtensionInfo("AWWORDML",
wordMLRenderer.LocalizedName, false, typeof(WordMLRenderer), false));
在第一篇文章中,我们将 Report Viewer 程序集合并到一个中,然后对其进行修改以将代码直接注入 IL。现在,我们将使用 .NET Framework 提供的强大技术——反射,从外部完成同样的事情。
实现它
请注意,只有当您希望将 Report Viewer 控件与第三方呈现扩展集成时,才需要执行步骤 1 到 5。这是因为此类扩展通常实现位于 *Microsoft.ReportingServices.Interfaces.dll* 程序集中的 IRenderingExtension
接口,而我们的目标是将它们重定向到 *Microsoft.ReportViewer.Common.dll* 程序集。如果您从头开始开发自己的呈现扩展,只需引用 *Microsoft.ReportViewer.Common.dll* 并继续执行步骤 6。
步骤 1:反汇编
与第一篇文章一样,我们将使用 .NET Framework SDK 中包含的反汇编器 ILDASM.exe。
启动 Visual Studio 2005 命令提示符,然后运行以下命令:
ildasm Aspose.Words.ReportingServices.dll /out=Aspose.Words.ReportingServices.il /unicode
步骤 2:删除公钥
由于我们将修改 IL 代码,因此我们必须删除公钥,因为之后我们将无法重新汇编代码。用记事本或任何其他文本编辑器打开 *Aspose.Words.ReportingServices.il*(加载可能需要一些时间,因为它大约有 37 MB)。搜索 “.publickey =”,按如下所示进行选择,然后按 Delete 键。
步骤 3:修改呈现扩展中的引用
我们的下一个目标是重定向呈现扩展程序集的引用,使其指向 *Microsoft.ReportViewer.Common.dll* 程序集,而不是 *Microsoft.ReportingServices.Interfaces.dll* 和 *Microsoft.ReportingServices.ProcessingCore.dll*。
在 *Aspose.Words.ReportingServices.il* 中找到这些引用并删除它们。
现在,添加对 *Microsoft.ReportViewer.Common.dll* 的引用:
不过,我们还没有完成。众所周知,IL 中的标识符始终是完全限定的,并且前面带有 [assembly_name],其中 *assembly_name* 是包含被引用对象的程序集的名称。因此,我们必须将所有这些引用替换为新的程序集名称。按 Ctrl-H,并将所有出现的 [Microsoft.ReportingServices.Interfaces] 和 [Microsoft.ReportingServices.ProcessingCore] 字符串替换为 [Microsoft.ReportViewer.Common]。
步骤 4:删除损坏的资源
Aspose.Words for Reporting Services 的混淆(至少是我使用的版本 2.0.2.0)存在一个小麻烦。它们使用的混淆工具会损坏一些嵌入式资源的名称,并且出于某种奇怪的原因,ILASM 无法汇编代码,尽管我确信它应该接受任何 Unicode 名称。
无论如何,我发现的最简单的方法是直接删除那些资源(它们似乎是一些测试工具的残留物)。
找到嵌入式资源指定的地点(一堆 .mresource
标记),然后删除所有具有无法读取的 Unicode 名称的资源(例如 *Ӕ.ӗ.resources*)。对于该呈现扩展的 2.0.2.0 版本,我找到了四个这样的损坏名称。
保存并关闭 *Aspose.Words.ReportingServices.il*。
步骤 5:汇编
现在,我们将汇编我们修改后的 IL 代码。
在 *C:\Work* 文件夹中,从 Visual Studio 命令提示符运行以下命令:
ilasm Aspose.Words.ReportingServices.il /dll
步骤 6:使用私有反射注入呈现扩展
这是最后也是关键的一步。现在,我们不是修改 Report Viewer 的代码,而是使用私有反射来完成同样的事情。将以下方法添加到您的项目中:
private static void AddExtension(ReportViewer viewer, string name, Type extensionType)
{
const BindingFlags Flags = BindingFlags.NonPublic |
BindingFlags.Public | BindingFlags.Instance;
FieldInfo previewService =
viewer.LocalReport.GetType().GetField("m_previewService", Flags);
MethodInfo ListRenderingExtensions =
previewService.FieldType.GetMethod("ListRenderingExtensions", Flags);
IList extensions = ListRenderingExtensions.Invoke(
previewService.GetValue(viewer.LocalReport), null) as IList;
Type localRenderingExtensionInfoType = Type.GetType(
"Microsoft.Reporting.LocalRenderingExtensionInfo, " +
"Microsoft.ReportViewer.Common," +
"Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
ConstructorInfo ctor = localRenderingExtensionInfoType.GetConstructor(
Flags, null, new Type[] { typeof(string), typeof(string),
typeof(bool), typeof(Type), typeof(bool) }, null);
object instance =
ctor.Invoke(new object[] { name, name, true, extensionType, true });
extensions.Add(instance);
}
现在,您可以随时调用此方法来向 Report Viewer 格式列表添加自定义导出格式(可以考虑的合适位置是 WinForms 或 ASP.NET 应用程序中的 Form_Load
或 Page_Load
事件处理程序,分别)。viewer
参数是 Report Viewer 实例,name
参数是要显示在列表中的导出格式的名称,extensionType
参数是呈现的 .NET 类型扩展。
AddExtension(ReportViewer1, "DOC - Word Document via Aspose.Words",
typeof(Aspose.Words.ReportingServices.DocRenderer));
步骤 7:测试新的导出格式
完成!只需运行您的项目,您应该会注意到列表中出现了一个新的导出格式。
结论
如您所见,与破解 Report Viewer 程序集相比,使用私有反射有许多优点:
- 整个过程更简单。您只需要修改第三方呈现扩展以将其重定向到 *Microsoft.ReportViewer.Common.dll*;如果您正在开发自己的呈现扩展,您只需要在代码中添加一个简短的方法。
- 您无需关心 Report Viewer 程序集放置在哪里;您无需修改 *web.config*;换句话说,您摆脱了第一篇文章中描述的所有困难。
- 您可以动态地向列表添加(也可能删除)导出格式。