zoukankan      html  css  js  c++  java
  • Asp.net Core 3.1 Razor视图模版动态渲染PDF

    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, 0, 0x1000);
                        if (count == 0)
                        {
    
                            return;
                        }
                        await context.HttpContext.Response.Body.WriteAsync(buffer, 0, 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(30);
        }
    }

     

    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 = "111111",
                    Address = "3333333",
                    Age = 22,
                    Sex = "",
                    Tel = "19927352816",
                    Des = "2222222222222222222"
                };
                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="1" style="background-color:red;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="2">住址</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。

     

     

     

     

  • 相关阅读:
    Solution -「Gym 102798I」Sean the Cuber
    Solution -「Gym 102798K」Tree Tweaking
    Solution -「Gym 102798E」So Many Possibilities...
    Solution -「Gym 102759I」Query On A Tree 17
    Solution -「Gym 102759G」LCS 8
    Solution -「Gym 102759F」Interval Graph
    Solution -「Gym 102759C」Economic One-way Roads
    Solution -「ABC 213G」Connectivity 2
    Solution -「ABC 213H」Stroll
    @WebFilter注入失败
  • 原文地址:https://www.cnblogs.com/dengjiahai/p/12915390.html
Copyright © 2011-2022 走看看