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. Mybatis 使用 SQL 递归获取单表中的树结构

    xml 代码 <resultMap type="xxx.xxx.xxx.xxx.实体类" id="xxxListTree"> <result ...

  2. Excel中的clean函数

    纯属note. 之前经常用excel处理数据的时候,对长文本或网站上拉取的值都会用clean函数清除一些我们肉眼看不到的非打印字符. Excel官方介绍:clean 删除文本中的所有非打印字符. 此次 ...

  3. google proto buf学习

    2019独角兽企业重金招聘Python工程师标准>>> protobuf是Google开发的一个序列化框架,类似XML,JSON,基于二进制,比传统的XML表示同样一段内容要短小得多 ...

  4. Fiddler 介绍

    1.fiddler原理介绍 fiddler 是一个抓包工具,当浏览器访问服务器会形成一个请求,此时,fiddler就处于请求之间,当浏览器发送请求,会先经过 fiddler,然后在到服务器:当服务器有 ...

  5. 关于SQL Server中存储过程在C#中调用的简单示例

    目录 0. 简介 1. 语法细节 2. 示例1:模拟转账 3. 示例2:测试返回DataTable 4. 源代码下载 shanzm-2020年5月3日 23:23:44 0. 简介 [定义]:存储过程 ...

  6. varnish 项目实战

    1.工作原理 在当前主流的Web服务架构体系中,Cache担任着越来越重要的作用.常见的基于浏览器的C/S架构,Web Cache更是节约服务器资源的关键.而最近几年由FreeBSD创始人之一Kamp ...

  7. 【Java8新特性】一张图带你领略Java8有哪些新特性

    写在前面 很多小伙伴留言说,冰河你能不能写一些关于Java8的文章呢,看书看不下去,看视频进度太慢.好吧,看到不少读者对Java8还是比较陌生的,那我就写一些关于Java8的文章吧,希望对大家有所帮助 ...

  8. OSG程序设计之osg::NodeVisitor

    本文所有内容来自<OpenSceneGraph三维渲染引擎设计与实践>一书. 本文主要讨论的是OSG中节点的访问. 对于节点的访问是从节点接收一个访问器开始的,用户执行某个节点的accep ...

  9. 数据结构之递归Demo(走迷宫)(八皇后)(汉诺塔)

    递归 顾名思义,递归就是递归就是递归就是递归就是递归......就是递归 Google递归:

  10. D - 小Z的加油店 线段树+差分+GCD

    D - 小Z的加油店 HYSBZ - 5028   这个题目是一个线段树+差分+GCD 推荐一个差分的博客:https://www.cnblogs.com/cjoierljl/p/8728110.ht ...