这篇随笔其实并没有意义,只是对在Web项目中使用RDLC报表时如何打印的尝试,而且是一个失败的尝试。希望后来的朋友不再使用RDLC在Web项目中做报表,除非不需要使用打印功能,在这个意义上,本随笔可以算作是对这个结论的一个证明。
不断地有人在我的Blog上提出这样的问题——为什么在Web窗体中使用.rdlc报表时,ReportViewer控件上没有“打印”按钮?在WEB窗体的设计状态,默认情况下,ReportViewer控件上是没有“打印”按钮的;如果在“ReportViewer 任务”窗口中选择一个客户端报表(一个.rdlc文件),也是没有“打印”按钮的;如果在“ReportViewer 任务”窗口中选择的是服务器端报表,“打印”按钮就出现了(如图1所示)。
图1 “打印”按钮的出现与隐藏
这个问题涉及到服务器端报表(对应RDL)和客户端报表(对应RDLC)的应用情境,对此,MSDN是这样解释的——
回到本随笔开头的问题,上面红色黑体的部分已经说明白了,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项目中做报表,除非不需要使用打印功能,在这个意义上,本随笔可以算作是对这个结论的一个证明。