来源:http://www.cnblogs.com/waxdoll/archive/2006/09/13/rdlc-report-in-web-application.html 不断地有人在我的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文件中的“打印”按钮
< input type ="image" name ="ReportViewer1$ctl01$ctl07$ctl00$ctl00$ctl00" src ="/LRPrintInWA/Reserved.ReportViewerWebControl.axd?OpType=Resource&Version=8.0.50727.42&Name=Icons.Print.gif" onclick ="ClientToolbarReportViewer1_ctl01.LoadPrintControl();return false;" style ="height:16px;width:16px;border-width:0px;padding:2px;" />
在上面的代码中,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方法
function LoadPrintControl() { var printFrame = GetControl( this .m_printFrame); if (printFrame != null ) { if (printFrame.src != this .m_printHtmlLink) { window.frames[ this .m_printFrame].window.location.replace( this .m_printHtmlLink); } else eval(this .m_printFrame + " .Print(); " ); } return false ; } RSToolbar.prototype.LoadPrintControl = LoadPrintControl;
另外,我们在该.js文件中还可以发现一个名为RSToolbar的函数——
代码3:RSToolbar构造函数
// Toolbar class constructor function RSToolbar(clientController, currentPageID, totalPagesID, firstEnableMethod, previousEnableMethod, nextEnableMethod, lastEnableMethod, zoomID, findTextID, findController, findNextController, formatsID, exportController, parametersRowID, docMapController, docMapGroupID, docMapVisibilityStateID, printHtmlLink, printFrame, exportUrlBase, enablePrintMethod) { this .m_clientController = clientController; this .m_currentPageID = currentPageID; this .m_totalPagesID = totalPagesID; this .m_zoomID = zoomID; this .m_findTextID = findTextID; this .m_findController = findController; this .m_findNextController = findNextController; this .m_formatsID = formatsID; this .m_exportController = exportController; this .m_parametersRowID = parametersRowID; this .m_docMapController = docMapController; this .m_docMapGroupID = docMapGroupID; this .m_docMapVisibilityStateID = docMapVisibilityStateID; this .m_printHtmlLink = printHtmlLink; this .m_printFrame = printFrame; this .m_exportUrlBase = exportUrlBase; this .m_enablePrintMethod = enablePrintMethod; // Hook up methods this .EnableFirstNav = firstEnableMethod; this .EnablePreviousNav = previousEnableMethod; this .EnableNextNav = nextEnableMethod; this .EnableLastNav = lastEnableMethod; this .m_nextHit = 0 ; this .m_clientController.SetToolBar( this ); }
这显然是ReportViewer的工具栏的构造函数,而我们对比wfLocalReport.aspx和wfServerReport.aspx两个页面返回的HTML文件,可以发现,它们的构造是不同的——
代码4:wfLocalReport.aspx向客户端发送的HTML文件中ReportViewer控件的工具栏的构造
var ClientToolbarReportViewer1_ctl01 = new RSToolbar(ClientControllerReportViewer1, " ReportViewer1_ctl01_ctl01_ctl02 " , " ReportViewer1_ctl01_ctl01_ctl04 " , ClientImageToggleReportViewer1_ctl01_ctl01_ctl00, ClientImageToggleReportViewer1_ctl01_ctl01_ctl01, ClientImageToggleReportViewer1_ctl01_ctl01_ctl05, ClientImageToggleReportViewer1_ctl01_ctl01_ctl06, " ReportViewer1_ctl01_ctl03_ctl00 " , " ReportViewer1_ctl01_ctl04_ctl00 " , TextLinkReportViewer1_ctl01_ctl04_ctl01, TextLinkReportViewer1_ctl01_ctl04_ctl03, " ReportViewer1_ctl01_ctl05_ctl00 " , TextLinkReportViewer1_ctl01_ctl05_ctl01, " ReportViewer1_ctl00 " , HoverImageReportViewer1_ctl01_ctl00_ctl00, " ReportViewer1_ctl01_ctl00 " , " ReportViewer1_ctl01_ctl00_ctl01 " , "" , " PrintFrameReportViewer1_ctl01_ctl07 " , " /LRPrintInWA/Reserved.ReportViewerWebControl.axd?Mode=true&ReportID=6e0a5ce971814c529735c9c1bb3c1edf&ControlID=cfa12ca1-06c0-4d45-9c83-a41706b9b141&Culture=2052&UICulture=2052&ReportStack=1&OpType=Export&FileName=Report&ContentDisposition=OnlyHtmlInline&Format= " , ClientImageToggleReportViewer1_ctl01_ctl07_ctl00);
代码5:wfServerReport.aspx向客户端发送的HTML文件中ReportViewer控件的工具栏的构造
var ClientToolbarReportViewer1_ctl01 = new RSToolbar(ClientControllerReportViewer1, " ReportViewer1_ctl01_ctl01_ctl02 " , " ReportViewer1_ctl01_ctl01_ctl04 " , ClientImageToggleReportViewer1_ctl01_ctl01_ctl00, ClientImageToggleReportViewer1_ctl01_ctl01_ctl01, ClientImageToggleReportViewer1_ctl01_ctl01_ctl05, ClientImageToggleReportViewer1_ctl01_ctl01_ctl06, " ReportViewer1_ctl01_ctl03_ctl00 " , " ReportViewer1_ctl01_ctl04_ctl00 " , TextLinkReportViewer1_ctl01_ctl04_ctl01, TextLinkReportViewer1_ctl01_ctl04_ctl03, " ReportViewer1_ctl01_ctl05_ctl00 " , TextLinkReportViewer1_ctl01_ctl05_ctl01, " ReportViewer1_ctl00 " , HoverImageReportViewer1_ctl01_ctl00_ctl00, " ReportViewer1_ctl01_ctl00 " , " ReportViewer1_ctl01_ctl00_ctl01 " , " /LRPrintInWA/Reserved.ReportViewerWebControl.axd?ReportSession=zrve2z55iaj3ttrfkd2owrfq&ControlID=6f50f184-8cd0-4aa2-9caa-3000112b1e09&Culture=2052&UICulture=2052&ReportStack=1&OpType=PrintHtml " , " PrintFrameReportViewer1_ctl01_ctl07 " , " /LRPrintInWA/Reserved.ReportViewerWebControl.axd?ReportSession=zrve2z55iaj3ttrfkd2owrfq&ControlID=6f50f184-8cd0-4aa2-9caa-3000112b1e09&Culture=2052&UICulture=2052&ReportStack=1&OpType=Export&FileName=Report1&ContentDisposition=OnlyHtmlInline&Format= " , ClientImageToggleReportViewer1_ctl01_ctl07_ctl00);
仔细对比代码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
< 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 >
也就是说,其实我们并不需要关心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项目中做报表,除非不需要使用打印功能,在这个意义上,本随笔可以算作是对这个结论的一个证明。