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

将 DOC、RTF 和 OOXML 导出格式添加到 Microsoft Report Viewer 控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (35投票s)

2008 年 2 月 27 日

CPOL

16分钟阅读

viewsIcon

299784

downloadIcon

1890

通过遵循本文概述的步骤,您将能够在本地模式下使 Report Viewer 生成 Microsoft Word 格式(DOC、RTF、WordprocessingML 和 OOXML)的报表。

引言

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 控件添加自定义渲染扩展。为此,我们将使用几种广泛使用的 .NET 工具来修改 Report Viewer 程序集。

本质上,这里描述的是“白盒破解”“道德黑客”。“白盒”意味着您自己遵循简单透明的步骤来制作一个破解版本,并且可以确保其中没有不必要的意外。“道德”意味着您不会对任何人造成伤害或骚扰。

本文所述技术将涉及

  • 使用 Reflector 查看程序集文件。
  • 使用 Microsoft 的 ILASM 和 ILDASM 工具来反汇编和汇编中间代码。
  • 使用 Microsoft 的 ILMerge 工具来合并程序集。

还有一种方法可以付出的努力少得多,实现相同的功能。此方法是在文章发布后,由社区成员Bravo Niner(非常感谢!)提出的。其思想是,您可以使用私有反射而不是破解 Report Viewer 控件。您仍然需要破解您想要集成到控件中的渲染扩展(当然,除非它是您自己开发的),但大多数烦人的步骤仍然可以省略。此技术在文章末尾的使用私有反射动态添加导出格式部分进行了描述。

免责声明

阅读和使用自担风险。作者仅表达个人观点,不认可或推广本文所述步骤。

调查它

要开始,您需要在计算机上安装 Microsoft Report Viewer 2005。您可以在安装 Microsoft Visual Studio 2005 时将其选为一项功能,或者可以从 Microsoft 网站下载Microsoft Report Viewer 2005 可再发行组件

让我们研究一下它是如何工作的以及我们最终想要得到什么。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 资源管理器做到这一点。当查看 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 文件

report-viewer-hack/image1.png

接下来,下载 Lutz Roeder 的优秀Reflector工具(除非您的机器上已经有了……几乎可以肯定您有),然后打开 Microsoft.ReportViewer.Common.dll 程序集。找到Microsoft.Reporting.ControlService.ListRenderingExtensions方法。反汇编后的方法如下所示

report-viewer-hack/image2.png

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类,该类又实现了IExtensionIRenderingExtension接口。这是一个有趣的事实,因为这些接口和渲染扩展类看起来与功能齐全且可扩展的 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,您会注意到几乎整个程序集都被混淆了,除了四个公共类,每个类代表一种特定格式的渲染扩展

report-viewer-hack/image3.png

这正是我们需要的。我们的最终目标是使用现有的模式将这些扩展的信息添加到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));

现在,让我们开始吧。

实现它

步骤 1:合并 ReportViewer 程序集

第一步是将提取的 Report Viewer 程序集合并到一个。我们为什么需要这样做?答案是:必要性和便利性。

首先,让我们考虑一下我们接下来要做什么。我们计划通过修改其 IL 代码来破解程序集;因此,我们将被迫移除它们的强名称和公钥。这意味着我们还将不得不更新所有对这些程序集的引用,并且不要忘记我们有六个 Report Viewer 程序集+一个渲染扩展程序集需要处理。

其次,Microsoft.ReportViewer.Common.dll 用一对InternalsVisibleTo属性标记,允许来自 Microsoft.ReportViewer.WinForms.dll Microsoft.ReportViewer.WebForms.dll 程序集的代码访问其内部成员。这意味着我们需要更新传递给属性构造函数的程序集名称;此外,此属性似乎根本不适用于未签名程序集。

因此,让我们将其合并为一个程序集,一次性解决所有问题。

但是,我们不能直接合并所有六个程序集,因为设计器程序集包含具有相同完全限定名称的类。此外,您很难在同一个大型程序集中同时拥有 WinForms 和 ASP.NET 控件。如果需要,可以创建两个程序集。在本文中,我们将处理 WinForms 控件,因此您需要关注的程序集是

  • Microsoft.ReportViewer.Common.dll
  • Microsoft.ReportViewer.WinForms.dll
  • Microsoft.ReportViewer.Design.dll

