使用 RsClientPrint 从 ASP.NET WebForms 打印报表





5.00/5 (2投票s)
一个“如何做”的文章,
引言
在开发使用 Microsoft SQL Server Reporting Services 2005 (SSRS) 显示和打印报表的 ASP.NET 应用程序时,当您想在传统的 Web 报表查看器之外为用户提供直接打印功能时,您会遇到一个问题。本文将介绍如何使用 Microsoft 提供的 RsClientPrint
ActiveX 控件在自定义 ASP.NET 应用程序中打印 SSRS 报表。
请注意,本文不包含任何可下载的示例;只包含代码片段和场景描述。
背景
如果您在网上搜索有关 SSRS 报表的打印选项,您会发现以下三种可能的选择:
- 将报表渲染为 HTML 或图像,并使用 JavaScript 使用 Web 浏览器(Internet Explorer 或 Firefox)的打印功能打印报表。此选项的弊端是:
- 当您的报表渲染为多页时,页面将不会以正确的格式打印。由于 Web 浏览器的打印页边距设置和其他打印问题,您会遇到分页不正确的页面,因为浏览器会将其作为 HTML 页面打印。
- Web 浏览器页眉和页脚将显示在打印输出中。您可以指导用户在您使用打印功能时删除 Web 浏览器的页眉和页脚。(用户会讨厌这一点。)
- 打印横向报表将要求用户每次想要打印横向报表时都更改其打印设置。
- 将报表导出为可用于打印的其他格式。例如 PDF 或 Microsoft Excel。弊端是:
- 用户必须拥有 PDF 或 Excel 查看器才能打印报表。
- 打印操作将很繁琐,因为用户需要执行许多步骤才能仅打印报表。
- 使用
RsClientPrint
ActiveX 控件。这是一个将报表直接发送到打印机的不错解决方案。弊端是:- 用户必须使用 Internet Explorer 作为 Web 浏览器来支持 ActiveX 控件。您也可以为该任务编写 Firefox 扩展。
- Microsoft 的 文档 非常简短(不要期望从 Microsoft MSDN 中获得太多关于使用
RsClientPrint
的信息)。 - 如果您遵循 Microsoft 文档,您将遇到 SSRS 身份验证问题,正如我们将在本文中讨论的那样。
在本文中,我将介绍如何高效地使用 RsClientPrint
,并实现 Microsoft 报表 Web 查看器中提供的相同打印功能。
介绍 RsClientPrint
RsClientPrint
是一个 ActiveX 控件。在使用它之前,必须将其注册到客户端 PC。
RsClientPrint
控件包含多个属性和一个方法。有关更多信息,请参阅 Microsoft RsClientPrint
此处。
以下是从上述 MSDN 网站借用的控件属性和方法列表:
RsClientPrint 属性
属性 | 类型 | RW | 默认值 | 描述 |
MarginLeft |
双精度浮点型 |
RW | 报表设置 | 获取或设置左边距。如果开发人员未设置或报表中未指定,则默认值为 12.2 毫米。有关报表边距的更多信息,请参阅 此链接。 |
MarginRight |
双精度浮点型 |
RW | 报表设置 | 获取或设置右边距。如果开发人员未设置或报表中未指定,则默认值为 12.2 毫米。有关报表边距的更多信息,请参阅 此链接。 |
MarginTop |
双精度浮点型 |
RW | 报表设置 | 获取或设置上边距。如果开发人员未设置或报表中未指定,则默认值为 12.2 毫米。有关报表边距的更多信息,请参阅 此链接。 |
MarginBottom |
双精度浮点型 |
RW | 报表设置 | 获取或设置下边距。如果开发人员未设置或报表中未指定,则默认值为 12.2 毫米。有关报表边距的更多信息,请参阅 此链接。 |
PageWidth |
双精度浮点型 |
RW | 报表设置 | 获取或设置页面宽度。如果开发人员或报表定义未设置,则默认值为 215.9 毫米。 |
PageHeight |
双精度浮点型 |
RW | 报表设置 | 获取或设置页面高度。如果开发人员或报表定义未设置,则默认值为 279.4 毫米。 |
文化 |
Int32 |
RW | 浏览器区域设置 | 指定区域设置标识符(LCID)。此值决定用户输入的度量单位。例如,如果用户输入 3,如果语言是法语,则该值将以毫米为单位;如果语言是英语(美国),则以英寸为单位。有效值包括:1028、1031、1033、1036、1040、1041、1042、2052、3082。 |
UICulture |
字符串 |
RW | 客户端区域设置 | 指定对话框的字符串本地化。打印对话框中的文本本地化为以下语言:简体中文、繁体中文、英语、法语、德语、意大利语、日语、韩语和西班牙语。有效值包括:1028、1031、1033、1036、1040、1041、1042、2052、3082。 |
Authenticate |
布尔值 |
RW | 指定控件是否发出 GET 命令给报表服务器,以启动到报表服务器的连接以进行会话外打印。 |
RsClientPrint 方法
RsClientPrint
控件只支持一个名为 print 的方法。此方法用于启动打印对话框。方法的参数在上述 Microsoft 链接中进行了描述。我已将其粘贴在此处作为参考。
参数 | I/O | 类型 | 描述 |
ServerPath |
In | 字符串 |
指定报表服务器的虚拟目录(例如,https://adventure-works/reportserver)。 |
ReportPathParameters |
In | 字符串 |
指定报表在报表服务器文件夹命名空间中的完整名称,包括参数。报表通过 URL 访问进行检索。例如:“/AdventureWorks Sample Reports/Employee Sales Summary&EmpID=1234” |
ReportName |
In | 字符串 |
报表的简短名称(在上面的示例中,简短名称是 Employee Sales Summary)。它显示在打印对话框和打印队列中。 |
RsClientPrint 如何工作
RsClientPrint
以 EMF(增强型图元文件)格式渲染报表,以将报表发送到打印机。EMF 格式是一种矢量格式,这使得渲染的报表独立于分辨率。有关更多信息,请访问 此 Wikipedia 链接。
RsClientPrint
使用报表服务直接 URL 访问来将报表渲染为 EMF 格式。直接 URL 访问允许用户使用报表服务 URL 和查询字符串与报表服务进行通信,以向报表服务传递命令。有关 URL 访问的更多信息,请访问 此 MSDN 链接。
当使用 EMF 格式渲染报表时,RsClientPrint
指示报表服务使用 URL 命令 rs:PresistStream=true
持久化渲染的报表流。此命令将强制报表服务渲染第一页并直接将结果流返回给调用者。要获取下一页,RsClientPrint
将用 rs:GetNextStream=true
替换 rs:PresistStream
命令。报表服务将渲染下一页并将其流返回给调用者。当没有更多页面时,报表服务将返回一个空流。
RsClientPrint
将读取结果 EMF 流并将其发送到选定的客户端打印机或准备进行预览。
Microsoft Web 报表查看器
当在 Internet Explorer 中使用报表查看器渲染 SSRS 报表时,您会看到一个小打印按钮。按下打印按钮将显示一个打印对话框和一个打印预览功能。此打印对话框是 Microsoft ActiveX 控件,称为 RsClientPrint
。
报表查看器有趣之处在于,如果您查看 Internet Explorer 的页面源代码,您不会找到任何引用 RsClientPrint
的代码。
Web 报表查看器如何打印?
报表查看器使用 IFRAM
对象在按下打印按钮时加载打印代码。此 IFRAME
对象具有 display: none 的样式。这样,IFRAM
将永远不会在客户端浏览器中可见。
首次在报表查看器中渲染报表时,IFRAM
的 src 属性为空。但打印报表时,src 属性将指向另一个包含打印所需报表所需 HTML 代码的临时页面。
下面是报表查看器生成并由隐藏的打印 IFRAME
使用的示例文本代码。
<HTML>
<BODY onload="Print()">
<OBJECT ID="RSClientPrint" CLASSID="CLSID:FA91DF8D-53AB-455D-AB20-F2F023E498D3"
CODEBASE="/Reports/Reserved.ReportViewerWebControl.axd?
ReportSession=4nfhfoj4e20k4v55jrw3ijnr&ControlID=
aa4dfda2cb2b48388a2956ca7db7d995&Culture=1033&
UICulture=9&ReportStack=1&OpType=PrintCab#Version=2005,090,3042,00"
VIEWASTEXT></OBJECT>
<script language="javascript">
function Print()
{
if (typeof RSClientPrint.Print == "undefined")
{
alert("Unable to load client print control.");
return;
}
RSClientPrint.MarginLeft = 9.906;
RSClientPrint.MarginTop = 9.906;
RSClientPrint.MarginRight = 9.906;
RSClientPrint.MarginBottom = 9.906;
RSClientPrint.PageHeight = 296.926;
RSClientPrint.PageWidth = 230.124;
RSClientPrint.Culture = 1033;
RSClientPrint.UICulture = 9;
RSClientPrint.UseEmfPlus = true;
RSClientPrint.Print("/Reports/Reserved.ReportViewerWebControl.axd",
"ReportSession=4nfhfoj4e20k4v55jrw3ijnr&ControlID=aa4dfda2cb2b48388a2956ca7db7d995&
Culture=1033&UICulture=9&ReportStack=1&OpType=PrintRequest","Cashier
Balance")
}
</script>
</BODY>
</HTML>
从上面的 HTML 代码来看,这是一个完整的 HTML 页面标签,包括以下内容:
- HTML 页面标签(HTML 和 Body 标签)
RsClientPrint
对象声明- JavaScript 打印方法
Body
属性,在页面加载时调用print
方法
这里有趣的是,如果我们查看传递给 RsClientPrint Print
方法的参数,我们将找不到远程报表服务的 URL?我们将找到报表查看器 HTTP 处理程序的 URL。当报表查看器放置在 ASP.NET 窗体中时,此 HTTP 处理程序会自动注册在 Web 应用程序的 web.config 文件中。
为什么报表查看器使用此 HTTP 处理程序而不是报表服务器 URL?答案是为了避免在使用 RsClientPrint
打印报表时进行身份验证。
要理解原因,我们需要了解更多关于报表服务的身份验证模式,如下所示:
报表服务默认由 IIS 托管,并使用 Windows 集成安全机制来保护对报表服务 Web 服务的连接。Windows 集成安全机制将向报表服务提供有关调用者的信息,并仅为特权项授权他。
此外,报表服务使用集成的 Windows 帐户来指定对不同报表服务文件夹、报表和数据源定义的访问权限。
因此,当用户尝试在默认配置下连接报表服务时,IIS 将用户的 Windows 帐户传递给报表服务,报表服务进而使用该帐户根据其分配的权限授权用户与报表服务项进行交互。
如果您的自定义 Web 应用程序发布到 Internet,或者您无法为所有用户授予报表服务项的特权,则此默认配置将不起作用。
授权所有用户的简单解决方案是执行以下操作:
- 将运行 IIS 的 ASP.NET 应用程序的用户添加到报表服务的授权用户列表,并为此用户分配所需的权限。您可以使用 SSRS 报表管理器来完成此操作。
- 为此用户分配所需的权限。您可以使用 SSRS 报表管理器来完成此操作。
对于 IIS 5.1,这将是 ASPNET 用户。一些开发人员喜欢使用自定义 Windows 用户来个性化 ASP.NET 应用程序。在这种情况下,他们会将此用户添加到报表服务而不是默认的 ASPNET 用户。
对于 IIS 6.0 和 7.0,这将是 IIS 应用程序池运行的身份。我无法在此深入探讨应用程序池。您可以阅读有关应用程序池的更多信息,请访问 IIS 网站。
这样做将使 ASP.NET 应用程序能够使用报表查看器控件,通过应用程序进程身份向用户呈现报表,以授权对报表服务的请求。
以上冗长的解释说明了为什么报表查看器控件使用 HTTP 处理程序来打印报表。
如果报表查看器传递报表服务器 URL 而不是使用 HTTP 处理程序,用户将需要输入 Windows 帐户用户名和密码才能访问报表服务。因为它是在 ASP.NET 应用程序上下文之外进行打印,所以需要这样做。
上述情况会引起问题,如果:
- 应用程序通过 Internet 启用,并且用户没有定义 Windows 帐户(表单身份验证)。
- 用户未在报表服务中获得授权,并且在使用 ASP.NET Web 应用程序时期望获得打印功能。
因此,报表查看器指示 RsClientPrint
与其 HTTP 处理程序通信,以接受其渲染请求。HTTP 处理程序将使用 ASP.NET 应用程序凭据上下文与报表服务进行通信,使用直接 URL 访问。报表服务将接受来自应用程序的连接,因为它正在以授权的用户身份运行。报表服务将渲染请求的报表,并将 EMF 流返回给应用程序。反过来,应用程序会将这些流转发给 RsClientPrint
控件。
注意:以上信息是通过观察得出的;我没有找到任何正式的参考资料。
实现 RsClientPrint 处理程序
从上面的描述可以看出,如果我们有一个 HTTP 处理程序,它可以接受 RsClientPrint
请求并在当前 ASP.NET 应用程序的上下文中渲染所需的报表,那么我们就可以在不需要与报表服务进行身份验证的情况下打印报表。
解决方案可以是一个 HTTP 处理程序或一个简单的 ASP.NET 网页。我考虑过,因为这篇文章已经很长了,所以为了简单起见,我将使用网页而不是 HTTP 处理程序。
让我们开始创建一个名为 PrintReport.aspx 的网页。这个网页将接受来自 RsClientPrint
控件的请求,并将渲染的 EMF 流发回。
以下是 PrintReport.aspx 的代码:
protected void Page_Load()
{
if(Request.QueryStrings.Count > 0)//this is a request from the RsClientPrint control
{
string requestUri = string.Format("http://{0}/reportserver?{1}",_serverName,
Request.QueryStrings.ToString());
WebRequest request = WebRequest.Create(requestUri);
CookieContainer cookies = null;
if(Session[“PrintReport.CookieContainer”] != null)
{
cookies = Session[“PrintReport.CookieContainer”] as CookieContainer;
}
else
{
cookies = new CookieContainer;
Session[“PrintReport.CookieContainer”] = cookies;
}
(request as HttpWebRequest).CookieContainer = cookies;
request.UseDefaultCredentials = true;
try
{
using (WebResponse rsResponse = request.GetResponse())
{
Stream s = rsResponse.GetResponseStream();
Response.Clear();
Response.BufferOutput = true;
BinaryReader reader = new BinaryReader(s);
byte[] buf = new byte[256];
int count = 0;
do
{
count = reader.Read(buf,0,256);
if(count > 0)
{
Response.OutputStream.Write(buf,0,count);
}
else
{
Response.End();
return;
}
}while(count > 0);
Response.Flust();
Response.End();
}
}
catch
{
//Report error
}
}
在上面的代码中,RsClientPrint
控件将请求发送到 PrintReport.aspx 页面。该页面将通过使用当前的 ASP.NET 凭据将其转发到报表服务器来处理请求。当从报表服务器收到响应时,页面将此响应转发给 RsClientPrint
控件。
RsClientPrint
将继续向 PrintReport.aspx 页面发送请求,直到收到空响应。只有当当前报表服务器会话中没有更多要渲染的报表页面时,才会收到空响应。
报表服务器通过传入请求中的请求 cookie 容器来标识会话。
嵌入 IFRAME 对象
在您要打印的页面中,您可以嵌入 IFRAM
对象,方法是在您的网页中插入以下代码片段:
<asp:Literal ID=”IFrameLiteral” runat=”server” Text=””/>
此 IFRAME
对象的 src 属性指向包含启动 RsClientPrint
控件 Print
方法所需代码的页面。
这里的诀窍是如何生成这段动态代码?
我们可以使用 PrintReport.aspx 网页生成这段动态代码,如下所示:
- 将格式化的
string
放在一个文本文件中。将此文件命名为 PrintScript.txt。 - 将以下文本粘贴到文件中:
<HTML> <BODY onload="Print()"> <OBJECT ID="RSClientPrint" CLASSID="CLSID:FA91DF8D-53AB-455D-AB20-F2F023E498D3" CODEBASE="RsClientPrint.cab" VIEWASTEXT></OBJECT> <script language="javascript"> function Print() [ if (typeof RSClientPrint.Print == "undefined") [ alert("Unable to load client print control."); return; ] RSClientPrint.MarginLeft = {0}; RSClientPrint.MarginTop = {1}; RSClientPrint.MarginRight = {2}; RSClientPrint.MarginBottom = {3}; RSClientPrint.PageHeight = {4}; RSClientPrint.PageWidth = {5}; RSClientPrint.Culture = 1033; RSClientPrint.UICulture = 9; RSClientPrint.UseEmfPlus = true; RSClientPrint.Print("{6}", "{7}", "{8}") ] </script> </BODY> </HTML>
- 当用户想要打印报表时,PrintScript.txt 中的文本可以按如下方式格式化:
string frameSrc = File.Load(Request.GetApplicationPath() + @”PrintScript.txt”); frameSrc = string.Format(frameSrc,marginLeft,marginTop,marginRight, marginBottom,”/PrintReport.aspx”,Report Path and parameters ,”Report Name”); frameSrc = frameSrc.Replace(‘[‘,’{‘); frameSrc = frameSrc.Replace(‘]‘,’}‘);
- 将创建的文本存储在会话变量中
Session[“FrameSource”] = frameSrc; IFrameLiteral.Text = “<IFRAME id=”PrintIFrame” src=”PrintReport.aspx”/>
- 加载页面时,
IFRAM
将加载 PrintReport.aspx 页面。PrinReport.aspx 页面将拦截调用并发送响应,如以下代码所示:protected void Page_Load() { if(Request.QueryStrings.Count == 0) { Response.Clear(); if(Session[“FrameSource”] != null) { Response.Write(Session[“FrameSource”].ToString()); } Response.End(); } }
从上面的代码可以看出,响应将返回
FrameSource
会话变量的内容到IFRAME
。这将包含一个完整的 HTML 代码,用于调用RsClientPrint
对象打印方法。 - 以下是结果
IFRAME
HTML 代码的示例:<HTML> <BODY onload="Print()"> <OBJECT ID="RSClientPrint" CLASSID="CLSID:FA91DF8D-53AB-455D-AB20-F2F023E498D3" CODEBASE="RsClientPrint.cab" VIEWASTEXT></OBJECT> <script language="javascript"> function Print() { if (typeof RSClientPrint.Print == "undefined") { alert("Unable to load client print control."); return; } RSClientPrint.MarginLeft= 10; RSClientPrint.MarginTop= 10; RSClientPrint.MarginRight= 10; RSClientPrint.MarginBottom= 10; RSClientPrint.PageHeight= 297; RSClientPrint.PageWidth= 210; RSClientPrint.Culture= 1033; RSClientPrint.UICulture= 9; RSClientPrint.UseEmfPlus= true; RSClientPrint.Print(https:///PrintReport.aspx, "/Reports/FinancialReport&Year=2008”, "Financial Report") } </script> </BODY> </HTML>
RsClientPrint
控件将如上所示与打印页面通信,并且RsClientPrint
控件打印对话框将出现。因此,场景如下:
- 用户点击 ASP.NET 打印按钮。
- 将触发打印按钮的
OnClick
事件。 - 在
OnClick
事件处理程序中,用户构建IFRAME
源string
并将其存储在会话变量中。 IFRAM
尝试加载 PrintReport.aspx 页面。- PrintReport.aspx 页面发送适当的响应。
IFRAM
JavaScript 中声明的RsClientPrint
控件加载打印对话框。RsClientPrint
控件与 PrintReport.aspx 通信以获取渲染报表的流。RsClientPrint
控件打印接收到的 EMF。
历史
这是本文的第三版,我将定期更新本文,以添加缺失的代码并改进其内容。