.Net Core在线预览打印PDF,JsReport的使用
说到在线预览并打印Pdf,Java的解决方案可谓层出不穷,尤以JasperReport为甚,再加上各大博客的资源分享,实现其功能更是易如反掌。再看回c#,资源寥寥无几,大多数是根据一个pdf模板,再生成一个新的pdf。
出去上网看了一下,终于找到解决方案:jsreport
在线预览Demo:jsreport playground
社区论坛:jsreport forum
基础文档:Learn jsreport

三大特征:
可支持生成多种文件类型(pdf, excel, docx, html, csv)
开源、跨平台(用于商业可购买)
html为模板(JasperReport以xml为模板,个人还是比较习惯用html的...)
基本介绍完之后,那么该如何在dotnet Core中使用呢?

Startup.cs的ConfigureServices加入:
#region jsreport services.AddJsReport( //为jsReport建一个临时文件,可以避免一些意外问题 要确保提供的文件路径存在 new LocalReporting().TempDirectory(AppDomain.CurrentDomain.BaseDirectory + "Files\\jsReportTemp") .UseBinary(JsReportBinary.GetBinary()) //自定义端口 默认5488端口 .Configure((cfg) => { cfg.HttpPort = 8008; return cfg; }) //在启动一个jsreport程序前 结束正在运行的程序 .KillRunningJsReportProcesses() .AsUtility() .Create()); #endregion //mvc支持 services.AddControllersWithViews();Controller加一个Action
x /// <summary> /// 打印标签 /// </summary> /// <returns></returns> [HttpGet("label")] //jsReport支持 [MiddlewareFilter(typeof(JsReportPipeline))] public IActionResult Label() { //jsReport支持 HttpContext.JsReportFeature().Recipe(Recipe.ChromePdf); //Recipe.ChromePdf 即模板 return View(Init()); } /// <summary> /// 生成ViewModel /// </summary> /// <returns></returns> private LabelModel Init() { var labelModel = new LabelModel { Fnsku = "X003O97GHR" }; if (string.IsNullOrWhiteSpace(labelModel.Fnsku)) throw new Exception("Fnsku不可未空"); var fileName = "条形码_" + labelModel.Fnsku + DateTime.Now.ToString("yyMMddHHmmss"); var path = AppDomain.CurrentDomain.BaseDirectory + "Files\\pdfTemp\\" + fileName + ".jpg"; Util.CreateBarcode(labelModel.Fnsku, 320, 37, path); labelModel.Src = Util.ImageToBase64(path); var title = "Amazon Basics 48 Pack AA High-Performance Alkaline Batteries, 10-Year Shelf Life, Easy to Open Value Pack"; if (!string.IsNullOrWhiteSpace(title) && title.Length > 18 && title.Length <= 35) labelModel.Title = title.Substring(0, 18) + "...," + title[^35..]; else if (!string.IsNullOrWhiteSpace(title) && title.Length > 35) labelModel.Title = title.Substring(0, 18) + "...," + title[^35..]; else labelModel.Title = title;
//if (System.IO.File.Exists(path)) System.IO.File.Delete(path);
return labelModel; }对应的View:
xxxxxxxxxx23@model JsReportTest.Models.LabelModel<html><head> <style> .text-center { text-align: center !important; }
p { margin-top: 0; margin-bottom: 0; font-size: 0.85em !important; } </style></head><body> <div style="width:280px;"> <img style="width:90%" src="data:image/jgp;base64,@Model.Src" /> <p class="text-center"> @Model.Fnsku </p> <p><span style="font-size:0.75em;">@Model.Title</span></p> </div></body></html>Action头部添加jsreport特性:[MiddlewareFilter(typeof(JsReportPipeline))]
返回视图前也要添加:HttpContext.JsReportFeature().Recipe(Recipe.ChromePdf);
View视图层就是pdf生成的模板,里面的数据可动态变化,只需传入对应的ViewModel即可。数据可动态变化,我们可以根据数据生成文档(pdf),而不需要每一份文档都存储到服务器里面,在预览合同、打印标签等场景下非常适用。
效果预览图:

如何预览并在程序中保存文档:
xxxxxxxxxx21 /// <summary> /// 打印标签并在程序中保存 /// </summary> /// <returns></returns> [HttpGet("labelAndSave")] //jsReport支持 [MiddlewareFilter(typeof(JsReportPipeline))] public IActionResult LabelAndSave() { //jsReport支持 HttpContext.JsReportFeature().Recipe(Recipe.ChromePdf).OnAfterRender((res)=> { var fileName = "条形码_" + DateTime.Now.ToString("yyMMddHHmmss"); using (var file = System.IO.File.Open(fileName+".pdf", FileMode.Create)) { res.Content.CopyTo(file); } res.Content.Seek(0, SeekOrigin.Begin); //todo 发邮件 }); return View("Label",Init()); }适用于发送邮件时发送附件或将文档存储到服务器的场景
值得注意的是:如果你的程序(常出现在web api项目)使用ActionFilterAttribute来过滤返回的结果集,要将当前预览pdf的Action排除出去,不然会出现生成不了pdf或者pdf内容不正确的情况。
xxxxxxxxxx15/// <summary>/// 打印标签并筛选结果集/// </summary>/// <returns></returns>[HttpGet("labelFilterTest")]//jsReport支持[MiddlewareFilter(typeof(JsReportPipeline))]//此处Filter作用不大,如果将此Filter用于修饰Class,效果会明显一点,此处不演示[TokenFilter]public IActionResult LabelFilterTest(){//jsReport支持HttpContext.JsReportFeature().Recipe(Recipe.ChromePdf); //Recipe.ChromePdf 即模板return View("Label",Init());}

改动后

补充:
错误一:
2021-02-08 10:45:31.6101 | Error | Microsoft.AspNetCore.Server.IIS.Core.IISHttpServer | Connection ID "17437937767379108040", Request ID "800000c9-0002-f200-b63f-84710c7967bb": An unhandled exception was thrown by the application. jsreport.Local.JsReportBinaryException: Error rendering report: A critical error occurred while trying to execute the render command (2). An error occurred while trying to start daemonized process: An error has occurred when trying to initialize jsreport (1). caused by error (2):-> stackError: at onCriticalError (D:\snapshot\jsreport\node_modules\jsreport-cli\lib\commands\render.js:302:19) at D:\snapshot\jsreport\node_modules\jsreport-cli\lib\commands\render.js:256:14caused by error (1):-> meta = {"code":"EACCES"}-> stackError: An error has occurred when trying to initialize jsreport at Object.1.ProcessRequestAsync()
AsyncStateMachineBox1.ExecutionContextCallback =>
解决方案:端口被占用,切换为可用端口(如:8018)即可,如果是发布到服务器上要在服务器管理后台上开放指定端口(如:8018)