要合并程序集,请下载并安装 Microsoft 的一个简单实用程序,名为 ILMerge。它可以在此处获取。

将当前目录更改为 C:\Work,然后在控制台运行以下命令

"c:\program files\microsoft\ilmerge\ilmerge" 
    /out:Microsoft.ReportViewer.WinForms.Modified.dll
    microsoft.reportviewer.common.dll
    microsoft.reportviewer.processingobjectmodel.dll
    microsoft.reportviewer.winforms.dll
    microsoft.reportviewer.design.dll

现在,我们有一个单一的 Microsoft.ReportViewer.WinForms.Modified.dll 程序集,其中包含所有必要的 Report Viewer 类,集中在一个地方。

report-viewer-hack/image4.png

步骤 2:反汇编

现在,我们准备从程序集中提取一些 IL 代码。我们需要修改 Report Viewer 和渲染扩展程序集,所以我们都反汇编。我们将使用ILDASM.exe,这是 .NET Framework SDK 附带的反汇编器。

启动 Visual Studio 2005 命令提示符,并运行以下命令

ildasm Microsoft.ReportViewer.WinForms.Modified.dll 
    /out=Microsoft.ReportViewer.WinForms.Modified.il

ildasm Aspose.Words.ReportingServices.dll 
    /out=Aspose.Words.ReportingServices.il /unicode

步骤 3:移除公钥

由于我们将修改 IL 代码,因此必须移除公钥,否则我们将无法重新汇编代码。但是,如果您查看 Microsoft.ReportViewer.WinForms.Modified.dll,您会发现它没有强命名。这是因为 ILMerge 默认会构建一个未签名的程序集,除非您明确指示它对目标程序集进行签名。

好的,但是我们仍然有已签名的 Aspose.Words.ReportingServices.dll,所以我们需要从 IL 代码中移除公钥。在记事本或其他文本编辑器中打开 Aspose.Words.ReportingServices.il(可能需要一些时间加载,因为它大约有 37MB)。搜索“.publickey =”,如下所示进行选择,然后按 Delete 键。

report-viewer-hack/image5.png

步骤 4:修改渲染扩展中的引用

我们的下一个目标是重定向渲染扩展程序集的引用,使其指向修改后的 Report Viewer 程序集,而不是 Microsoft.ReportingServices.Interfaces.dll Microsoft.ReportingServices.ProcessingCore.dll

Aspose.Words.ReportingServices.il 中找到这些引用并删除它们。

report-viewer-hack/image6.png

report-viewer-hack/image7.png

现在,添加对 Microsoft.ReportViewer.WinForms.Modified.dll 的引用

.assembly extern Microsoft.ReportViewer.WinForms.Modified
{
}

report-viewer-hack/image8.png

但我们还没完。如您所知,IL 中的标识符始终是完全限定的,并且前面带有 [assembly_name],其中assembly_name是包含被引用对象的程序集的名称。因此,我们必须将所有这些引用替换为新的程序集名称。按 Ctrl-H 并将[Microsoft.ReportingServices.Interfaces][Microsoft.ReportingServices.ProcessingCore] 字符串的所有出现项替换为[Microsoft.ReportViewer.WinForms.Modified]

report-viewer-hack/image9.png

步骤 5:移除损坏的资源

Aspose.Words for Reporting Services 的混淆存在一个小麻烦,或者至少是我使用的版本(2.0.2.0)是这样。他们使用的混淆工具会损坏某些嵌入式资源的名称,并且出于某种奇怪的原因,ILASM 无法汇编代码,尽管我确信它应该接受任何 Unicode 名称。

无论如何,我找到的最简单的方法是仅仅删除那些资源(它们似乎是一些测试环境的残留)。

找到指定嵌入式资源的位置(一堆 **.mresource** token),然后简单地删除所有具有无法读取的 Unicode 名称的资源(例如Ӕ.ӗ.resources)。我为渲染扩展的 2.0.2.0 版本找到了四个这样的损坏名称。

report-viewer-hack/image10.png

保存并关闭 Aspose.Words.ReportingServices.il

步骤 6:修改 Report Viewer 中的引用

我们几乎准备好添加自定义渲染扩展(因此附加的导出格式)的信息了,但首先,我们需要添加对渲染扩展程序集的引用。

打开 Microsoft.ReportViewer.WinForms.Modified.il 并添加引用

.assembly extern Aspose.Words.ReportingServices
{
}

report-viewer-hack/image11.png

