最近一个项目使用ReportViewer来呈现本地RDLC模块的报表,需要用户点击至少三次才能直正打印,用户感觉易用性很不好,需要我们修改。
经过网上查找相关资料,发现直接使用ACTIVEX控件RSClientPrint直接打印使用SQLSERVER报表服务的资料很多,也说的比较详细,可唯独没打印本地报表的相关内容,看来只能自已摸索了。
经过研究有关打印SQLSERVER报表服务的资料,特别是这篇文章:/KB/reporting-services/RsClientPrint.aspx,决下先写一个简单的HTML文 件测试一下:
代码 <!DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.0Transitional//EN""/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<htmlxmlns="/1999/xhtml">
<head>
<title></title>
</head>
<BODYonload="Print()">
<OBJECTID="RSClientPrint"
classid="CLSID:5554DCB0-700B-498D-9B58-4E40E5814405"CODEBASE="/RSClientPrint-x86.cab"VIEWASTEXT></OBJECT>
<scripttype="text/javascript"language="javascript">
functionPrint(oid,name){
if(typeofRSClientPrint.Print=="undefined"){
alert("Unabletoloadclientprintcontrol.");
return;
}
RSClientPrint.MarginLeft=0;
RSClientPrint.MarginTop=0;
RSClientPrint.MarginRight=0;
RSClientPrint.MarginBottom=0;
RSClientPrint.PageHeight=296.926;
RSClientPrint.PageWidth=210.058;
RSClientPrint.Culture=2052;
RSClientPrint.UICulture=2052;
RSClientPrint.UseSingleRequest=true;
RSClientPrint.UseEmfPlus=true;
RSClientPrint.Print("http://localhost:2940/JDCJY/ReportHandler.ashx","oid="+escape(oid)+"&name="+escape(name),"test");
}
</script>
</BODY>
</html>
然后实现ReportHandler.ashx
代码publicclassReportHandler:IHttpHandler
{
publicvoidProcessRequest(HttpContextcontext)
{
Warning[]warningArray;
HttpResponseresponse=context.Response;
response.StatusCode=200;
MemoryStreamlastMemoryStream=null;
context.Response.BufferOutput=false;
context.Response.ContentType=null;
context.Response.Expires=-1;
vards=newDataSet();
LocalReportlocalReport=newLocalReport();
localReport.ReportEmbeddedResource=string.Format("{0}.rdlc",context.Request.QueryString["name"]);
localReport.DataSources.Add(newMicrosoft.Reporting.WebForms.ReportDataSource("ds",ds));
StringBuilderbuilder=newStringBuilder("<DeviceInfo>");
NameValueCollectionrequestParameters=context.Request.QueryString;
for(inti=0;i<requestParameters.Count;i++)
{
if(requestParameters.Keys[i]!=null)
{
if(requestParameters.Keys[i].StartsWith("rc:",StringComparison.OrdinalIgnoreCase))
{
builder.AppendFormat("<{0}>{1}</{0}>",XmlConvert.EncodeName(requestParameters.Keys[i].Substring(3)),HttpUtility.HtmlEncode(requestParameters[i]));
}
}
}
builder.Append("</DeviceInfo>");
localReport.Render("IMAGE",builder.ToString(),delegate(stringname,stringextension,Encodingencoding,stringmimeType,boolwillSeek)
{
if(!HttpContext.Current.Response.IsClientConnected)
{
thrownewHttpException("Clientdisconnected");
}
if(lastMemoryStream!=null)
{
this.SendPrintStream(lastMemoryStream,response);
lastMemoryStream.Dispose();
lastMemoryStream=null;
}
lastMemoryStream=newMemoryStream();
returnlastMemoryStream;
},outwarningArray);
this.SendPrintStream(lastMemoryStream,response);
lastMemoryStream.Dispose();
this.SendPrintStream(null,response);
if(!response.BufferOutput)
{
stringa=context.Request.ServerVariables["SERVER_PROTOCOL"];
if(string.Equals(a,"HTTP/1.0",StringComparison.OrdinalIgnoreCase))
{
context.Response.Close();
}
}
}
privatevoidSendPrintStream(Streamstream,HttpResponseresponse)
{
intlength=0;
if(stream!=null)
{
length=(int)stream.Length;
}
foreach(bytenum2inBitConverter.GetBytes(length))
{
response.OutputStream.WriteByte(num2);
}
if(stream!=null)
{
stream.Position=0L;
StreamToResponse(stream,response);
response.Flush();
}
}
internalstaticvoidStreamToResponse(Streamdata,HttpResponseresponse)
{
intcount=0;
byte[]buffer=newbyte[0x14000];
while((count=data.Read(buffer,0,0x14000))>0)
{
response.OutputStream.Write(buffer,0,count);
}
}
publicboolIsReusable
{
get
{
returntrue;
}
}
}
再后经测试,直接加载上面的HTML页面就会提示直接打印。整个过程花了不少时间,有几个地方需要注意:
1.在服务器端生成DEVICEINFO时一定要使用上面所示的方法,我在测试过程中为了图简单,直接使用我原来在WINFORM写的一个代码,固定的DEVICEINFO信息,结果打印出来时始终会重复,不知道是怎么回事,经过使用Reflector反编译Microsoft.ReportViewer.WebForms,仔细这里面实现的报表相关服务,发现这点实现的区别,才最终搞定问题
2.服务器代码生成EMF流如果有问题,客户端会报一个错误代码,这时你很可能会以为是客户端的错误,我开始就是,因为报错误的速度太快了。我上网对对应问题代码的资料也没有明确结果,最后看还是直接跟踪代码试试,发现是服务器端代码有些问题。