Web项目中使用RDLC报表可以打印吗?

   不断地有人在我的Blog上提出这样的问题——为什么在Web窗体中使用.rdlc报表时,ReportViewer控件上没有“打印”按钮?在WEB窗体的设计状态,默认情况下,ReportViewer控件上是没有“打印”按钮的;如果在“ReportViewer 任务”窗口中选择一个客户端报表(一个.rdlc文件),也是没有“打印”按钮的;如果在“ReportViewer 任务”窗口中选择的是服务器端报表,“打印”按钮就出现了(如图1所示)。


图1 “打印”按钮的出现与隐藏

   这个问题涉及到服务器端报表(对应RDL)和客户端报表(对应RDLC)的应用情境,对此,MSDN是这样解释的——

何时使用远程处理

基于服务器的报表功能为实现下列任务提供了方法:集中存储和管理报表、设置策略和确保对报表及文件夹的安全访问、控制处理和分布报表的方式,以及设置在业务中使用报表的标准方式。Reporting Services 可以以单服务器、分布式和群集配置的方式进行安装。如果报表具有下列特征,请考虑使用远程处理:

  • 报表将被许多用户访问。
  • 报表有一个非常复杂的查询或包含大量数据,从而导致应用程序所在的计算机上的系统资源超载。
  • 报表已发布在报表服务器上,并且希望将其包含在所创建和部署的应用程序中。

——将 ReportViewer 配置为进行远程处理



何时使用本地处理

建议对于包括中小型号报表和数据集的应用程序使用本地处理模式。由于所有数据和报表的处理都是在客户端进行的,因此,如果您试图处理大型或复杂的报表和查询,性能可能会降低。如果您需要简单的部署策略,其中应用程序的所有部分都在同一台计算机上一起运行,也建议使用本地处理模式。本地处理模式的功能不及远程处理强大,它适用于不需要报表服务器的独立应用程序。熟悉在远程 SQL Server Reporting Services 报表服务器上运行的服务器报表的用户应注意以下特别之处:

  • 客户端报表定义 (.rdlc) 中的报表参数不映射到查询参数。客户端报表定义中没有参数输入区域,它接受随后在查询中使用的值。
  • 客户端报表定义不包含嵌入式查询信息。您必须定义返回可供报表使用的数据的数据源。
  • 通过 RSClientPrint ActiveX 控件执行的基于浏览器的打印不适用于 ReportViewer Web 服务器控件中运行的客户端报表定义。打印控件是报表服务器功能集的一部分。

——将 ReportViewer 配置为进行本地处理

    回到本随笔开头的问题,上面红色黑体的部分已经说明白了,Web窗体中的ReportViewer控件使用LocalReport是不能使用打印按钮进行打印的。

   在没有看到上面引用的两段来自MSDN的描述时,我还进行了一些尝试,结果当然是不成功的,但是还是记录下来这个过程:

   为了对比在Web项目中使用LocalReport和ServerReport的不同,我在Web项目中新建了两个页面wfLocalReport.aspx和wfServerReport.aspx,在这两个页面上分别放置一个ReportViewer控件,并在“ReportViewer 任务”中分别指定一个客户端报表和一个服务器端报表。我们已经知道,无论在设计时还是在运行时,前者是没有打印按钮的,而后者是有的。前者的打印按钮是不是隐藏了?如果是,可以通过某种方法把它显示出来吗?

   对比一下ReportViewer控件的属性,发现除了LocalReport和ServerReport两个属性设置不同之外并无其它不同之处,而且两者的ShowPrintButton属性均已默认设置为True。

   运行这两个页面,在浏览器中右键“查看源文件”,这时候我们会发现,返回到客户端的HTML确实是不同的,前者根本就没有表示打印按钮的HTML代码出现,而后者就有——

代码1:wfServerReport.aspx向客户端发送的HTML文件中的“打印”按钮

   在上面的代码中,LoadPrintControl()方法从何而来呢?使用路径http://localhost:2167/LRPrintInWA/Reserved.ReportViewerWebControl.axd?OpType=Resource&Version=8.0.50727.42&Name=Scripts.ReportViewer.js可以得到一个名为Scripts.ReportViewer.js的.js文件,LoadPrintControl()方法就定义在该文件中——

代码2:LoadPrintControl方法

   另外,我们在该.js文件中还可以发现一个名为RSToolbar的函数——

代码3:RSToolbar构造函数

   这显然是ReportViewer的工具栏的构造函数,而我们对比wfLocalReport.aspx和wfServerReport.aspx两个页面返回的HTML文件,可以发现,它们的构造是不同的——

代码4:wfLocalReport.aspx向客户端发送的HTML文件中ReportViewer控件的工具栏的构造

