SQL Server Reporting Services 2005/2008/2012 的 Zip 渲染扩展





5.00/5 (20投票s)
如何创建和部署 SSRS 渲染扩展,通过一个功能性的 Zip 渲染扩展来解释 SSRS 2005、2008 (R2) 和 2012。
- 下载 SSRS2005 二进制文件 - 279.2 KB
- 下载 SSRS2008 二进制文件 - 288.3 KB
- 下载 SSRS2012 二进制文件 - 279.9 KB
- ZipRenderer 源代码 - 34.1 KB
简介
在 Reporting Services 的报表管理器中,您可以将报表导出为 Excel、CSV、PDF 等格式。但如果您想为每个 PDF 添加水印呢?或者,将导出的 Excel 的工作表名称从“Sheet2”更改为更有意义的名称?或者,对现有(内置)渲染扩展进行任何类型的处理呢?
本文介绍了如何创建一个 SQL Server Reporting Services (SSRS) 渲染扩展,该扩展可以在报表发送给客户端之前更改其他渲染器的输出——在本例中,它将一个或多个报表压缩到 .zip 文件中。
此外,还有关于部署和故障排除的通用说明,这些说明对于任何渲染扩展都很有用。我已尽力使内容清晰完整。关于部署的一些文档*确实*存在,但散布在各处(尤其是在 MSDN 上)。本文应能帮助任何人开始使用渲染扩展。
目录
- 背景
- 使用代码
- 关注点
MSDN 链接列表
- 渲染扩展
- 概述:http://msdn.microsoft.com/en-us/library/ms154606.aspx
- 实现
IRenderingExtension
接口:http://msdn.microsoft.com/en-us/library/ms154018.aspx - 部署:http://msdn.microsoft.com/en-us/library/ms154516.aspx
- 配置,设备信息
- 将设备信息设置传递给渲染扩展:http://msdn.microsoft.com/en-us/library/ms155397.aspx
- 在 RSReportServer.Config 中自定义渲染扩展参数 http://msdn.microsoft.com/en-us/library/ms156281.aspx
- 使用 Reporting Services 安全策略文件:http://msdn.microsoft.com/en-us/library/ms152828.aspx
- 指南
- SSRS 2005 入门指南: http://msdn.microsoft.com/en-us/magazine/cc163840.aspx
背景
在我参与的一个项目中,我创建了一个包含数十兆字节数据的报表。该报表后来被安排每天通过电子邮件发送。这当然太大,不适合通过电子邮件发送,但如果您想将数据发送给客户,通常别无选择。
因此,我开始寻找更小的替代方案。最小的现有导出是 .CSV,但是您只能将结构化为表的导出数据导出到 .CSV 文件中。.CSV 处理起来也不实用,除非您设置了自动数据传输。
然后,我读到了我的同胞撰写的关于渲染扩展的这篇优秀文章:http://www.broes.nl/2011/02/pdf-watermarkbackground-rendering-extension-for-ssrs-part-1/ 这篇文章详细介绍了如何修改导出的 PDF(添加水印)。
因此,我想到,如果您可以修改 PDF,当然也可以将输出内容打包成 zip 文件并返回一个 zip 文件?这个 ZipRenderer 的基本思想是一样的:获取对现有渲染器的引用,让它渲染一个报表。获取输出并对其进行处理。
尽管 broes.nl 上的文章是一个很好的开始(我强烈建议您也阅读它),但仍有许多东西需要弄清楚。例如,2005 年可用的 PdfRenderer 从 2008 年起就不再可用了,所以文章中的方法也不再起作用了。
使用代码
这部分是关于部署的;下面的步骤适用于任何渲染扩展。(不包括那些显然与 ZipRenderer 相关的内容)
构建和部署扩展
打开适用于您环境的相应项目。2012 年的项目可用于 SSRS 2008 和 2012。由于这是一个普通的 .NET 程序集项目,因此不需要 BIDS。
- 添加对以下项的引用
Microsoft.ReportingServices.ExcelRendering.dll
Microsoft.ReportingServices.ProcessingCore.dll
Microsoft.ReportingServices.Interfaces.dll
SSRS 2005 项目还需要
Microsoft.ReportingServices.CsvRendering.dll
Microsoft.ReportingServices.ImageRendering.dll所有这些都在 SQL Server 机器上的 [SQL Reporting Services 文件夹]\ReportServer\Bin\ 中。我发现最简单的方法是将该 bin 文件夹的全部内容复制到开发机器上,以便进行研究和将来在扩展中使用。
ExcelRendering 和 ProcessingCore 会因 .NET 版本较新而发出警告,您可以安全地忽略它。(请参阅 http://support.microsoft.com/kb/2722683) - 为了实现 zip 功能,我们需要 DotNetZip 库:http://dotnetzip.codeplex.com/releases 下载最新的 Runtime,从 bin/Release 文件夹之一中提取 Ionic.Zip.dll(zip-v#.# 包含普通的 Zip 压缩),然后添加对 Ionic.Zip.dll 的引用。
- 构建项目。
- 将 ZipRenderer.dll 和 Ionic.Zip.dll 从输出文件夹移动到 [SQL Reporting Services 文件夹]\ReportServer\Bin\。
您可能会注意到输出文件夹中还有很多其他的 dll。这些都与报表服务器相关,不需要复制到报表服务器。(实际上,它们最初就源自那里)。
服务器配置
现在需要进行一些配置,以便我们的扩展能够显示在报表管理器中。多亏了这篇文章,这并不难。 http://www.broes.nl/2011/02/pdf-watermark-background-rendering-extension-for-ssrs-part-2/
- 将以下内容添加到 [SQL Reporting Services 文件夹]\ReportServer\rsreportserver.config 中,位于
</Render>
标签的正上方<Extension Name="ZIPEXCEL" Type="ZipRenderer.ZipRenderingProvider,ZipRenderer"> <Configuration> <DeviceInfo> <ZipRenderer description ="Zipped Excel"> <SubRenderers> <SubRenderer extension="xls" format="EXCEL" /> </SubRenderers> </ZipRenderer> </DeviceInfo> </Configuration> </Extension>
(请注意
<DeviceInfo>
内的自定义标签。) - 并将以下内容添加到 [SQL Reporting Services 文件夹]\ReportServer\rssrvpolicy.config:
-
<CodeGroup class="UnionCodeGroup" version="1" PermissionSetName="FullTrust" Name="Zip Renderer" Description="This code group grants Zip Renderer code full trust."> <IMembershipCondition class="UrlMembershipCondition" version="1" Url="[SQL Reporting Services folder]\ReportServer\bin\ZipRenderer.dll" /> </CodeGroup>
将此 XML 放在配置文件中,具体说明请参见 MSDN
对于您开发的扩展和自定义程序集,建议将您的自定义代码组直接放在 URL 成员关系 "$CodeGen$/*" 的现有条目下方。
这是一个
UrlMembershipCondition
,它使用 DLL 的路径或 URL。请记住将上面的 Url 更改为指向 ZipRenderer.dll 的正确路径!您也可以使用
StrongNameMembershipCondition
,请再次查看 Broes 的文章第 2 部分。这需要更多的操作才能设置好。如果您想以这种方式部署您的程序集,请查看那里。 - 最后,重新启动 Reporting Services 服务以应用对 .config 文件的更改。
*较新版本的 SSRS 会检测到对配置文件所做的更改,并自动重新启动/重新配置服务。这在应用程序事件日志中会显示为一条信息事件,例如“RSReportServer.config 文件已被修改”。
故障排除
现在,如果一切设置正确,您应该会在运行报表时在导出下拉菜单中看到名为“Zipped excel”的扩展。如果情况是这样,恭喜您,您已成功部署了该扩展。单击导出链接,您将下载一个包含 .xls 文件的 zip 文件。
现在,很多事情都可能出错——我认为我都遇到了——如果它没有显示或不起作用,这里有一些提示:
- 如果扩展根本不显示,最可能的原因是配置文件未正确修改(指向 ZipRenderer.dll 的路径错误)、文件未保存到磁盘或未应用。确保它们已保存,然后重新启动 SQL Server Reporting Services。在 SSRS 2005 中,重新启动 IIS 中的整个网站可能也有帮助。
- 还请检查应用程序事件日志以获取错误消息,例如“报表服务器(MS SQL Server)无法加载 ZIPEXCEL 扩展。”,这将为您指明正确的方向。在错误事件附近的 ASP.NET 警告事件的堆栈跟踪中可能包含有用信息。
- 检查 [Reporting Services 文件夹]\LogFiles 中的最新日志文件。按修改日期排序并打开最新的日志文件。然后向下滚动并查找堆栈跟踪。
- 如果选择 ZipExcel 时出现服务器错误页面,您也可以在页面本身中找到详细信息。如果未显示错误详细信息,请在服务器上打开报表页面以查看错误详细信息。
调试
如果您在 SSRS 计算机上运行 Visual Studio,则调试非常简单,但这在 MSDN 上并未记录。(页面 Debugging Delivery Extensions 最接近)
首先,将 ZipRenderer.pdb 文件复制到服务器。否则,调试器将抱怨符号未加载。要开始调试:
- 以管理员身份运行 Visual Studio 并打开项目
- 选择“工具”->“附加到进程”。
- 如果需要,请选中“显示所有用户的进程”和“显示所有会话的进程”
- 找到并选择托管 ZipRenderer DLL 的可执行文件
- SSRS 2008+:ReportServicesService.exe
- IIS6:w3wp.exe
- IIS7:aspnet_wp.exe
- 附加。
如果您无法在服务器上运行 Visual Studio,则可以使用远程调试。MSDN 上对此进行了说明: 远程调试 设置。这需要多少工作取决于您的服务器配置。我将其留给其他专门的文章来解释。
兴趣点
现在……终于到了项目中的所有精巧代码部分了。代码中实际的 zip 过程相当小——关键在于获取渲染器和正确的数据流。
内置的 ExcelRenderer
问题 1:读取内置 ExcelRenderer 的输出
起初我最关心的问题之一是如何获取其他内置渲染器(尤其是 ExcelRenderer
)的输出。在 SSRS 2005、2008 和 2012 中,Excel 渲染器在 Microsoft.ReportingServices.ExcelRendering
命名空间中是公开的。
一切顺利,您可以将 ExcelRenderer
添加到您的引用中,实例化它并让它渲染一些报表。但是:ExcelRenderer
的行为不佳;它会关闭 CreateAndRegisterStream
回调函数提供的 MemoryStream
。无法获取结果。即使对 MemoryStream
调用 ToArray()
也无效:只返回 Excel 文件的一部分,其余部分被截断,因此如果您尝试写入它,最终会得到一个损坏的文件。
我不是 Stream 专家,但我认为它之所以这样行为,是因为 ExcelRenderer
调用 CreateAndRegisterStream
多次;每次调用一次用于每个工作表或嵌入的图像。
解决方案:UnclosableMemoryStream
在花费了很多时间之后,我决定直接继承 MemoryStream
来阻止 ExcelRenderer
关闭*我的* MemoryStreams
。一个简单的解决方案,但效果很好!
class UnclosableMemoryStream: MemoryStream
{
private bool allowClose;
public bool AllowClose
{
get { return allowClose; }
set { allowClose = value; }
}
public override void Close()
{
if (AllowClose)
base.Close();
}
}
此流在您允许之前不会关闭。因此,在准备好此类后,您可以将其实例传递给 CreateAndRegisterStream
回调函数中的 ExcelRenderer
,并且以后仍然可以读取内容。
问题 2:多个流
解决了 MemoryStream
问题后,还需要处理 ExcelRenderer 所需的多个流。
渲染器的 CreateAndRegisterStream
仅被调用一次,其参数值为 StreamOper.CreateAndRegister
。这是最终包含所有 Excel 内容的主 Stream,所以我们将它存储为 RegisteredStream
。
下面是 CreateAndRegisterStream
的 Excel 版本。
(注意:实际的 UnclosableMemoryStream 实例在包装类 CreateAndRegisterStreamUnclosableMemoryStream
中创建。此类还存储 CreateAndRegisterStream
调用所需的参数,供以后在 Render 方法中使用。)
// Intermediate CreateAndRegisterStream method that matches the delegate
// Microsoft.ReportingServices.Interfaces.CreateAndRegisterStream
// It will return a reference to a new MemoryStream, so we can get to
// the results of the intermediate render-step later.
public Stream IntermediateCreateAndRegisterStreamExcel(
string name,
string extension,
Encoding encoding,
string mimeType,
bool willSeek,
StreamOper operation)
{
//Create a stream container and store every parameter in it
CreateAndRegisterStreamStream crss =
new CreateAndRegisterStreamUnclosableMemoryStream(
name, extension, encoding, mimeType, willSeek, operation);
//Store stream container
intermediateStreams.Add(crss);
if (operation == StreamOper.CreateAndRegister)
//Create the main stream. Contents of this stream are returned later
RegisteredStream = crss.Stream;
return crss.Stream;
}
来自配置文件的自定义 DeviceInfo
您可能已经注意到,包括现有渲染器在内的所有渲染器都可以从 rsreportserver.config 进行配置。读取您自己渲染器配置并应用它的地方是在 IExtension.SetConfiguration
中:
/// <summary>
/// Process XML data stored in the configuration file
/// </summary>
/// <param name="configuration">The XML string from the configuration file that contains extension configuration data.</param>
void IExtension.SetConfiguration(string configuration)
参数“configuration”包含 rsreportserver.config 中 <Configuration>
标签的 Inner XML。
IRenderingExtension
.Render 方法还有一个名为 deviceInfo
的配置参数,它是一个 NameValueCollection。DeviceInfo
也是 rsreportserver.config 中的一个配置项,但更简化:它包含 <DeviceInfo>
的每个直接子项及其 XmlNode.InnerText
作为值。
有关 DeviceInfo
的更多信息,请参阅 MSDN 此处和此处
更新:增加了对 zip 模块设置的 deviceInfo 参数的支持。请参阅本章下面的章节。
对于 zip 渲染器,我设置了一个配置格式,允许将多个 SubRenderer(我称之为它们)包装到 zip 文件中,并且它们可以拥有自己独立的 DeviceInfo
配置。每个 <SubRenderer>
的内容会原样传递给它们各自的 Renderer 对象,并用作配置字符串。
一个配置示例
<Extension Name="ZIPEXCELPDF" Type="ZipRenderer.ZipRenderingProvider, ZipRenderer">
<Configuration>
<DeviceInfo>
<ZipRenderer description="Zipped Excel + PDF">
<SubRenderers>
<SubRenderer extension="xls" format="EXCEL" />
<SubRenderer extension="pdf" format="PDF">
<DeviceInfo>
<StartPage>3</StartPage>
<EndPage>4</EndPage>
</DeviceInfo>
</SubRenderer>
</SubRenderers>
</ZipRenderer>
</DeviceInfo>
</Configuration>
</Extension>
以下是它的解释方式
- description="Zipped Excel + PDF" 是您在报表管理器导出列表中看到的名称
- <Subrenderers> 告诉扩展使用以下 SubRenderer
- SubRenderer: format EXCEL: 使用 ExcelRenderer,使用“xls”作为文件扩展名
- (无附加 DeviceInfo)
- SubRenderer: format PDF: 使用 PdfRenderer,使用“pdf”作为文件扩展名
- 调用 pdfrenderer 时添加附加 DeviceInfo:从第 3 页开始,渲染到第 4 页(是的,我知道,这很荒谬,但仅用于演示)
解析 XML 的代码
/// <summary>
/// Process XML data stored in the configuration file
/// </summary>
/// <param name="configuration">The XML string from the configuration file that contains extension configuration data.</param>
void IExtension.SetConfiguration(string configuration)
{
this.configuration = configuration;
// Create the document and load the Configuration element
XmlDocument doc = new XmlDocument();
try
{
doc.LoadXml(configuration);
//Check for the DeviceInfo element
if (doc.DocumentElement.Name == "DeviceInfo")
{
//Find the ZipRenderer node
XmlNode zipRendererNode = doc.DocumentElement.SelectSingleNode("ZipRenderer");
if (zipRendererNode == null)
throw new System.Configuration.ConfigurationErrorsException(
"Missing ZipRenderer node in configuration", doc.DocumentElement);
//Read this extension's description
description = zipRendererNode.Attributes["description"].Value;
//Read all of the SubRenderers configured
subRenderers = new List<SubRenderer>();
foreach (XmlNode zippedReportNode in zipRendererNode.SelectNodes("SubRenderers/SubRenderer"))
{
try
{
//Try and create a SubRenderer
subRenderers.Add(SubRenderer.CreateSubRenderer(
zippedReportNode.Attributes["format"].Value,
zippedReportNode.Attributes["extension"].Value,
zippedReportNode.InnerXml));
}
catch (System.Configuration.ConfigurationErrorsException ex)
{
//An invalid SubRenderer format was used.
throw new System.Configuration.ConfigurationErrorsException(
String.Format("The SubReport format {0} could not be found",
zippedReportNode.Attributes["format"].Value),
ex, zippedReportNode);
//Tried this but fails in 2008 / 2012. The ZipRenderer
// and the ReportViewer renderer will keep waiting for each other. Deadlock.
//TODO: Replace by WebServiceSubRenderer.
//subRenderers.Add(new ReportViewerSubRenderer(
// zippedReportNode.Attributes["format"].Value));
}
}
}
}
catch (XmlException ex)
{
throw new System.Configuration.ConfigurationErrorsException(
"Failed to read configuration data: " + ex.Message, ex);
}
}
实际的 SubRenderer
是在 SubRenderer.CreateSubRenderer(…)
中确定的。
请注意,SubRenderer
节点内的 InnerXml
会原样传递给 SubRenderer
;这可能包含额外的 DeviceInfo
。SubRenderer
负责解析自己的配置字符串。
更新:zip 过程的 DeviceInfo
根据读者的建议,包含的 ziprenderer 现在还支持 Ionic.zip 的以下设置:
CompressionLevel、CompressionMethod、 Strategy、 Comment、 EnableZip64、 IgnoreCase、 Encryption 和 Password。
请关注链接以了解每个配置项的可用值,或查看 Ionic.Zip Zip 参考。
配置项不是必需的,但是如果您启用 Encryption,则还必须输入 Password 值。
配置示例:
<Extension Name="ZIPEXCELOPENXML" Type="ZipRenderer.ZipRenderingProvider,ZipRenderer">
<Configuration>
<DeviceInfo>
<CompressionLevel>BestCompression</CompressionLevel>
<Encryption>WinZipAes256</Encryption>
<Password>testpass</Password>
<ZipRenderer description ="Zipped Excel 2012">
<SubRenderers>
<SubRenderer extension="xlsx" format="EXCELOPENXML">
</SubRenderer>
</SubRenderers>
</ZipRenderer>
</DeviceInfo>
</Configuration>
</Extension>
读取设置的代码(在 ZipRenderingProvider.Render
中)
(...)
//Create a Zip output and tell it to keep the provided stream open - we use it outside the using clause
using (ZipOutputStream zipOutput = new ZipOutputStream(outputMemoryStream, true))
{
//Read zip deviceinfo
try
{
if (deviceInfo["CompressionLevel"] != null)
zipOutput.CompressionLevel = (Ionic.Zlib.CompressionLevel)Enum.Parse(
typeof(Ionic.Zlib.CompressionLevel), deviceInfo["CompressionLevel"], true);
if (deviceInfo["CompressionMethod"] != null)
zipOutput.CompressionMethod = (Ionic.Zip.CompressionMethod)Enum.Parse(
typeof(Ionic.Zip.CompressionMethod), deviceInfo["CompressionMethod"], true);
if (deviceInfo["Strategy"] != null)
zipOutput.Strategy = (Ionic.Zlib.CompressionStrategy)Enum.Parse(
typeof(Ionic.Zlib.CompressionStrategy), deviceInfo["Strategy"], true);
if (deviceInfo["Comment"] != null)
zipOutput.Comment = deviceInfo["Comment"];
if (deviceInfo["EnableZip64"] != null)
zipOutput.EnableZip64 = (Ionic.Zip.Zip64Option)Enum.Parse(
typeof(Ionic.Zip.Zip64Option), deviceInfo["EnableZip64"], true);
if (deviceInfo["IgnoreCase"] != null)
zipOutput.IgnoreCase = Boolean.Parse(deviceInfo["IgnoreCase"]);
if (deviceInfo["Encryption"] != null)
zipOutput.Encryption = (Ionic.Zip.EncryptionAlgorithm)Enum.Parse(
typeof(Ionic.Zip.EncryptionAlgorithm), deviceInfo["Encryption"], true);
if (deviceInfo["Password"] != null)
zipOutput.Password = deviceInfo["Password"];
}
catch (Exception ex)
{
throw new System.Configuration.ConfigurationErrorsException(
"Invalid DeviceInfo configuration value", ex);
}
foreach (SubRenderer sr in subRenderers)
{<span style="font-size: 9pt;">
(...)</span>
SSRS 2008 R2 / 2012 中的内置 PDFRenderer
在 SSRS 2005 中,您可以像使用 ExcelRenderer 一样使用内置的 PdfRenderer,因为它也是公开的。
但在 SSRS 2008 (R2) / 2012 中就没那么幸运了。Microsoft 已将 PdfRenderer 设为内部密封类,因此您必须诉诸反射方法才能访问它。是的,您没听错。以下是执行此操作所需的代码。
// Initialize the PDF renderer. it is an internal sealed class but we can still get to it using..Reflection!
if (pdfRendererType == null)
{
//1. Load the ImageRendering Assembly
//Use a disassembler tool like ILSpy to find The AssemblyName, type and constructor
//methods for other internal renderers in their respective .dlls
//This is code for the Sql Server 2012 assembly.
//Replace Version=11.0.0.0 by Version=10.0.0.0 and you are good to go for 2008.
Assembly IR = Assembly.Load(new AssemblyName("Microsoft.ReportingServices.ImageRendering,
Version=11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91"));
//2. Read the PdfRenderer type from the Assembly
pdfRendererType = IR.GetType("Microsoft.ReportingServices.Rendering.ImageRenderer.PDFRenderer");
}
//3. Create an instance of type PdfRenderer. PdfRenderer inherits from
// IRenderingExtension which is a public interface so cast it to IRenderingExtension
renderer = (IRenderingExtension)pdfRendererType.GetConstructor(BindingFlags.Public |
BindingFlags.Instance, null, Type.EmptyTypes, null).Invoke(null);
// /Reflection
// phew..
这里的诀窍是,最终 pdfRenderer 被强制转换为接口 IRenderingExtension
。除非您想使用 pdfRenderer 特有的函数,否则反射到此为止。这很好。
有了这段代码,在 SSRS 2008/2012 中实现 PDF 水印就成为可能!
通用 SubRenderer
对于每个内置渲染器,我们需要获取对 SSRS 渲染器类的引用。此外,还有各种关于流的问题需要解决。您可能会想:难道不能告诉报表服务器创建报表,然后将其取回吗?
嗯,我们可以:通过调用 ReportExecutionService
或报表服务器 URL 访问。但是,服务器已经在 IRenderingExtension
.Render 方法的 report
参数中为我们提供了报表数据。什么都不做而重新运行整个报表,岂不是一种浪费?
ReportViewer 来拯救(还是不救?)
您可能已经在报表管理器中注意到,导出报表时没有延迟。这是因为它没有重新运行整个报表:它将 SessionID 传递给 SSRS 引擎。这样 SSRS 就知道它需要重用已显示的数据,并只需以不同方式进行渲染。 这在代码中也是可能的,请参阅 ReportViewerSubRenderer
类(修改自此博客评论)。它会生成一个 HttpWebRequest
,如下所示:
/Reports/Reserved.ReportViewerWebControl.axd?ReportSession=4obc0z550
su1si2em2oxv445&Culture=1043&CultureOverrides=False&UICulture=9&UICultureOverrides=
False&ReportStack=1&ControlID=ab4010a279a44dffbe1b15e6c7182d7b&OpType=Export&
FileName=Report1&ContentDisposition=OnlyHtmlInline&Format=EXCEL
有一个问题。其实有两个
在订阅中,没有可用的 SessionID。没有 SessionID,ReportViewerWebControl
无法用于渲染报表,它不知道要渲染哪个报表……
而且,虽然这在 SSRS 2005 中(从报表管理器)起作用,但在 SSRS 2008 中,引擎似乎变得更智能了,并且会阻止对 ReportViewerWebControl
的附加调用;上述请求将超时。我假设这是为了防止重复请求,以保护服务器资源。
这是一张图表来说明这一点——请记住,这都是基于观察,并且可能不反映实际 过程。
解决方案
如果您将 Web 服务/URL 访问与报表数据缓存一起使用,它仍然可能是一个可行的选择。只需将缓存过期时间设置为 5 分钟之类的短时间,足以让报表数据被存储和重用。只需继承 SubRenderer
并在 CreateSubRenderer
中指向您的类。——我将来可能会自己添加它。
除此之外,内置渲染器是最有效的解决方案。您只需单独编码每个渲染器。
如果我们可以像在Report.Render(format, deviceInfo)
方法中访问交付扩展一样,一切都会容易得多……
历史
- 2012 年 11 月 11 日 v1.0:发布第一篇文章!
- 2012 年 1 月 26 日 v1.1:增加了对 CompressionLevel 等 Zip 选项的支持