Asp.net Core 3.1 Razor视图模版动态渲染PDF

  1. 前言

最近的线上项目受理回执接入了电子签章,老项目一直是html打印,但是接入的电子签章是仅仅对PDF电子签章,目前还没有Html电子签章或者其他格式文件的电子签章。首先我想到的是用一个js把前端的html转换PDF,再传回去服务器电子签章。但是这个样子就有一个bug,用户可以在浏览器删改html,这样电子签章的防删改功能就用不到,那么电子签章还有啥意义?所以PDF签章前还是不能给用户有接触的机会,不然用户就要偷偷干坏事了。于是这种背景下,本插件应运而生。我想到直接把Razor渲染成html,html再渲染成PDF。

该项目的优点在于,可以很轻松的把老旧项目的Razor转换成PDF文件,无需后台组装PDF,如果需要排版PDF,我们只需要修改CSS样式和Html代码即可做到。而且我们可以直接先写好Razor视图,做到动态半可视化设计,最后切换一下ActionResult。不必像以前需要在脑海里面设计PDF板式,并一次一次的重启启动调试去修改样式。

2.依赖项目

本插件 支持net45,net46,core的各个版本,(我目前仅仅使用net45和core 3.1.对于其他版本我还没实际应用,但是稍微调整都是支持的,那么简单来说就是支持net 45以上,现在演示的是使用Core3.1)。

依赖插件

Haukcode.DinkToPdf

RazorEngine.NetCore

第一个插件是Html转换PDF的核心插件,具体使用方法自行去了解,这里不多说。

第二个是根据数据模版渲染Razor.

3.核心代码

Razor转Html代码

 protected string RunCompileRazorTemplate(object model,string razorTemplateStr)
{
if(string.IsNullOrWhiteSpace(razorTemplateStr))
throw new ArgumentException("Razor模版不能为空"); var htmlString= Engine.Razor.RunCompile(razorTemplateStr, razorTemplateStr.GetHashCode().ToString(), null, model);
return htmlString;
}

Html模版转PDF核心代码

 private static readonly SynchronizedConverter PdfConverter = new SynchronizedConverter(new PdfTools());
 private byte[] ExportPdf(string htmlString, PdfExportAttribute pdfExportAttribute )
{
var objSetting = new ObjectSettings
{
HtmlContent = htmlString,
PagesCount = pdfExportAttribute.IsEnablePagesCount ? true : (bool?)null,
WebSettings = { DefaultEncoding = Encoding.UTF8.BodyName },
HeaderSettings= pdfExportAttribute?.HeaderSettings,
FooterSettings= pdfExportAttribute?.FooterSettings, }; var htmlToPdfDocument = new HtmlToPdfDocument
{
GlobalSettings =
{
PaperSize = pdfExportAttribute?.PaperKind,
Orientation = pdfExportAttribute?.Orientation,
ColorMode = ColorMode.Color,
DocumentTitle = pdfExportAttribute?.Name
},
Objects =
{
objSetting
}
}; var result = PdfConverter.Convert(htmlToPdfDocument);
return result;
}

Razor 渲染PDF ActionResult核心代码