代码5:wfServerReport.aspx向客户端发送的HTML文件中ReportViewer控件的工具栏的构造

   仔细对比代码4和代码5,可以发现代码4中倒数第四个参数为空字符串,那么,我们有理由怀疑代码5中的倒数第四个参数,运行Web项目进入页面wfServerReport.aspx,查看源文件,取出这个参数的字符串值将其附加到地址栏中(类似这样的一个URL:http://localhost:2167/LRPrintInWA/Reserved.ReportViewerWebControl.axd?ReportSession=5z3m52ucuk4avi55xh0yx0jr&ControlID=6bea4929-cf7e-4ed6-bb88-360e33f26d41&Culture=2052&UICulture=2052&ReportStack=1&OpType=PrintHtml),回车,哈哈,我们看到了什么?


图2 通过一个URL调出的打印窗口(点击小图看大图)

   对照LoadPrintControl方法来看,之所以页面中会生成iframe,是为了保持页面的状态,而只需更改该iframe的location就可以调出该打印窗口。同样的,报表的其它操作也是通过这种方法来实现的。

   那么,我们就此是否就可以有很多想法了?比如,在wfLocalReport.aspx中自己手动增加一个按钮也按照上面的方法进行报表的打印?

   这就不好说了,说不定一不留神,这个问题解决了,呵呵,那样的话,基本上就可以用RDLC代替Reporting Services了,我们似乎也就不用买SSRS的license了,嘿嘿,妄想中……

   现在,我们需要分析一下这个URL——http://localhost:2167/LRPrintInWA/Reserved.ReportViewerWebControl.axd?ReportSession=5z3m52ucuk4avi55xh0yx0jr&ControlID=6bea4929-cf7e-4ed6-bb88-360e33f26d41&Culture=2052&UICulture=2052&ReportStack=1&OpType=PrintHtml。

   Reserved.ReportViewerWebControl.axd是什么?恩,这是个问题,并不存在这个物理文件,MSDN上也没有说明,但是在Web.Config中我们可以看到以下代码——

代码6:Web.Config中的Reserved.ReportViewerWebControl.axd

   也就是说,其实我们并不需要关心Reserved.ReportViewerWebControl.axd是什么,遇到客户端请求这个文件,没关系,ASP .NET引擎会搞定它。

   接下来看上述URL的参数。如果我们能够知道各个参数的来源以及在程序中取得参数值的方法,那么我们也可以使用类似上面的一个URL来进行报表的打印了。恩,我们的目标是知道在使用RDLC报表的情境下这些参数的值,但是在此之前,我们需要在ServerReport情境下找到这些参数的来源,然后对应看在RDLC报表情境下是否可以取得这些参数的值,如果对应的结果是肯定的,那么,我们也就可以实现客户端报表的打印了。

   首先,参数Culture、UICulture、ReportStack和OpType的取值应该是确定的,虽然可能并不是非常清楚这些参数的含义,但不妨猜测一下:Culture和UICulture不用说了吧,从其取值2052(简体中文)就可以知道大差不离了;ReportStack,估计是和堆栈是有关的,看其取值,估计1应该是一种方式或属性等的代码;而OpType应该是操作类型,取值PrintHTML就是我们要做的事情嘛。

   而对于参数ControlID来说,是ReportViewer控件的标识符,好像Microsoft并没有公开获取该值的方法(也许是我没找到。:)),不过ReportViewer控件的非公有成员m_instanceIdentifier的取值就是这个ControlID(如图3所示)。


图3 ReportViewer控件的非公有成员m_instanceIdentifier

   不过,这个ControlID我们是不用发愁的,我们在Web窗体发送到客户端的HTML文件中是可以找到的,缩放等报表操作已经提供了这个值,取过来就OK了。

   最后就剩一个ReportSession了,ReportSession是什么呢?怎样得到ReportSession的值呢?使用和参数ControlID一样的方法从客户端HTML文件中取?

   事实上,参数ControlID在wbLocalReport.aspx和wbServerReport.aspx这两个Web窗体发送到客户端的HTML文件中都出现了多次,参数ReportSession在wbServerReport.aspx发送到客户端的HTML文件中也出现了多次,而在wbLocalReport.aspx发送到客户端的HTML文件中并没有出现。

   既然参数的名称是ReportSession,那么会不会和Session有关呢?在页面中监视一下this.Session,发现this.Session[0]是一个Microsoft.Reporting.WebForms.ReportInfo结构,也可以在改结构中发现一个非公有成员m_executionID(如图4所示),其值和参数ReportSession的取值相同。


图4 ReportViewer控件的非公有成员m_executionID

   而且,Microsoft也实现了一个方法用于获取该值:this.ReportViewer1.ServerReport.GetExecutionId()。

   上面描述的两个参数是ServerReport才具备的特征,那么LocalReport呢?参数ControlID是OK的,而参数ReportSession在LocalReport中是无法取到的,这是非常遗憾的地方。而上述URL的ReportSession参数是无法省略或随便赋值的(会出现“ASP.NET 会话已过期”错误),这也就是说,我们的努力已经宣告落空了…………

   本来和一个朋友说昨天晚上就发这篇随笔的,到现在才发出来,抱歉!另外,这篇随笔其实并没有意义,只是对在Web项目中使用RDLC报表时如何打印的尝试,而且是一个失败的尝试。希望后来的朋友不再使用RDLC在Web项目中做报表,除非不需要使用打印功能,在这个意义上,本随笔可以算作是对这个结论的一个证明。

   
 

posted @ 2006-09-13 00:39  蜡人张  阅读(30037)  评论(84编辑  收藏