2022最终版FastReport浏览器直接打印无须预览,之所以写这篇文章,是因为在两年前首发在 jhrs.com 的FastReport.net 打印功能这篇文章被很多网站转(偷)载,也是为了填一下2次封装FastReport.net 打印功能,Web项目网页支持直接打印这篇文章留下的坑,本文将会给出详细的代码以实现浏览器上并结合FastReport.Net实现无须预览直接打印功能。
无须预览直接打印也叫静默打印,即点一下按钮(当然你可以任意方式实现让打印机开始)打印机直接开始工作打印文件;在WinForm下很好实现,而web 静默打印一直是难点,且实现较为麻烦,这也是本文要解决的问题。
以下的文章是2年前和后来写的关于FastReport.Net的一些系列文章,原文还是值得翻出来看一下的,讲的是一些实现思路。本文会略过VS2022怎样打包安装程序,如果你是C#开发人员,会开发WinForm程序但不熟悉怎样打包安装程序,可以看看我之前写的WPF程序打包教程,它们是一个路数。
- 2次封装FastReport.net 打印功能,Web项目网页支持直接打印
- 自定义协议打开本地客户端程序遇上CSP内容安全策略
- VS2019打包WPF安装程序最新教程
源码解决方案说明
当你从github下载源码后,使用VS2022打开会看到如下的样式,如果你首次打开时JHRS.PrintClientSetup不能正常加载,说明你没有安装Installer Projects,参见VS2022 打不开vdproj项目解决办法,升级Installer Projects这篇文章解决即可。
web 打印控件封装源码解决方案
源码中提供了 2 个网站程序,分别是使用asp.net webform技术编写和asp.net core razor page编写,从命名上就已经区分了;JHRS.PrintClient是基于FastReport.net封装的客户端软件,引用了FastReport.net相关dll文件。
JHRS.PrintClientSetup是打包安装程序,为什么需要打包安装呢?那是因为在生产环境中你不可能将Debug或Release目录下的文件发给客户让他们使用,况且也使用不起来,而且还需要借助该打包程序往注册表写入自定义协议信息,具体在下面代码中会介绍。打包成安装程序后,即可在生产环境中的网页上提供下载功能,需要使用打印功能的客户端,只需要下载安装上该客户端即可,之后就可以打印了。
Github地址:https://github.com/jhrscom/JHRS-PrintClient
演示网站:https://fastreport.jhrs.com/
提示:如果打开Github有问题,你最好自备一个上网工具。你可以进入演示网站首先下载客户端程序安装上,之后再点击打印按钮即可。
网页上使用 FastReport.net 直接打印演示视频
你可以在YouTube上观看这个视频。如果不能打开这个视频,参见上面打开Github的方法。
https://youtu.be/1O0J08dx5LU
FastReport浏览器直接打印无须预览
我们在使用web技术开发管理系统,如医疗领域的HIS系统,企业的ERP系统,财务系统等等,都有打印需求,而正是各类系统和客户都有着各种不相同的打印需要,模板,格式,数据不一样等;市面上报表有多种产品,但我们选择了FastReport.Net做为解决打印需求的第三方组件,当然,商用它是付费的,不过对于公司来说,这都没有什么问题。
本文中所涉及到的FastReport.Net是使用的来自网上的古老的 2.0 版本,正式产品中使用,还是需要购买授权的。接下来就来说说怎样在web环境下结合FastReport.net 实现无预览直接打印功能,另外需要说明的是,我封装的客户端你可以自由的设置是否需要使用预览功能,客户端的功能点如下:
- 支持选择多打印机,并且可以设置默认打印机
- 支持无须预览直接打印,也可以启动客户端将预览功能开启。
- 跨浏览器支持,Chrome,IE,FireFox,Edge全都支持。
- 打印日志功能(保存在安装目录)
- 桌面快捷方式(一般无须打开,只有更改设置时才使用,如更换默认打印机,设置静默打印【无预览】)
要实现FastReport浏览器静默打印功能,我们需要做两步工作:
第一步是编写客户端程序,并且提供下载功能,用户首次使用时先安装客户端,首次点击打印会在浏览器上看到如下提示:
web 静默打印首次提示
第二步是web应用程序界面上提供打印功能和获取打印数据接口,具体的实现会在源码中展示。
编写客户端打印程序
客户端打印程序其实就是将FastReport打印组件封装进去,因此我们需要编写一个WinForm窗体程序,当然你也可以使用WPF来实现,在这个窗体程序上需要拖动一个 PreviewControl 控件到主窗体上面,该窗体程序非常简单,就一个FastReport.Preview.PreviewControl控件。当然我放在github上的源码你 clone下来后可以看到其它的,只是一些辅助功能而已。
封装的FastReport web打印控件窗体程序主界面
完整的源码如下:
using JHRS.PrintClient.Entity;
using JHRS.PrintClient.Extensions;
using System.Collections.Generic;
using System.Windows.Forms;
namespace JHRS.PrintClient
public partial class MainForm : Form
public string[] args { get; set; }
/// <param name="args"></param>
public MainForm(string[] args)
/// <param name="args"></param>
private void Print(string[] args)
var printConfig = ConfigList.FirstOrDefault(x => x.Selected);
LogHelper.WriteLog($"----------------------------------------------开始打印----------------------------------------------");
LogHelper.WriteLog($"进入打印方法,打印设置名称:{printConfig?.ConfigName},打印机名称:【{printConfig?.DefaultPrinter}】,是否启用直接打印功能:{printConfig?.DirectPrint}");
LogHelper.WriteLog("未指定默认打印机,请先做打印设置!");
LogHelper.WriteLog($"----------------------------------------------结束打印----------------------------------------------\r\n\r\n");
DialogResult result = MessageBox.Show("未指定默认打印机,请先做打印设置!", "系统提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
if (result == DialogResult.OK)
DialogResult dialog = new PrintSet().ShowDialog(this);
if (dialog == DialogResult.Cancel)
LogHelper.WriteLog($"打印数据请求接口来源:{args[0].Replace("jhrsprint:", "")}");
HttpClient client = new HttpClient();
HttpResponseMessage response = client.GetAsync(args[0].Replace("jhrsprint:", "")).Result;
if (!response.IsSuccessStatusCode)
var data = response.Content.ReadAsStringAsync().Result;
LogHelper.WriteLog($"打印数据:{data}");
PrintData printData = JsonConvert.DeserializeObject<PrintData>(data);
byte[] arrary = printData.FrxFile.Split('^').Select(x => (byte)int.Parse(x)).ToArray();
report1.Preview = previewControl1;
report1.Load(new MemoryStream(arrary));
report1.RegisterData(printData.Data, "打印数据源");
report1.PrintSettings.Printer = printConfig.DefaultPrinter;
LogHelper.WriteLog($"----------------------------------------------结束打印----------------------------------------------\r\n\r\n");
if (printConfig.DirectPrint)
report1.PrintSettings.ShowDialog = false;
private void MainForm_Load(object sender, EventArgs e)
public void SetMenuItem(bool reload = false)
var arrary = ConfigList.Select(x => x.ConfigName).ToArray();
toolStripComboBox1.Items.Add("未设置");
toolStripComboBox1.SelectedIndex = 0;
toolStripComboBox1.Items.Clear();
toolStripComboBox1.Items.AddRange(ConfigList.Select(x => x.ConfigName).ToArray());
var selected = ConfigList.FirstOrDefault(x => x.Selected);
if (selected != null) toolStripComboBox1.SelectedItem = selected.ConfigName;
private bool reloadConfig = false;
private List<Configs> list;
private List<Configs> ConfigList
if (list == null || reloadConfig == true)
list = XmlHelper.Deserialize<List<Configs>>(Constants.ConfigSavePath);
if (list != null) return list;
return list ?? new List<Configs>();
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
MessageBoxButtons mess = MessageBoxButtons.OKCancel;
var printConfig = ConfigList.FirstOrDefault(x => x.Selected);
if (printConfig != null && printConfig.DirectPrint)
DialogResult dr = MessageBox.Show("您确定要退出打印客户端吗?", "系统提示", mess, MessageBoxIcon.Question);
if (dr == DialogResult.OK)
private void notifyIcon1_MouseClick(object sender, MouseEventArgs e)
if (e.Button == MouseButtons.Left)
if (this.WindowState == FormWindowState.Minimized)
this.WindowState = FormWindowState.Maximized;
this.ShowInTaskbar = true;
private void 退出打印客户端ToolStripMenuItem_Click(object sender, EventArgs e)
MessageBoxButtons mess = MessageBoxButtons.OKCancel;
DialogResult dr = MessageBox.Show("您确定要退出打印客户端吗?", "系统提示", mess, MessageBoxIcon.Question);
if (dr == DialogResult.OK) ExitSystem();
private void 打印设置ToolStripMenuItem_Click(object sender, EventArgs e)
var set = new PrintSet();
if (set.ParentForm == null)
set.StartPosition = FormStartPosition.CenterScreen;
/// <param name="sender"></param>
/// <param name="e"></param>
private void toolStripComboBox1_SelectedIndexChanged(object sender, EventArgs e)
foreach (var item in list)
if (item.ConfigName == toolStripComboBox1.SelectedItem.ToString())
XmlHelper.SerializeToXmlFile(list);
private void 打印日志ToolStripMenuItem_Click(object sender, EventArgs e)
new LogForm().ShowDialog(this);
private void ExitSystem()
/// 待打印数据使用的报表,由服务器端返回告诉客户端控件
public string FrxFile { get; set; }
public DataTable Data { get; set; }
using JHRS.PrintClient.Entity;
using JHRS.PrintClient.Extensions;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Windows.Forms;
namespace JHRS.PrintClient
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
public string[] args { get; set; }
/// <summary>
/// 带参
/// </summary>
/// <param name="args"></param>
public MainForm(string[] args)
{
this.args = args;
InitializeComponent();
}
/// <summary>
/// 打印方法
/// </summary>
/// <param name="args"></param>
private void Print(string[] args)
{
var printConfig = ConfigList.FirstOrDefault(x => x.Selected);
LogHelper.WriteLog($"----------------------------------------------开始打印----------------------------------------------");
LogHelper.WriteLog($"进入打印方法,打印设置名称:{printConfig?.ConfigName},打印机名称:【{printConfig?.DefaultPrinter}】,是否启用直接打印功能:{printConfig?.DirectPrint}");
if (printConfig == null)
{
LogHelper.WriteLog("未指定默认打印机,请先做打印设置!");
LogHelper.WriteLog($"----------------------------------------------结束打印----------------------------------------------\r\n\r\n");
DialogResult result = MessageBox.Show("未指定默认打印机,请先做打印设置!", "系统提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
if (result == DialogResult.OK)
{
DialogResult dialog = new PrintSet().ShowDialog(this);
if (dialog == DialogResult.Cancel)
{
Print(args);
}
}
return;
}
LogHelper.WriteLog($"打印数据请求接口来源:{args[0].Replace("jhrsprint:", "")}");
HttpClient client = new HttpClient();
HttpResponseMessage response = client.GetAsync(args[0].Replace("jhrsprint:", "")).Result;
if (!response.IsSuccessStatusCode)
{
return;
}
var data = response.Content.ReadAsStringAsync().Result;
LogHelper.WriteLog($"打印数据:{data}");
PrintData printData = JsonConvert.DeserializeObject<PrintData>(data);
byte[] arrary = printData.FrxFile.Split('^').Select(x => (byte)int.Parse(x)).ToArray();
report1.Preview = previewControl1;
report1.Load(new MemoryStream(arrary));
report1.RegisterData(printData.Data, "打印数据源");
report1.PrintSettings.Printer = printConfig.DefaultPrinter;
LogHelper.WriteLog($"----------------------------------------------结束打印----------------------------------------------\r\n\r\n");
if (printConfig.DirectPrint)
{
report1.PrintSettings.ShowDialog = false;
report1.Print();
ExitSystem();
}
else
{
report1.Prepare();
report1.ShowPrepared();
}
}
private void MainForm_Load(object sender, EventArgs e)
{
if (args != null)
{
Print(args);
}
SetMenuItem();
}
/// <summary>
/// 设置默认打印机
/// </summary>
public void SetMenuItem(bool reload = false)
{
reloadConfig = reload;
var arrary = ConfigList.Select(x => x.ConfigName).ToArray();
if (arrary.Length == 0)
{
toolStripComboBox1.Items.Add("未设置");
toolStripComboBox1.SelectedIndex = 0;
}
else
{
toolStripComboBox1.Items.Clear();
toolStripComboBox1.Items.AddRange(ConfigList.Select(x => x.ConfigName).ToArray());
var selected = ConfigList.FirstOrDefault(x => x.Selected);
if (selected != null) toolStripComboBox1.SelectedItem = selected.ConfigName;
}
}
private bool reloadConfig = false;
private List<Configs> list;
private List<Configs> ConfigList
{
get
{
if (list == null || reloadConfig == true)
{
list = XmlHelper.Deserialize<List<Configs>>(Constants.ConfigSavePath);
reloadConfig = false;
}
if (list != null) return list;
return list ?? new List<Configs>();
}
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
MessageBoxButtons mess = MessageBoxButtons.OKCancel;
var printConfig = ConfigList.FirstOrDefault(x => x.Selected);
if (printConfig != null && printConfig.DirectPrint)
{
ExitSystem();
}
else
{
DialogResult dr = MessageBox.Show("您确定要退出打印客户端吗?", "系统提示", mess, MessageBoxIcon.Question);
if (dr == DialogResult.OK)
ExitSystem();
else
e.Cancel = true;
}
}
private void notifyIcon1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (this.WindowState == FormWindowState.Minimized)
{
this.WindowState = FormWindowState.Maximized;
this.ShowInTaskbar = true;
}
this.Activate();
}
}
private void 退出打印客户端ToolStripMenuItem_Click(object sender, EventArgs e)
{
MessageBoxButtons mess = MessageBoxButtons.OKCancel;
DialogResult dr = MessageBox.Show("您确定要退出打印客户端吗?", "系统提示", mess, MessageBoxIcon.Question);
if (dr == DialogResult.OK) ExitSystem();
}
private void 打印设置ToolStripMenuItem_Click(object sender, EventArgs e)
{
var set = new PrintSet();
if (set.ParentForm == null)
{
set.StartPosition = FormStartPosition.CenterScreen;
}
set.ShowDialog(this);
}
/// <summary>
/// 更改打印设置
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void toolStripComboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
var list = ConfigList;
foreach (var item in list)
{
if (item.ConfigName == toolStripComboBox1.SelectedItem.ToString())
{
item.Selected = true;
}
else
{
item.Selected = false;
}
}
XmlHelper.SerializeToXmlFile(list);
}
private void 打印日志ToolStripMenuItem_Click(object sender, EventArgs e)
{
new LogForm().ShowDialog(this);
}
/// <summary>
/// 退出系统
/// </summary>
private void ExitSystem()
{
notifyIcon.Dispose();
Environment.Exit(0);
}
}
/// <summary>
/// 打印数据实体
/// </summary>
public class PrintData
{
/// <summary>
/// 待打印数据使用的报表,由服务器端返回告诉客户端控件
/// </summary>
public string FrxFile { get; set; }
/// <summary>
/// 报表数据源,由服务器端返回
/// </summary>
public DataTable Data { get; set; }
}
}
using JHRS.PrintClient.Entity;
using JHRS.PrintClient.Extensions;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Windows.Forms;
namespace JHRS.PrintClient
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
public string[] args { get; set; }
/// <summary>
/// 带参
/// </summary>
/// <param name="args"></param>
public MainForm(string[] args)
{
this.args = args;
InitializeComponent();
}
/// <summary>
/// 打印方法
/// </summary>
/// <param name="args"></param>
private void Print(string[] args)
{
var printConfig = ConfigList.FirstOrDefault(x => x.Selected);
LogHelper.WriteLog($"----------------------------------------------开始打印----------------------------------------------");
LogHelper.WriteLog($"进入打印方法,打印设置名称:{printConfig?.ConfigName},打印机名称:【{printConfig?.DefaultPrinter}】,是否启用直接打印功能:{printConfig?.DirectPrint}");
if (printConfig == null)
{
LogHelper.WriteLog("未指定默认打印机,请先做打印设置!");
LogHelper.WriteLog($"----------------------------------------------结束打印----------------------------------------------\r\n\r\n");
DialogResult result = MessageBox.Show("未指定默认打印机,请先做打印设置!", "系统提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
if (result == DialogResult.OK)
{
DialogResult dialog = new PrintSet().ShowDialog(this);
if (dialog == DialogResult.Cancel)
{
Print(args);
}
}
return;
}
LogHelper.WriteLog($"打印数据请求接口来源:{args[0].Replace("jhrsprint:", "")}");
HttpClient client = new HttpClient();
HttpResponseMessage response = client.GetAsync(args[0].Replace("jhrsprint:", "")).Result;
if (!response.IsSuccessStatusCode)
{
return;
}
var data = response.Content.ReadAsStringAsync().Result;
LogHelper.WriteLog($"打印数据:{data}");
PrintData printData = JsonConvert.DeserializeObject<PrintData>(data);
byte[] arrary = printData.FrxFile.Split('^').Select(x => (byte)int.Parse(x)).ToArray();
report1.Preview = previewControl1;
report1.Load(new MemoryStream(arrary));
report1.RegisterData(printData.Data, "打印数据源");
report1.PrintSettings.Printer = printConfig.DefaultPrinter;
LogHelper.WriteLog($"----------------------------------------------结束打印----------------------------------------------\r\n\r\n");
if (printConfig.DirectPrint)
{
report1.PrintSettings.ShowDialog = false;
report1.Print();
ExitSystem();
}
else
{
report1.Prepare();
report1.ShowPrepared();
}
}
private void MainForm_Load(object sender, EventArgs e)
{
if (args != null)
{
Print(args);
}
SetMenuItem();
}
/// <summary>
/// 设置默认打印机
/// </summary>
public void SetMenuItem(bool reload = false)
{
reloadConfig = reload;
var arrary = ConfigList.Select(x => x.ConfigName).ToArray();
if (arrary.Length == 0)
{
toolStripComboBox1.Items.Add("未设置");
toolStripComboBox1.SelectedIndex = 0;
}
else
{
toolStripComboBox1.Items.Clear();
toolStripComboBox1.Items.AddRange(ConfigList.Select(x => x.ConfigName).ToArray());
var selected = ConfigList.FirstOrDefault(x => x.Selected);
if (selected != null) toolStripComboBox1.SelectedItem = selected.ConfigName;
}
}
private bool reloadConfig = false;
private List<Configs> list;
private List<Configs> ConfigList
{
get
{
if (list == null || reloadConfig == true)
{
list = XmlHelper.Deserialize<List<Configs>>(Constants.ConfigSavePath);
reloadConfig = false;
}
if (list != null) return list;
return list ?? new List<Configs>();
}
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
MessageBoxButtons mess = MessageBoxButtons.OKCancel;
var printConfig = ConfigList.FirstOrDefault(x => x.Selected);
if (printConfig != null && printConfig.DirectPrint)
{
ExitSystem();
}
else
{
DialogResult dr = MessageBox.Show("您确定要退出打印客户端吗?", "系统提示", mess, MessageBoxIcon.Question);
if (dr == DialogResult.OK)
ExitSystem();
else
e.Cancel = true;
}
}
private void notifyIcon1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (this.WindowState == FormWindowState.Minimized)
{
this.WindowState = FormWindowState.Maximized;
this.ShowInTaskbar = true;
}
this.Activate();
}
}
private void 退出打印客户端ToolStripMenuItem_Click(object sender, EventArgs e)
{
MessageBoxButtons mess = MessageBoxButtons.OKCancel;
DialogResult dr = MessageBox.Show("您确定要退出打印客户端吗?", "系统提示", mess, MessageBoxIcon.Question);
if (dr == DialogResult.OK) ExitSystem();
}
private void 打印设置ToolStripMenuItem_Click(object sender, EventArgs e)
{
var set = new PrintSet();
if (set.ParentForm == null)
{
set.StartPosition = FormStartPosition.CenterScreen;
}
set.ShowDialog(this);
}
/// <summary>
/// 更改打印设置
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void toolStripComboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
var list = ConfigList;
foreach (var item in list)
{
if (item.ConfigName == toolStripComboBox1.SelectedItem.ToString())
{
item.Selected = true;
}
else
{
item.Selected = false;
}
}
XmlHelper.SerializeToXmlFile(list);
}
private void 打印日志ToolStripMenuItem_Click(object sender, EventArgs e)
{
new LogForm().ShowDialog(this);
}
/// <summary>
/// 退出系统
/// </summary>
private void ExitSystem()
{
notifyIcon.Dispose();
Environment.Exit(0);
}
}
/// <summary>
/// 打印数据实体
/// </summary>
public class PrintData
{
/// <summary>
/// 待打印数据使用的报表,由服务器端返回告诉客户端控件
/// </summary>
public string FrxFile { get; set; }
/// <summary>
/// 报表数据源,由服务器端返回
/// </summary>
public DataTable Data { get; set; }
}
}
在上面代码中的Print(string[] args)方法中,args参数就是通过自定义协议传入的,该方法就是最终的调用FastReport.Net组件在(客户端)浏览器上实现打印功能,完全具备和桌面软件一样的功能。有关自定义协议的内容,参见下面这篇文章:
编写安装程序类
安装程序类的用途是将自定义协议信息写入注册表,当你安装客户端程序时,它会自动的被操作系统的 Installer 自动调用,当然需要你在打包项目中做一些设置。完整的安装程序类的源码如下:
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Threading.Tasks;
namespace JHRS.PrintClient
public partial class PrintInstall : Installer
protected override void OnAfterInstall(IDictionary savedState)
string path = this.Context.Parameters["targetdir"];
//获取用户设定的安装目标路径, 注意,需要在Setup项目里面自定义操作的属性栏里面的CustomActionData添加上/targetdir="[TARGETDIR]\"
const string UriScheme = "jhrsprint";
const string FriendlyName = "jhrsprint自定义协议";
using (var key = Registry.LocalMachine.CreateSubKey("SOFTWARE\\Classes\\" + UriScheme))
string applicationLocation = path.Substring(0, path.Length - 1) + @"JHRS.PrintClient.exe";
LogWrite($"打印客户端安装路径:{applicationLocation}");
key.SetValue("", "URL:" + FriendlyName);
LogWrite($"自定义协议名称:URL:{FriendlyName}");
key.SetValue("URL Protocol", "");
using (var defaultIcon = key.CreateSubKey("DefaultIcon"))
defaultIcon.SetValue("", applicationLocation + ",1");
using (var commandKey = key.CreateSubKey(@"shell\open\command"))
commandKey.SetValue("", "\"" + applicationLocation + "\" \"%1\"");
LogWrite($"设置结束!key.Name是:{key.Name},{key}");
base.OnAfterInstall(savedState);
public override void Install(IDictionary stateSaver)
base.Install(stateSaver);
protected override void OnBeforeInstall(IDictionary savedState)
LogWrite("OnBeforeInstall!");
base.OnBeforeInstall(savedState);
public override void Uninstall(IDictionary savedState)
LogWrite("Uninstall!"); base.Uninstall(savedState);
public override void Rollback(IDictionary savedState)
base.Rollback(savedState);
public void LogWrite(string str)
string LogPath = @"c:\log\";
if (!System.IO.Directory.Exists(LogPath)) System.IO.Directory.CreateDirectory(LogPath);
using (System.IO.StreamWriter sw = new System.IO.StreamWriter(LogPath + @"SetUpLog.txt", true))
sw.WriteLine(DateTime.Now.ToString("[yyyy-MM-dd HH:mm:ss] ") + str + "\n");
using Microsoft.Win32;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Linq;
using System.Threading.Tasks;
namespace JHRS.PrintClient
{
[RunInstaller(true)]
public partial class PrintInstall : Installer
{
public PrintInstall()
{
InitializeComponent();
}
protected override void OnAfterInstall(IDictionary savedState)
{
string path = this.Context.Parameters["targetdir"];
//获取用户设定的安装目标路径, 注意,需要在Setup项目里面自定义操作的属性栏里面的CustomActionData添加上/targetdir="[TARGETDIR]\"
LogWrite(path);
const string UriScheme = "jhrsprint";
const string FriendlyName = "jhrsprint自定义协议";
using (var key = Registry.LocalMachine.CreateSubKey("SOFTWARE\\Classes\\" + UriScheme))
{
string applicationLocation = path.Substring(0, path.Length - 1) + @"JHRS.PrintClient.exe";
LogWrite($"打印客户端安装路径:{applicationLocation}");
key.SetValue("", "URL:" + FriendlyName);
LogWrite($"自定义协议名称:URL:{FriendlyName}");
key.SetValue("URL Protocol", "");
using (var defaultIcon = key.CreateSubKey("DefaultIcon"))
{
defaultIcon.SetValue("", applicationLocation + ",1");
}
using (var commandKey = key.CreateSubKey(@"shell\open\command"))
{
commandKey.SetValue("", "\"" + applicationLocation + "\" \"%1\"");
}
LogWrite($"设置结束!key.Name是:{key.Name},{key}");
}
base.OnAfterInstall(savedState);
}
public override void Install(IDictionary stateSaver)
{
LogWrite("Install!");
base.Install(stateSaver);
}
protected override void OnBeforeInstall(IDictionary savedState)
{
LogWrite("OnBeforeInstall!");
base.OnBeforeInstall(savedState);
}
public override void Uninstall(IDictionary savedState)
{
LogWrite("Uninstall!"); base.Uninstall(savedState);
}
public override void Rollback(IDictionary savedState)
{
LogWrite("Rollback");
base.Rollback(savedState);
}
public void LogWrite(string str)
{
#if DEBUG
string LogPath = @"c:\log\";
if (!System.IO.Directory.Exists(LogPath)) System.IO.Directory.CreateDirectory(LogPath);
using (System.IO.StreamWriter sw = new System.IO.StreamWriter(LogPath + @"SetUpLog.txt", true))
{
sw.WriteLine(DateTime.Now.ToString("[yyyy-MM-dd HH:mm:ss] ") + str + "\n");
}
#endif
}
}
}
using Microsoft.Win32;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Linq;
using System.Threading.Tasks;
namespace JHRS.PrintClient
{
[RunInstaller(true)]
public partial class PrintInstall : Installer
{
public PrintInstall()
{
InitializeComponent();
}
protected override void OnAfterInstall(IDictionary savedState)
{
string path = this.Context.Parameters["targetdir"];
//获取用户设定的安装目标路径, 注意,需要在Setup项目里面自定义操作的属性栏里面的CustomActionData添加上/targetdir="[TARGETDIR]\"
LogWrite(path);
const string UriScheme = "jhrsprint";
const string FriendlyName = "jhrsprint自定义协议";
using (var key = Registry.LocalMachine.CreateSubKey("SOFTWARE\\Classes\\" + UriScheme))
{
string applicationLocation = path.Substring(0, path.Length - 1) + @"JHRS.PrintClient.exe";
LogWrite($"打印客户端安装路径:{applicationLocation}");
key.SetValue("", "URL:" + FriendlyName);
LogWrite($"自定义协议名称:URL:{FriendlyName}");
key.SetValue("URL Protocol", "");
using (var defaultIcon = key.CreateSubKey("DefaultIcon"))
{
defaultIcon.SetValue("", applicationLocation + ",1");
}
using (var commandKey = key.CreateSubKey(@"shell\open\command"))
{
commandKey.SetValue("", "\"" + applicationLocation + "\" \"%1\"");
}
LogWrite($"设置结束!key.Name是:{key.Name},{key}");
}
base.OnAfterInstall(savedState);
}
public override void Install(IDictionary stateSaver)
{
LogWrite("Install!");
base.Install(stateSaver);
}
protected override void OnBeforeInstall(IDictionary savedState)
{
LogWrite("OnBeforeInstall!");
base.OnBeforeInstall(savedState);
}
public override void Uninstall(IDictionary savedState)
{
LogWrite("Uninstall!"); base.Uninstall(savedState);
}
public override void Rollback(IDictionary savedState)
{
LogWrite("Rollback");
base.Rollback(savedState);
}
public void LogWrite(string str)
{
#if DEBUG
string LogPath = @"c:\log\";
if (!System.IO.Directory.Exists(LogPath)) System.IO.Directory.CreateDirectory(LogPath);
using (System.IO.StreamWriter sw = new System.IO.StreamWriter(LogPath + @"SetUpLog.txt", true))
{
sw.WriteLine(DateTime.Now.ToString("[yyyy-MM-dd HH:mm:ss] ") + str + "\n");
}
#endif
}
}
}
web打印客户端安装后会在桌面生成一个快捷方式,平常不会使用它的,除非你需要对客户端做一些设置。
FastReport.net 直接打印
完成了web客户端程序的编写和打包程序的设置, 剩下的操作你只需要将客户端打包就行了,之后放到网站目录,并提供一个下载功能,供用户下载安装你的客户端即可。
接下来我们来看网站端是怎样使用的FastReport web打印功能的。
FastReport web打印页面代码
页面上要触发打印功能,通常都是放置按钮或者链接来实现,正如下面的代码那样,我使用asp.net core razor page来编写的。页面上放置了一个按钮,然后将请求提交到当前页面来处理。
<input type="button" value="下载并安装控件" onclick="location.href='?handler=Download'" />
<input type="button" value="下载并安装控件" onclick="location.href='?handler=Download'" />
<input type="button" value="下载并安装控件" onclick="location.href='?handler=Download'" />
完整的代码你可以在github上找到。后台处理打印请求,即获取打印数据的示例代码如下:
public ContentResult OnGetPrintData()
if ("https://jhrs.com".Equals(Request.Query["par1"].ToString())&& "znlive.com".Equals(Request.Query["myblog"].ToString()))
List<SbEntity> list = new List<SbEntity>();
for (int i = 0; i < 1; i++)
启用日期 = DateTime.Now.AddDays(i),
条码 = "BH20190302002" + i,
二维码 = "https://znlive.com/the-best-cheap-vpn",
生产厂家 = "jhrs.com 科技有限公司",
质保日期 = DateTime.Now.AddYears(i + 1)
var print = new PrintDataEntity<List<SbEntity>> { Data = list };
var path = Path.Combine(Environment.CurrentDirectory, "wwwroot/print/设备标牌.frx");
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
byte[] buffur = new byte[fs.Length];
fs.Read(buffur, 0, (int)fs.Length);
print.FrxFile = string.Join("^", buffur);
return Content(new { Success = false, ErrorMsg = ex.Message }.ToJson());
return Content(print.ToJson());
return Content(new { Success = false, ErrorMsg = "狗日的,参数不对,重新传!" }.ToJson());
/// <summary>
/// 获取打印数据
/// </summary>
/// <returns></returns>
public ContentResult OnGetPrintData()
{
if ("https://jhrs.com".Equals(Request.Query["par1"].ToString())&& "znlive.com".Equals(Request.Query["myblog"].ToString()))
{
List<SbEntity> list = new List<SbEntity>();
for (int i = 0; i < 1; i++)
{
list.Add(new SbEntity
{
标题 = "江湖人士醫院超聲設備標牌",
使用科室 = "超聲科-" + i,
启用日期 = DateTime.Now.AddDays(i),
型号 = "XH34534-" + i,
序列号 = "XLH-3452" + i,
序号 = (i + 1).ToString(),
条码 = "BH20190302002" + i,
二维码 = "https://znlive.com/the-best-cheap-vpn",
生产厂家 = "jhrs.com 科技有限公司",
规格 = "GGX-1" + i,
设备名称 = "X射線髮射器",
责任人 = "趙佳發",
质保日期 = DateTime.Now.AddYears(i + 1)
});
}
var print = new PrintDataEntity<List<SbEntity>> { Data = list };
var path = Path.Combine(Environment.CurrentDirectory, "wwwroot/print/设备标牌.frx");
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
{
try
{
byte[] buffur = new byte[fs.Length];
fs.Read(buffur, 0, (int)fs.Length);
print.FrxFile = string.Join("^", buffur);
}
catch (Exception ex)
{
return Content(new { Success = false, ErrorMsg = ex.Message }.ToJson());
}
}
return Content(print.ToJson());
}
return Content(new { Success = false, ErrorMsg = "狗日的,参数不对,重新传!" }.ToJson());
}
/// <summary>
/// 获取打印数据
/// </summary>
/// <returns></returns>
public ContentResult OnGetPrintData()
{
if ("https://jhrs.com".Equals(Request.Query["par1"].ToString())&& "znlive.com".Equals(Request.Query["myblog"].ToString()))
{
List<SbEntity> list = new List<SbEntity>();
for (int i = 0; i < 1; i++)
{
list.Add(new SbEntity
{
标题 = "江湖人士醫院超聲設備標牌",
使用科室 = "超聲科-" + i,
启用日期 = DateTime.Now.AddDays(i),
型号 = "XH34534-" + i,
序列号 = "XLH-3452" + i,
序号 = (i + 1).ToString(),
条码 = "BH20190302002" + i,
二维码 = "https://znlive.com/the-best-cheap-vpn",
生产厂家 = "jhrs.com 科技有限公司",
规格 = "GGX-1" + i,
设备名称 = "X射線髮射器",
责任人 = "趙佳發",
质保日期 = DateTime.Now.AddYears(i + 1)
});
}
var print = new PrintDataEntity<List<SbEntity>> { Data = list };
var path = Path.Combine(Environment.CurrentDirectory, "wwwroot/print/设备标牌.frx");
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
{
try
{
byte[] buffur = new byte[fs.Length];
fs.Read(buffur, 0, (int)fs.Length);
print.FrxFile = string.Join("^", buffur);
}
catch (Exception ex)
{
return Content(new { Success = false, ErrorMsg = ex.Message }.ToJson());
}
}
return Content(print.ToJson());
}
return Content(new { Success = false, ErrorMsg = "狗日的,参数不对,重新传!" }.ToJson());
}
怎么样,是不是很简单呢?在演示网站中,也提供了下载功能,这里就不做过多介绍了,完整的解决方案源码你可以在github上找到。
最后提示:FastReport.net 组件是付费产品,你在使用过程中,需要购买授权,你可以在它们的官网上联系售后进行购买。下面提供的2.0学习版较为古老,最新版界面风格也变了,但使用方式类似。
总结
本文写得比较简单,如果你是一个新手,况且对于FastReport.net没有过多的了解,或者不熟悉,会遇到很多的问题,本来还想画一张图来说明一下的,但想想还是算了,毕竟当阅读到这篇文章的人都是开发人员,且有需求,应该会弄明白的。
顺便提一下,本文并没有讲解如何使用FastReport.net,例如怎样制作报表文件,怎样绑定数据源,怎样添加字典文件等,这些内容,只需要看看FastReport.net提供的demo程序就明白了。做为学习使用,这里提供了一个国外网盘下载地址,你可以下载FastReport.net来学习,如果下载不了,请参见上面打开github的方法解决,毕竟是国外的网盘。
你下载时需要做好心里准备,这TMD的网盘要连点几次广告才能下载。。当然钱能解决一切问题。
FastReport.net 2.0学习版下载地址:点击下载
写文章太费时间了,高兴就好,你懂的。