步骤 7A(适用于 WinForms 和 WebForms 控件):修改 ListRenderingExtensions 方法

我们准备修改Microsoft.Reporting.ControlService.ListRenderingExtensions方法,通过添加包含附加渲染扩展信息的LocalRenderingExtensionInfo对象。基本上,我们可以注意到如何在 IL 中实现此添加,然后复制/粘贴此代码块并进行一些更改。以下是添加 Aspose.Words for Reporting Services 渲染器的修改后的方法(更改已突出显示)

.method public hidebysig virtual instance class 
    [mscorlib]System.Collections.Generic.IEnumerable`1
    <class Microsoft.Reporting.LocalRenderingExtensionInfo> 
    ListRenderingExtensions() cil managed 
{ 
// Code size 403 (0x193) 
.maxstack 41 
.locals init (class [mscorlib]System.Collections.Generic.List`1
    <class Microsoft.Reporting.LocalRenderingExtensionInfo> V_0, 
class Microsoft.ReportingServices.Rendering.HtmlRenderer.Html40RenderingExtension V_1, 
class Microsoft.ReportingServices.Rendering.ExcelRenderer.ExcelRenderer V_2,

// --- ADDED CODE
 
class [Aspose.Words.ReportingServices]Aspose.Words.ReportingServices.DocRenderer V_3, 
class [Aspose.Words.ReportingServices]Aspose.Words.ReportingServices.RtfRenderer V_4, 
class [Aspose.Words.ReportingServices]Aspose.Words.ReportingServices.WordMLRenderer V_5, 
class [Aspose.Words.ReportingServices]Aspose.Words.ReportingServices.DocxRenderer V_6,

// --- END OF ADDED CODE
 
class Microsoft.ReportingServices.Rendering.ImageRenderer.RemoteGdiReport V_7, 
class Microsoft.ReportingServices.Rendering.ImageRenderer.ImageReport V_8, 
class Microsoft.ReportingServices.Rendering.ImageRenderer.PdfReport V_9) 
IL_0000: ldarg.0 
IL_0001: ldfld class [mscorlib]System.Collections.Generic.List`1
    <class Microsoft.Reporting.LocalRenderingExtensionInfo> 
    Microsoft.Reporting.ControlService::m_renderingExtensions 
IL_0006: brtrue IL_018c 
IL_000b: newobj instance void class [mscorlib]System.Collections.Generic.List`1
    <class Microsoft.Reporting.LocalRenderingExtensionInfo>::.ctor() 
IL_0010: stloc.0 
IL_0011: newobj instance void Microsoft.ReportingServices.Rendering.
    HtmlRenderer.Html40RenderingExtension::.ctor() 
IL_0016: stloc.1 
IL_0017: ldloc.0 
IL_0018: ldstr "HTML4.0" 
IL_001d: ldloc.1 
IL_001e: callvirt instance string Microsoft.ReportingServices.Rendering.
    HtmlRenderer.RenderingExtensionBase::get_LocalizedName() 
IL_0023: ldc.i4.0 
IL_0024: ldtoken Microsoft.ReportingServices.Rendering.HtmlRenderer.
    Html40RenderingExtension 
IL_0029: call class [mscorlib]System.Type 
    [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) 
IL_002e: ldc.i4.0 
IL_002f: newobj instance void Microsoft.Reporting.
  LocalRenderingExtensionInfo::.ctor(string, string, bool, class [mscorlib]System.Type, bool) 
IL_0034: callvirt instance void class [mscorlib]System.Collections.Generic.List`1
    <class Microsoft.Reporting.LocalRenderingExtensionInfo>::Add(!0) 

...

// --- ADDED CODE

IL_0061: newobj instance void [Aspose.Words.ReportingServices]
    Aspose.Words.ReportingServices.DocRenderer::.ctor() 
IL_0066: stloc.3 
IL_0067: ldloc.0 
IL_0068: ldstr "AWDOC" 
IL_006d: ldloc.3 
IL_006e: callvirt instance string [Aspose.Words.ReportingServices]
    Aspose.Words.ReportingServices.DocRenderer::get_LocalizedName() 
IL_0073: ldc.i4.1 
IL_0074: ldtoken [Aspose.Words.ReportingServices]
    Aspose.Words.ReportingServices.DocRenderer 
IL_0079: call class [mscorlib]System.Type 
    [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) 
IL_007e: ldc.i4.1 
IL_007f: newobj instance void Microsoft.Reporting.
  LocalRenderingExtensionInfo::.ctor(string, string, bool, class [mscorlib]System.Type, bool) 
IL_0084: callvirt instance void class [mscorlib]System.Collections.Generic.List`1
    <class Microsoft.Reporting.LocalRenderingExtensionInfo>::Add(!0) 
