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。

Asp.net Core 3.1 Razor视图模版动态渲染PDF的更多相关文章

  1. Asp.net MVC Razor视图模版动态渲染PDF,Razor模版生成静态Html

    Asp.net MVC Razor视图模版动态渲染PDF,Razor模版生成静态Html 1.前言 上一篇文章我开源了轮子,Asp.net Core 3.1 Razor视图模版动态渲染PDF,然后,很 ...

  2. ASP.NET Core 3.x Razor视图运行时刷新实时编译

    前言: 很长一段时间没有写过ASP.NET Core Razor(.cshtml)视图开发WEB页面了,今天刚好把之前做的一个由ASP.NET Core 2.2+Razor开发的项目升级到ASP.NE ...

  3. ASP.NET Core中使用Razor视图引擎渲染视图为字符串

    一.前言 在有些项目需求上或许需要根据模板生产静态页面,那么你一样可以用Razor语法去直接解析你的页面从而把解析的页面生成静态页,这样的使用场景很多,不限于生成静态页面,视图引擎为我们提供了模型到视 ...

  4. ASP.NET Core中使用Razor视图引擎渲染视图为字符串(转)

    一.视图渲染说明 在有些项目需求上或许需要根据模板生产静态页面,那么你一样可以用Razor语法去直接解析你的页面从而把解析的页面生成静态页,这样的使用场景很多,不限于生成静态页面,视图引擎为我们提供了 ...

  5. ASP.NET Core MVC的Razor视图中,使用Html.Raw方法输出原生的html

    我们在ASP.NET Core MVC项目中,有一个Razor视图文件Index.cshtml,如下: @{ Layout = null; } <!DOCTYPE html> <ht ...

  6. ASP.NET Core 3.1 Razor 视图预编译、动态编译

    1.安装NuGet包 Install-Package Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation 2.Startup.cs 配置 public ...

  7. 007.Adding a view to an ASP.NET Core MVC app -- 【在asp.net core mvc中添加视图】

    Adding a view to an ASP.NET Core MVC app 在asp.net core mvc中添加视图 2017-3-4 7 分钟阅读时长 本文内容 1.Changing vi ...

  8. 【翻译】介绍 ASP.NET Core 中的 Razor Pages

    介绍 ASP.NET Core 中的 Razor Pages 原文地址:Introduction to Razor Pages in ASP.NET Core         译文地址:介绍 asp. ...

  9. Core中使用Razor视图引擎渲染视图为字符串 阅读目录

    Core中使用Razor视图引擎渲染视图为字符串 } <!DOCTYPE html> <html> <head> <title>Render view ...

随机推荐

  1. jquery动态live绑定toggle事件

    $(".btn").live("click",function(){ $(this).toggle( function () { //事件 1 console. ...

  2. 解决linux(ubuntu18)下无法挂载ntfs磁盘,并读写挂载硬盘

    首先需要有ntfs-3g,没有的话sudo apt-get install ntfs-3g 挂载硬盘: chen@ilaptop:/$ sudo mount -o rw,remount /dev/sd ...

  3. 虚拟化KVM之安装(二)

    安装KVM虚拟化 1.系统基础环境: [root@linux-node1 ~]# ip addr | grep inet | awk '{ print $2; }' | sed 's/\/.*$//' ...

  4. Vue 2.x折腾记 - (17) 基于Ant Design Vue 封装一个配置式的表单组件

    前言 写了个类似上篇搜索的封装,但是要考虑的东西更多. 具体业务比展示的代码要复杂,篇幅太长就不引入了. 效果图 2019-04-25 添加了下拉多选的渲染,并搜索默认过滤文本而非值 简化了渲染的子组 ...

  5. 【BUG之group_concat默认长度限制】

    2019独角兽企业重金招聘Python工程师标准>>> 问题:mysql数据库使用group_concat将多个id组成字符串数组,一共200个,到160个被截断: 原因:mysql ...

  6. mpvue开发微信小程序之时间+日期选择器

    最近在做微信小程序,技术栈为mpvue+iview weapp组件库. 因项目需求,要用到日期+时间选择器,iview组件库目前还未提供时间日期选择器的组件,小程序官方组件日期时间也是分开的,在简书上 ...

  7. Codeforce-Ozon Tech Challenge 2020-A. Kuroni and the Gifts

    the i-th necklace has a brightness ai, where all the ai are pairwise distinct (i.e. all ai are diffe ...

  8. Acmer 仅以此纪念最痛苦的一天

    今天打比赛,完全不在状态,看到别人又AK了,自己心里真TM不是个滋味,我为什么这么弱,菜鸡,每天都在水题,我的人生也是这么水?伪学习?没有学习能力,这不只是队伍的问题,是自己的问题,别人平均3题我们队 ...

  9. 图论-网络流-Dinic (邻接表版)

    //RQ的板子真的很好用 #include<cstdio> #include<cstring> #include<queue> #define INF 1e9 us ...

  10. requests抓取数据示例

    1:获取豆瓣电影名称及评分 # 抓取豆瓣电影名称及评分 url="https://movie.douban.com/j/search_subjects" start=input(& ...