【江湖人士】(jhrs.com)原创文章,作者:江小编,如若转载,请注明出处:https://jhrs.com/2021/44698.html
扫码加入电报群,让你获得国外网赚一手信息。
文章标题:FastReport浏览器直接打印无须预览(2025终版)
请问:测试打后 报错:Unexpected character encountered while parsing value: <. Path '', line 0, position 0
应如何解决
win10
有关调用实时(JIT)调试而不是此对话框的详细信息,
请参见此消息的结尾。
************** 异常文本 **************
Newtonsoft.Json.JsonReaderException: Unexpected character encountered while parsing value: <. Path '', line 0, position 0.
在 Newtonsoft.Json.JsonTextReader.ParseValue()
在 Newtonsoft.Json.JsonTextReader.Read()
在 Newtonsoft.Json.JsonReader.ReadForType(JsonContract contract, Boolean hasConverter)
在 Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
在 Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
在 Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
在 Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)
在 Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value)
在 JHRS.PrintClient.MainForm.Print(String[] args)
在 JHRS.PrintClient.MainForm.MainForm_Load(Object sender, EventArgs e)
在 System.Windows.Forms.Form.OnLoad(EventArgs e)
在 System.Windows.Forms.Form.OnCreateControl()
在 System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)
在 System.Windows.Forms.Control.CreateControl()
在 System.Windows.Forms.Control.WmShowWindow(Message& m)
在 System.Windows.Forms.Control.WndProc(Message& m)
在 System.Windows.Forms.ScrollableControl.WndProc(Message& m)
在 System.Windows.Forms.ContainerControl.WndProc(Message& m)
在 System.Windows.Forms.Form.WmShowWindow(Message& m)
在 System.Windows.Forms.Form.WndProc(Message& m)
在 System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
在 System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
在 System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
************** 已加载的程序集 **************
mscorlib
程序集版本:4.0.0.0
Win32 版本:4.8.4250.0 built by: NET48REL1LAST_C
基本代码:file:///C:/Windows/Microsoft.NET/Framework/v4.0.30319/mscorlib.dll
—————————————-
JHRS.PrintClient
程序集版本:1.0.0.0
Win32 版本:1.0.0.0
基本代码:file:///C:/QMDownload/%E6%89%93%E5%8D%B0%E5%AE%A2%E6%88%B7%E7%AB%AF/JHRS.PrintClient.exe
—————————————-
System.Windows.Forms
程序集版本:4.0.0.0
Win32 版本:4.8.4270.0 built by: NET48REL1LAST_C
基本代码:file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/System.Windows.Forms/v4.0_4.0.0.0__b77a5c561934e089/System.Windows.Forms.dll
—————————————-
System
程序集版本:4.0.0.0
Win32 版本:4.8.4200.0 built by: NET48REL1LAST_C
基本代码:file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/System/v4.0_4.0.0.0__b77a5c561934e089/System.dll
—————————————-
System.Drawing
程序集版本:4.0.0.0
Win32 版本:4.8.3761.0 built by: NET48REL1
基本代码:file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/System.Drawing/v4.0_4.0.0.0__b03f5f7f11d50a3a/System.Drawing.dll
—————————————-
System.Configuration
程序集版本:4.0.0.0
Win32 版本:4.8.4190.0 built by: NET48REL1LAST_B
基本代码:file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/System.Configuration/v4.0_4.0.0.0__b03f5f7f11d50a3a/System.Configuration.dll
—————————————-
System.Xml
程序集版本:4.0.0.0
Win32 版本:4.8.3761.0 built by: NET48REL1
基本代码:file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/System.Xml/v4.0_4.0.0.0__b77a5c561934e089/System.Xml.dll
—————————————-
Accessibility
程序集版本:4.0.0.0
Win32 版本:4.8.3761.0 built by: NET48REL1
基本代码:file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/Accessibility/v4.0_4.0.0.0__b03f5f7f11d50a3a/Accessibility.dll
—————————————-
FastReport
程序集版本:2013.2.5.0
Win32 版本:
基本代码:file:///C:/QMDownload/%E6%89%93%E5%8D%B0%E5%AE%A2%E6%88%B7%E7%AB%AF/FastReport.DLL
—————————————-
FastReport.Bars
程序集版本:2013.2.5.0
Win32 版本:2013.2.5.0
基本代码:file:///C:/QMDownload/%E6%89%93%E5%8D%B0%E5%AE%A2%E6%88%B7%E7%AB%AF/FastReport.Bars.DLL
—————————————-
System.Data
程序集版本:4.0.0.0
Win32 版本:4.8.4270.0 built by: NET48REL1LAST_C
基本代码:file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_32/System.Data/v4.0_4.0.0.0__b77a5c561934e089/System.Data.dll
—————————————-
System.Core
程序集版本:4.0.0.0
Win32 版本:4.8.4270.0 built by: NET48REL1LAST_C
基本代码:file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/System.Core/v4.0_4.0.0.0__b77a5c561934e089/System.Core.dll
—————————————-
System.Net.Http
程序集版本:4.0.0.0
Win32 版本:4.8.3761.0 built by: NET48REL1
基本代码:file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/System.Net.Http/v4.0_4.0.0.0__b03f5f7f11d50a3a/System.Net.Http.dll
—————————————-
Newtonsoft.Json
程序集版本:13.0.0.0
Win32 版本:13.0.1.25517
基本代码:file:///C:/QMDownload/%E6%89%93%E5%8D%B0%E5%AE%A2%E6%88%B7%E7%AB%AF/Newtonsoft.Json.DLL
—————————————-
Microsoft.GeneratedCode
程序集版本:1.0.0.0
Win32 版本:4.8.3761.0 built by: NET48REL1
基本代码:file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/System.Xml/v4.0_4.0.0.0__b77a5c561934e089/System.Xml.dll
—————————————-
System.Numerics
程序集版本:4.0.0.0
Win32 版本:4.8.3761.0 built by: NET48REL1
基本代码:file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/System.Numerics/v4.0_4.0.0.0__b77a5c561934e089/System.Numerics.dll
—————————————-
System.Runtime.Serialization
程序集版本:4.0.0.0
Win32 版本:4.8.4250.0 built by: NET48REL1LAST_C
基本代码:file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/System.Runtime.Serialization/v4.0_4.0.0.0__b77a5c561934e089/System.Runtime.Serialization.dll
—————————————-
System.Windows.Forms.resources
程序集版本:4.0.0.0
Win32 版本:4.8.3761.0 built by: NET48REL1
基本代码:file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/System.Windows.Forms.resources/v4.0_4.0.0.0_zh-Hans_b77a5c561934e089/System.Windows.Forms.resources.dll
—————————————-
mscorlib.resources
程序集版本:4.0.0.0
Win32 版本:4.8.3761.0 built by: NET48REL1
基本代码:file:///C:/WINDOWS/Microsoft.Net/assembly/GAC_MSIL/mscorlib.resources/v4.0_4.0.0.0_zh-Hans_b77a5c561934e089/mscorlib.resources.dll
—————————————-
************** JIT 调试 **************
要启用实时(JIT)调试,
该应用程序或计算机的 .config 文件(machine.config)的 system.windows.forms 节中必须设置
jitDebugging 值。
编译应用程序时还必须启用
调试。
例如:
启用 JIT 调试后,任何未经处理的异常
都将被发送到在此计算机上注册的 JIT 调试程序,
而不是由此对话框处理。
可能是json序列化的问题,你可以从github上拉下代码,自己搭建环境调试一下就知道了。