IL_0089: newobj instance void [Aspose.Words.ReportingServices]
    Aspose.Words.ReportingServices.RtfRenderer::.ctor() 
IL_008e: stloc.s V_4 
IL_0090: ldloc.0 
IL_0091: ldstr "AWRTF" 
IL_0096: ldloc.s V_4 
IL_0098: callvirt instance string [Aspose.Words.ReportingServices]
    Aspose.Words.ReportingServices.RtfRenderer::get_LocalizedName() 
IL_009d: ldc.i4.1 
IL_009e: ldtoken [Aspose.Words.ReportingServices]
    Aspose.Words.ReportingServices.RtfRenderer 
IL_00a3: call class [mscorlib]System.Type 
    [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) 
IL_00a8: ldc.i4.1 
IL_00a9: newobj instance void Microsoft.Reporting.
  LocalRenderingExtensionInfo::.ctor(string, string, bool, class [mscorlib]System.Type, bool) 
IL_00ae: callvirt instance void class [mscorlib]System.Collections.Generic.List`1
    <class Microsoft.Reporting.LocalRenderingExtensionInfo>::Add(!0) 
IL_00b3: newobj instance void [Aspose.Words.ReportingServices]
    Aspose.Words.ReportingServices.WordMLRenderer::.ctor() 
IL_00b8: stloc.s V_5 
IL_00ba: ldloc.0 
IL_00bb: ldstr "AWWML" 
IL_00c0: ldloc.s V_5 
IL_00c2: callvirt instance string [Aspose.Words.ReportingServices]
    Aspose.Words.ReportingServices.WordMLRenderer::get_LocalizedName() 
IL_00c7: ldc.i4.1 
IL_00c8: ldtoken [Aspose.Words.ReportingServices]
    Aspose.Words.ReportingServices.WordMLRenderer 
IL_00cd: call class [mscorlib]System.Type 
    [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) 
IL_00d2: ldc.i4.1 
IL_00d3: newobj instance void Microsoft.Reporting.
  LocalRenderingExtensionInfo::.ctor(string, string, bool, class [mscorlib]System.Type, bool) 
IL_00d8: callvirt instance void class [mscorlib]System.Collections.Generic.List`1
    <class Microsoft.Reporting.LocalRenderingExtensionInfo>::Add(!0) 
IL_00dd: newobj instance void [Aspose.Words.ReportingServices]
    Aspose.Words.ReportingServices.DocxRenderer::.ctor() 
IL_00e2: stloc.s V_6 
IL_00e4: ldloc.0 
IL_00e5: ldstr "AWDOCX" 
IL_00ea: ldloc.s V_6 
IL_00ec: callvirt instance string [Aspose.Words.ReportingServices]
    Aspose.Words.ReportingServices.DocxRenderer::get_LocalizedName() 
IL_00f1: ldc.i4.1 
IL_00f2: ldtoken [Aspose.Words.ReportingServices]
    Aspose.Words.ReportingServices.DocxRenderer 
IL_00f7: call class [mscorlib]System.Type 
    [mscorlib]System.Type::GetTypeFromHandle(valuetype 
                     [mscorlib]System.RuntimeTypeHandle) 
IL_00fc: ldc.i4.1 
IL_00fd: newobj instance void Microsoft.Reporting.
    LocalRenderingExtensionInfo::.ctor(string, string, bool, 
               class [mscorlib]System.Type, bool) 
IL_0102: callvirt instance void class [mscorlib]System.Collections.Generic.List`1
    <class Microsoft.Reporting.LocalRenderingExtensionInfo>::Add(!0) 

// --- END OF ADDED CODE

...

IL_0185: ldarg.0 
IL_0186: ldloc.0 
IL_0187: stfld class [mscorlib]System.Collections.Generic.List`1
    <class Microsoft.Reporting.LocalRenderingExtensionInfo> 
       Microsoft.Reporting.ControlService::m_renderingExtensions 
