使用RSClientPrint直接打印本地RDLC报表
最近一个项目使用ReportViewer来呈现本地RDLC模块的报表,需要用户点击至少三次才能直正打印,用户感觉易用性很不好,需要我们修改。
经过网上查找相关资料,发现直接使用ACTIVEX控件RSClientPrint直接打印使用SQLSERVER报表服务的资料很多,也说的比较详细,可唯独没打印本地报表的相关内容,看来只能自已摸索了。
经过研究有关打印SQLSERVER报表服务的资料,特别是这篇文章:http://www.codeproject.com/KB/reporting-services/RsClientPrint.aspx,决下先写一个简单的HTML文 件测试一下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<BODY onload="Print()">
<OBJECT ID="RSClientPrint"
classid="CLSID:5554DCB0-700B-498D-9B58-4E40E5814405" CODEBASE="/RSClientPrint-x86.cab" VIEWASTEXT></OBJECT>
<script type="text/javascript" language="javascript">
function Print(oid,name) {
if (typeof RSClientPrint.Print == "undefined") {
alert("Unable to load client print control.");
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>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<BODY onload="Print()">
<OBJECT ID="RSClientPrint"
classid="CLSID:5554DCB0-700B-498D-9B58-4E40E5814405" CODEBASE="/RSClientPrint-x86.cab" VIEWASTEXT></OBJECT>
<script type="text/javascript" language="javascript">
function Print(oid,name) {
if (typeof RSClientPrint.Print == "undefined") {
alert("Unable to load client print control.");
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

public class ReportHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
Warning[] warningArray;
HttpResponse response = context.Response;
response.StatusCode = 200;
MemoryStream lastMemoryStream = null;
context.Response.BufferOutput = false;
context.Response.ContentType = null;
context.Response.Expires = -1;
var ds = new DataSet();
LocalReport localReport = new LocalReport();
localReport.ReportEmbeddedResource = string.Format("{0}.rdlc", context.Request.QueryString["name"]);
localReport.DataSources.Add(new Microsoft.Reporting.WebForms.ReportDataSource("ds", ds));
StringBuilder builder = new StringBuilder("<DeviceInfo>");
NameValueCollection requestParameters = context.Request.QueryString ;
for (int i = 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(string name, string extension, Encoding encoding, string mimeType, bool willSeek)
{
if (!HttpContext.Current.Response.IsClientConnected)
{
throw new HttpException("Client disconnected");
}
if (lastMemoryStream != null)
{
this.SendPrintStream(lastMemoryStream, response);
lastMemoryStream.Dispose();
lastMemoryStream = null;
}
lastMemoryStream = new MemoryStream();
return lastMemoryStream;
}, out warningArray);
this.SendPrintStream(lastMemoryStream, response);
lastMemoryStream.Dispose();
this.SendPrintStream(null, response);
if (!response.BufferOutput)
{
string a = context.Request.ServerVariables["SERVER_PROTOCOL"];
if (string.Equals(a, "HTTP/1.0", StringComparison.OrdinalIgnoreCase))
{
context.Response.Close();
}
}
}
private void SendPrintStream(Stream stream, HttpResponse response)
{
int length = 0;
if (stream != null)
{
length = (int)stream.Length;
}
foreach (byte num2 in BitConverter.GetBytes(length))
{
response.OutputStream.WriteByte(num2);
}
if (stream != null)
{
stream.Position = 0L;
StreamToResponse(stream, response);
response.Flush();
}
}
internal static void StreamToResponse(Stream data, HttpResponse response)
{
int count = 0;
byte[] buffer = new byte[0x14000];
while ((count = data.Read(buffer, 0, 0x14000)) > 0)
{
response.OutputStream.Write(buffer, 0, count);
}
}
public bool IsReusable
{
get
{
return true;
}
}
}
{
public void ProcessRequest(HttpContext context)
{
Warning[] warningArray;
HttpResponse response = context.Response;
response.StatusCode = 200;
MemoryStream lastMemoryStream = null;
context.Response.BufferOutput = false;
context.Response.ContentType = null;
context.Response.Expires = -1;
var ds = new DataSet();
LocalReport localReport = new LocalReport();
localReport.ReportEmbeddedResource = string.Format("{0}.rdlc", context.Request.QueryString["name"]);
localReport.DataSources.Add(new Microsoft.Reporting.WebForms.ReportDataSource("ds", ds));
StringBuilder builder = new StringBuilder("<DeviceInfo>");
NameValueCollection requestParameters = context.Request.QueryString ;
for (int i = 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(string name, string extension, Encoding encoding, string mimeType, bool willSeek)
{
if (!HttpContext.Current.Response.IsClientConnected)
{
throw new HttpException("Client disconnected");
}
if (lastMemoryStream != null)
{
this.SendPrintStream(lastMemoryStream, response);
lastMemoryStream.Dispose();
lastMemoryStream = null;
}
lastMemoryStream = new MemoryStream();
return lastMemoryStream;
}, out warningArray);
this.SendPrintStream(lastMemoryStream, response);
lastMemoryStream.Dispose();
this.SendPrintStream(null, response);
if (!response.BufferOutput)
{
string a = context.Request.ServerVariables["SERVER_PROTOCOL"];
if (string.Equals(a, "HTTP/1.0", StringComparison.OrdinalIgnoreCase))
{
context.Response.Close();
}
}
}
private void SendPrintStream(Stream stream, HttpResponse response)
{
int length = 0;
if (stream != null)
{
length = (int)stream.Length;
}
foreach (byte num2 in BitConverter.GetBytes(length))
{
response.OutputStream.WriteByte(num2);
}
if (stream != null)
{
stream.Position = 0L;
StreamToResponse(stream, response);
response.Flush();
}
}
internal static void StreamToResponse(Stream data, HttpResponse response)
{
int count = 0;
byte[] buffer = new byte[0x14000];
while ((count = data.Read(buffer, 0, 0x14000)) > 0)
{
response.OutputStream.Write(buffer, 0, count);
}
}
public bool IsReusable
{
get
{
return true;
}
}
}
再后经测试,直接加载上面的HTML页面就会提示直接打印。整个过程花了不少时间,有几个地方需要注意:
1.在服务器端生成DEVICEINFO时一定要使用上面所示的方法,我在测试过程中为了图简单,直接使用我原来在WINFORM写的一个代码,固定的DEVICEINFO信息,结果打印出来时始终会重复,不知道是怎么回事,经过使用Reflector反编译Microsoft.ReportViewer.WebForms,仔细这里面实现的报表相关服务,发现这点实现的区别,才最终搞定问题
2.服务器代码生成EMF流如果有问题,客户端会报一个错误代码,这时你很可能会以为是客户端的错误,我开始就是,因为报错误的速度太快了。我上网对对应问题代码的资料也没有明确结果,最后看还是直接跟踪代码试试,发现是服务器端代码有些问题。
分类:
找问题系列
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述