using JESAI.HtmlTemplate.Pdf;
#if !NET45
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.DependencyInjection;
#else
using System.Web.Mvc;
using System.Web;
#endif
using RazorEngine.Compilation.ImpromptuInterface.Optimization;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using JESAI.HtmlTemplate.Pdf.Utils; namespace Microsoft.AspNetCore.Mvc
{
public class PDFResult<T> : ActionResult where T:class
{
private const string ActionNameKey = "action";
public T Value { get; private set; }
public PDFResult(T value)
{
Value = value;
}
//public override async Task ExecuteResultAsync(ActionContext context)
// {
// var services = context.HttpContext.RequestServices;
// // var executor = services.GetRequiredService<IActionResultExecutor<PDFResult>>();
// //await executor.ExecuteAsync(context, new PDFResult(this));
// }
#if !NET45
private static string GetActionName(ActionContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
} if (!context.RouteData.Values.TryGetValue(ActionNameKey, out var routeValue))
{
return null;
} var actionDescriptor = context.ActionDescriptor;
string normalizedValue = null;
if (actionDescriptor.RouteValues.TryGetValue(ActionNameKey, out var value) &&
!string.IsNullOrEmpty(value))
{
normalizedValue = value;
} var stringRouteValue = Convert.ToString(routeValue, CultureInfo.InvariantCulture);
if (string.Equals(normalizedValue, stringRouteValue, StringComparison.OrdinalIgnoreCase))
{
return normalizedValue;
} return stringRouteValue;
}
#endif #if !NET45
public override async Task ExecuteResultAsync(ActionContext context)
{
var viewName = GetActionName(context);
var services = context.HttpContext.RequestServices;
var exportPdfByHtmlTemplate=services.GetService<IExportPdfByHtmlTemplate>();
var viewEngine=services.GetService<ICompositeViewEngine>();
var tempDataProvider = services.GetService<ITempDataProvider>();
var result = viewEngine.FindView(context, viewName, isMainPage: true);
#else
public override void ExecuteResult(ControllerContext context)
{
var viewName = context.RouteData.Values["action"].ToString();
var result = ViewEngines.Engines.FindView(context, viewName, null); IExportPdfByHtmlTemplate exportPdfByHtmlTemplate = new PdfByHtmlTemplateExporter ();
#endif
if (result.View == null)
throw new ArgumentException($"名称为:{viewName}的视图不存在,请检查!");
context.HttpContext.Response.ContentType = "application/pdf";
//context.HttpContext.Response.Headers.Add("Content-Disposition", "attachment; filename=test.pdf");
var html = "";
using (var stringWriter = new StringWriter())
{ #if !NET45
var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()) { Model = Value };
var viewContext = new ViewContext(context, result.View, viewDictionary, new TempDataDictionary(context.HttpContext, tempDataProvider), stringWriter, new HtmlHelperOptions()); await result.View.RenderAsync(viewContext);
#else
var viewDictionary = new ViewDataDictionary(new ModelStateDictionary()) { Model = Value };
var viewContext = new ViewContext(context, result.View, viewDictionary, context.Controller.TempData, stringWriter);
result.View.Render(viewContext, stringWriter);
result.ViewEngine.ReleaseView(context, result.View);
#endif
html = stringWriter.ToString(); }
//var tpl=File.ReadAllText(result.View.Path);
#if !NET45
byte[] buff=await exportPdfByHtmlTemplate.ExportByHtmlPersistAsync<T>(Value,html);
#else
byte[] buff = AsyncHelper.RunSync(() => exportPdfByHtmlTemplate.ExportByHtmlPersistAsync<T>(Value, html));
context.HttpContext.Response.BinaryWrite(buff);
context.HttpContext.Response.Flush();
context.HttpContext.Response.Close();
context.HttpContext.Response.End(); #endif #if !NET45
using (MemoryStream ms = new MemoryStream(buff))
{
byte[] buffer = new byte[0x1000];
while (true)
{
int count = ms.Read(buffer, , 0x1000);
if (count == )
{ return;
}
await context.HttpContext.Response.Body.WriteAsync(buffer, , count); }
}
#endif
}
}
}

PDF属性设置特性核心代码

#if NET461 ||NET45
using TuesPechkin;
using System.Drawing.Printing;
using static TuesPechkin.GlobalSettings;
#else
using DinkToPdf;
#endif
using System;
using System.Collections.Generic;
using System.Text; namespace JESAI.HtmlTemplate.Pdf
{
public class PdfExportAttribute:Attribute
{
#if !NET461 &&!NET45
/// <summary>
/// 方向
/// </summary>
public Orientation Orientation { get; set; } = Orientation.Landscape;
#else
/// <summary>
/// 方向
/// </summary>
public PaperOrientation Orientation { get; set; } = PaperOrientation.Portrait;
#endif /// <summary>
/// 纸张类型(默认A4,必须)
/// </summary>
public PaperKind PaperKind { get; set; } = PaperKind.A4; /// <summary>
/// 是否启用分页数
/// </summary>
public bool IsEnablePagesCount { get; set; } /// <summary>
/// 头部设置
/// </summary>
public HeaderSettings HeaderSettings { get; set; } /// <summary>
/// 底部设置
/// </summary>
public FooterSettings FooterSettings { get; set; }
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 服务器是否保存一份
/// </summary>
public bool IsEnableSaveFile { get; set; } = false;
/// <summary>
/// 保存路径
/// </summary>
public string SaveFileRootPath { get; set; } = "D:\\PdfFile";
/// <summary>
/// 是否缓存
/// </summary>
public bool IsEnableCache { get; set; } = false;
/// <summary>
/// 缓存有效时间
/// </summary>
public TimeSpan CacheTimeSpan { get; set; } = TimeSpan.FromMinutes();
}
}