IL_018c: ldarg.0 
IL_018d: ldfld class [mscorlib]System.Collections.Generic.List`1
    <class Microsoft.Reporting.LocalRenderingExtensionInfo> 
       Microsoft.Reporting.ControlService::m_renderingExtensions 
IL_0192: ret 
} // end of method ControlService::ListRenderingExtensions

用上面的列表替换您的ListRenderingExtensions方法,然后继续下一步。

步骤 7B(仅适用于 WebForms 控件):修改 HTTP 处理程序类型

为了使 WebForms 控件正常工作,我们需要进行更多更改。由于 Microsoft.ReportViewer.WebForms.Modified.dll 不再有公钥,因此所有 HTTP 处理程序类型引用现在都应指向此未签名程序集。这是 Microsoft.ReportViewer.WebForms.Modified.il 中您应该额外修改的两个地方

  1. 找到以下方法
  2. .method assembly hidebysig static bool 
      ConfigContainsHandler(class [System.Configuration]
           System.Configuration.Configuration config)

    并将 HTTP 处理程序引用从

          IL_0064:  ldstr      "Microsoft.Reporting.WebForms.HttpHandler, Microsof"
          + "t.ReportViewer.WebForms, Version=8.0.0.0, Culture=neutral, PublicKeyTok"
          + "en=b03f5f7f11d50a3a"

    to

          IL_0064:  ldstr      "Microsoft.Reporting.WebForms.HttpHandler, Microsof"
          + "t.ReportViewer.WebForms.Modified"
  3. 找到类
  4. .class private abstract auto ansi sealed 
           beforefieldinit Microsoft.Reporting.WebForms.Constants
           extends [mscorlib]System.Object

    并替换以下字段

      .field public static literal string HttpHandlerTypeName = 
         "Microsoft.Reporting.WebForms.HttpHandler, Microsof"
         + "t.ReportViewer.WebForms, Version=8.0.0.0, Culture=neutral, 
                                     PublicKeyToken=b03f5f7f11d50a3a"

    to

      .field public static literal string HttpHandlerTypeName = 
      "Microsoft.Reporting.WebForms.HttpHandler, Microsof"
      + "t.ReportViewer.WebForms.Modified"

    请注意,之后您还需要相应地修改 web.config:替换原始类型引用

    ...
    <httpHandlers>
    
    <add path="Reserved.ReportViewerWebControl.axd" verb="*" 
    type="Microsoft.Reporting.WebForms.HttpHandler, Microsoft.ReportViewer.WebForms, 
          Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
    
    validate="false" />
    
    </httpHandlers>
    ...
    <buildProviders>
    
    <add extension=".rdlc" 
    type="Microsoft.Reporting.RdlBuildProvider, Microsoft.ReportViewer.Common, 
          Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
    
    </buildProviders>
    
    ...

    ...
    <httpHandlers>
    
    <add path="Reserved.ReportViewerWebControl.axd" verb="*" 
      type="Microsoft.Reporting.WebForms.HttpHandler, 
            Microsoft.ReportViewer.WebForms.Modified"
    
    validate="false" />
    
    </httpHandlers>
    ...
    <buildProviders>
    
    <add extension=".rdlc" 
      type="Microsoft.Reporting.RdlBuildProvider, 
            Microsoft.ReportViewer.WebForms.Modified />
    
    </buildProviders>
    
    ...

步骤 8:修改设计器引用

Report Viewer 控件使用一个设计器,该设计器为标准 Visual Studio 设计器添加了一些自定义操作。正如您可能知道的,设计器通过使用DesignerAttribute进行标记来绑定到控件。Microsoft.Reporting.WinForms.ReportViewer类(即控件本身)使用此属性进行标记,如 Reflector 中所示

[Designer("Microsoft.Reporting.WinForms.ReportViewerDesigner, 
    Microsoft.ReportViewer.Design, Version=8.0.0.0, 
    Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(IDesigner))]

请注意,属性构造函数的第一个参数是一个字符串,表示设计器类名称和包含程序集的名称,用逗号分隔。请记住,我们已将所有 Report Viewer 程序集(包括设计器程序集)合并到一个程序集中,因此此引用无效,并且在将控件拖放到窗体上时,设计器将无法加载。要使其工作,我们必须指定设计器类现在位于同一程序集中。最简单的方法是仅使用接受TypeDesignerAttribute构造函数重载,并传递typeof(Microsoft.Reporting.WinForms.ReportViewerDesigner)。假设设计器类不再是外部的,修改后的属性在 IL 中如下所示

.custom instance void [System]System.ComponentModel.
    DesignerAttribute::.ctor(class [mscorlib]System.Type) = (
01 00 31 4D 69 63 72 6F 73 6F 66 74 2E 52 65 70 // ..1Microsoft.Rep 
6F 72 74 69 6E 67 2E 57 69 6E 46 6F 72 6D 73 2E // orting.WinForms. 
52 65 70 6F 72 74 56 69 65 77 65 72 44 65 73 69 // ReportViewerDesi 
67 6E 65 72 00 00 ) // gner..

搜索DesignerAttribute(整个文件中应该只有一个出现项),并将其替换为上面的代码。

report-viewer-hack/image12.png

保存并关闭 Microsoft.ReportViewer.WinForms.il

步骤 9:汇编

我们差不多完成了!我们所要做的就是汇编我们修改后的 IL 代码。

C:\Work 文件夹中时,从 Visual Studio 命令提示符运行以下命令

ilasm Microsoft.ReportViewer.WinForms.Modified.il /dll

ilasm Aspose.Words.ReportingServices.il /dll

步骤 10:测试新的导出格式

是时候将我们修改后的控件添加到 Visual Studio 2005 了。启动 IDE,创建一个 Windows 应用程序项目,右键单击工具箱,然后选择“选择项”。现在,浏览到 C:\Work 并选择 Microsoft.ReportViewer.WinForms.Modified.dll

report-viewer-hack/image13.png

修改后的 Report Viewer 控件应出现在当前的工具箱组中。将其拖放到窗体上,然后完成您的应用程序。

请注意,虽然 Microsoft.ReportViewer.WinForms.Modified.dll 默认情况下会自动复制到输出目录,但 Aspose.Words.ReportingServices.dll 不会,您需要手动复制它。如果您希望克服此问题,我认为您至少有两个选择

  • 使用您自己的公钥重新签名程序集并将它们安装到 GAC。请注意,在编辑程序集的 IL 代码时,您需要相应地更新引用。
  • 将两个程序集合并为一个大型程序集。

将修改后的 Report Viewer 添加到您的应用程序后,您会注意到控件的外观、行为和其他属性与原始控件完全相同,唯一的区别是导出格式下拉列表中出现了一堆新格式。

report-viewer-hack/image14.png

恭喜您,您现在可以在 Report Viewer 控件中将报表导出为 Microsoft Word 格式了!

使用私有反射动态添加导出格式

文章发布后,其一位读者(Bravo Niner)提出了一个非常有用的想法,可以大大简化此过程。实际上,有一种更简单的方法可以实现目标——为什么不直接使用私有反射而不是破解控件呢?这样,您就可以在需要时动态地将渲染扩展注入列表。当然,如果您使用第三方渲染扩展(如 Aspose.Words for Reporting Services),您仍然需要修改它,使其引用定义了IRenderingExtension接口的 Microsoft.ReportViewer.Common.dll 程序集。这意味着您仍然需要

  1. 反汇编 Aspose.Words.ReportingServices.dll 到 IL(参见步骤 2)。
  2. 移除公钥(参见步骤 3)。
  3. 修改引用(参见步骤 4,但请注意这次,您需要添加对 Microsoft.ReportViewer.Common.dll 的引用,因为我们不合并/破解/做任何其他操作 Report Viewer 程序集)。
  4. 移除损坏的资源(参见步骤 5)。
  5. 将 IL 重新汇编回 Aspose.Words.ReportingServices.dll(参见步骤 9)。

但是,如果您从头开始创建自己的渲染扩展,则根本不需要破解任何东西;只需确保它实现了位于 Microsoft.ReportViewer.Common.dll 程序集中的IRenderingExtension接口。

准备好渲染扩展后,实现以下方法

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_LoadPage_Load事件处理程序中)。viewer参数是 Report Viewer 实例,name参数是导出格式在列表中应显示的名称,而extensionType参数是渲染扩展的 .NET 类型。

AddExtension(ReportViewer1, "DOC - Word Document via Aspose.Words", 
             typeof(Aspose.Words.ReportingServices.DocRenderer));

我没有发现此技术与上述步骤相比有任何缺点……它确实有效!所以,除非您是 IL 和黑客攻击的爱好者,否则我强烈推荐它:)

© . All rights reserved.