4.使用方式

建立一个BaseController,在需要使用PDF渲染的地方继承BaseController

    public abstract class BaseComtroller:Controller
{
public virtual PDFResult<T> PDFResult<T>(T data) where T:class
{
return new PDFResult<T>(data);
}
}

  建一个model实体,可以使用PdfExport特性设置PDF的一些属性。

[PdfExport(PaperKind = PaperKind.A4)]
public class Student
{ public string Name { get; set; }
public string Class { get; set; }
public int Age { get; set; }
public string Address { get; set; }
public string Tel { get; set; }
public string Sex { get; set; }
public string Des { get; set; }
}

新建一个控制器和视图

 public class HomeController : BaseComtroller
{
private readonly ILogger<HomeController> _logger;
private readonly ICacheService _cache; public HomeController(ILogger<HomeController> logger, ICacheService cache)
{
_logger = logger;
_cache = cache;
} public IActionResult GetPDF()
{
var m = new Student()
{
Name = "",
Address = "",
Age = ,
Sex = "男",
Tel = "",
Des = ""
};
return PDFResult<Student>(m);
}
@{
Layout = null;
}
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <head>
<meta charset="utf-8" />
<title></title>
</head> <body>
<table border="" style="background-color:red;width:800px;height:500px;">
<tr>
<td>姓名</td>
<td>@Model.Name</td>
<td>性别</td>
<td>@Model.Sex</td>
</tr>
<tr>
<td>年龄</td>
<td>@Model.Age</td>
<td>班级</td>
<td>@Model.Class</td>
</tr>
<tr>
<td>住址</td>
<td>@Model.Address</td>
<td>电话</td>
<td>@Model.Tel</td>
</tr>
<tr>
<td clospan="">住址</td>
<td>@Model.Des</td>
</tr>
</table>
</body>
</html>

启用本项目插件,strup里面设置

   public void ConfigureServices(IServiceCollection services)
{
services.AddHtmlTemplateExportPdf();
services.AddControllersWithViews();
}

5.运行效果:

  

6.项目代码:

代码托管:https://gitee.com/Jesai/JESAI.HtmlTemplate.Pdf

希望看到的点个星星点个赞,写文章不容易,开源更不容易。同时希望本插件对你有所帮助。

补充:后面陆陆续续有人私下问我有没有电子签章的源码开源。在这里我只能告诉你们,电子签章这个东西是非常复杂的一个东西。暂时没有开源。我们也是用了第三方的服务。这里仅仅给大家看一下效果已经如果接入使用。项目里面有一个PDFCallResult 的ActionResult。

最新文章

  1. Markdown是怎样接管我的各种的写作工作的
  2. UploadFile控件,提交图片后,页面预览显示刚刚提交的图片
  3. opengl
  4. ListView13添加2
  5. python学习笔记整理——集合 set
  6. ManualResetEvent &amp; AutoResetEvent
  7. HTML+CSS学习笔记(2) - 认识标签(1)
  8. AJAX 控件集之TextBoxWatermark(水印文本框)控件
  9. UVA 11769 All Souls Night 的三维凸包要求的表面面积
  10. UVa 136 - Ugly Numbers
  11. 【转】flash air中读取本地文件的三种方法
  12. 一、源代码-面向CLR的编译器-托管模块-(元数据&amp;IL代码)
  13. 使用 Kubeadm 安装部署 Kubernetes 1.12.1 集群
  14. 【原创】逆向练习(CrackMe)
  15. Java(C#)基础差异-数组
  16. JavaScript Math Object 数字
  17. opencv 图片降噪
  18. Linux驱动程序接口
  19. 二、ansible配置简要介绍
  20. Android-各个屏幕的logo尺寸要求

热门文章

  1. 理解java容器底层原理--手动实现HashMap
  2. 关于Google下插件SwitchyOmega用法
  3. 在Thinkphp中微信公众号JsApi支付
  4. Python之numpy,pandas实践
  5. 自动化之SaltStack
  6. vue2.x学习笔记(三十二)
  7. Node.js快速创建一个访问html文件的服务器
  8. Mysql 查看被锁住的表
  9. JAVA_WEB--jsp概述
  10. 白话typescript中的【extends】和【infer】(含vue3的UnwrapRef)