在ASP.NET WebForms中使用 Chrome将HTML转换为PDF

目录

基本理念

重要的CSS属性

1. 将页边距设置为0(zero)

2. 设置纸张大小

3. 以固定宽度和边距将所有内容包装在DIV内

4. 使用“分页后”的CSS在页面之间拆分

5. 所有字体必须已安装或托管在您的网站中

6. 图片的URL链接,外部css样式表引用必须包含根路径。

完整网页示例

背后的C#代码


*注意:请使用Microsoft Edge作为更好的选择。阅读更多: https://blog.csdn.net/mzl87/article/details/130726582

使用Chrome.exe作为HTMLPDF转换器有一些缺点。

主要缺点是执行EXE的权限。由于安全问题,网络托管环境将禁止直接执行任何EXE,包括Chrome.exe

如果尝试在本地IIS上运行此程序,则必须将应用程序池的标识设置为本地系统,以允许池运行外部 EXE

尽管仍然可以通过网络服务器运行 Chrome.exe,但强烈建议不要这样做。这就是为什么与Chrome.exe相比,使用Microsoft Edge是一个非常好的选择。

阅读更多关于使用Microsoft Edge将HTML转换为PDF​​​​​​​

基本理念

Chrome有一个内置功能,用于为HTML页面生成PDF

根据我在进行这项研究时收集的信息,所有基于chromium的网络浏览器的工作方式都相同,但我还没有在其他基于chromium的网络浏览器上测试过这一点。

以下是运行Chrome.exe的基本命令行以生成带有参数/开关的PDF

chrome.exe

// arguments:
--headless
--disable-gpu
--run-all-compositor-stages-before-draw
--print-to-pdf="{filePath}"
{url}

完整的命令行示例:

C:\Program Files\Google\Chrome\Application\chrome.exe --headless 
--disable-gpu --run-all-compositor-stages-before-draw 
--print-to-pdf="D:\test\web_pdf\pdf_chrome\temp\pdf\345555635.pdf" 
http://localhost:55977/temp/pdf/345555635.html

基于此,我编写了一个简单的C#类库来自动执行此过程。

您现在可以在一行简单的行中生成PDF

这会将PDF作为附件传输以供下载:

pdf.GeneratePdfAttachment(html, "file.pdf");

这将在浏览器中打开PDF

pdf.GeneratePdfInline(html);

是的,完成了。就这样。

好的,让我们深入了解一些重要的细节。

重要的CSS属性

您必须在HTML页面中包含一些必要的CSS才能正常工作。

  1. 将页边距设置为0(zero)
  2. 设置纸张大小
  3. 将所有内容换行在具有固定宽度和边距的“div”内
  4. 使用page-break-always的CSS在页面之间拆分。
  5. 所有字体必须已安装或托管在您的网站上
  6. 图像的URL链接,外部css样式表引用必须包含根路径。

1. 将页边距设置为0zero

@page {
    margin: 0;
}

这样做的目的是隐藏页眉和页脚:

2. 设置纸张大小

1

@page {
    margin: 0;
    size: A4 portrait;
}

2

@page {
    margin: 0;
    size: letter landscape;
}

3

自定义尺寸(英寸)*宽度然后高度

@page {
    margin: 0;
    size: 4in 6in;
}

4

自定义尺寸(厘米)*宽然后高

@page {
    margin: 0;
    size: 14cm 14cm;
}

有关@pageCSS的更多选项/信息,您可以参考此链接。

3. 以固定宽度和边距将所有内容包装在DIV

例子:

<div class="page">
    <h1>Page 1</h1>
    <img src="/pdf.jpg" style="width: 100%; height: auto;" />
    <!-- The rest of the body content -->
</div>

使用类page设置样式div"(充当主块/包装器/容器)。由于页面的边距为零,我们需要在CSS中手动指定上边距:

.page {
    width: 18cm;
    margin: auto;
    margin-top: 10mm;
}

必须指定宽度。

margin: autodiv块水平对齐居中。

"margin-top: 10mm,将在主块和顶部纸张边缘之间提供空间。

4. 使用分页后CSS在页面之间拆分

要拆分页面,请使用div和样式,CSSpage-break-after

page-break-after: always

例子:

<div class="page">
    <h1>Page 1</h1>
    <img src="/pdf.jpg" style="width: 100%; height: auto;" />
</div>

<div style="page-break-after: always"></div>

<div class="page">
    <h1>Page 2</h1>
    <img src="/pdf.jpg" style="width: 100%; height: auto;" />
</div>

<div style="page-break-after: always"></div>

<div class="page">
    <h1>Page 3</h1>
    <img src="/pdf.jpg" style="width: 100%; height: auto;" />
</div>

5. 所有字体必须已安装或托管在您的网站中

如果字体托管在第三方的服务器上,则字体呈现可能无法正常工作,例如:Google 字体。尝试将字体安装到服务器Windows操作系统中或在您的网站中托管字体。

6. 图片的URL链接,外部css样式表引用必须包含根路径。

例如,以下img标记可能无法正确呈现。图像可能会在最终渲染的PDF输出中丢失。

<img src="logo.png" />

相反,请包含如下所示的根路径:

<img src="/logo.png" />

<img src="/images/logo.png" />

完整网页示例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style type="text/css">
        h1 {
            margin: 0;
            padding: 0;
        }
        .page {
            margin: auto;
            margin-top: 10mm;
            border: 1px solid black;
            width: 18cm;
            height: 27cm;
        }

        @page {
            margin: 0;
            size: A4 portrait;
        }
    </style>
</head>

<body>

    <div class="page">
        <h1>Page 1</h1>
        <img src="/pdf.jpg" style="width: 100%; height: auto;" />
    </div>

    <div style="page-break-after: always"></div>

    <div class="page">
        <h1>Page 2</h1>
        <img src="/pdf.jpg" style="width: 100%; height: auto;" />
    </div>

    <div style="page-break-after: always"></div>

    <div class="page">
        <h1>Page 3</h1>
        <img src="/pdf.jpg" style="width: 100%; height: auto;" />
    </div>

</body>

</html>

背后的C#代码

在这里,我将解释代码是如何工作的。

准备主要方法:

public static void GeneratePdf(string url, string filePath)
{

}

确定 Chrome .exe的文件路径。将其定位在两个常见位置:

public static void GeneratePdf(string url, string filePath)
{
    var chromePath = @"C:\Program Files\Google\Chrome\Application\chrome.exe";

    if (!File.Exists(chromePath))
    {
        string userfolder = Environment.GetFolderPath
                            (Environment.SpecialFolder.UserProfile);
        chromePath = 
        $@"{userfolder}\AppData\Local\Google\Chrome\Application\chrome.exe";
    }

    if (!File.Exists(chromePath))
    {
        throw new Exception("Unable to locate Chrome.exe");
    }
}

添加以下using语句:

using System.Diagnostics;
using System.IO;

启动一个process来运行Chrome.exe并带有参数:

public static void GeneratePdf(string url, string pdfFilePath)
{
    var chromePath = @"C:\Program Files\Google\Chrome\Application\chrome.exe";

    if (!File.Exists(chromePath))
    {
        string userfolder = Environment.GetFolderPath
                            (Environment.SpecialFolder.UserProfile);
        chromePath = 
        $@"{userfolder}\AppData\Local\Google\Chrome\Application\chrome.exe";
    }

    if (!File.Exists(chromePath))
    {
        throw new Exception("Unable to locate Chrome.exe");
    }

    using (var p = new Process())
    {
        p.StartInfo.FileName = chromePath;
        p.StartInfo.Arguments = $"--headless --disable-gpu 
        --run-all-compositor-stages-before-draw --print-to-pdf=\"{pdfFilePath}\" {url}";
        p.Start();
        p.WaitForExit();
    }
}

以上将生成PDF文件。

Chrome.exe 需要一个网址来生成PDF文件。

因此,以下代码将生成所需的URL

准备一个enum变量:

public enum TransmitMethod
{
    None,
    Attachment,
    Inline
}

准备为 Chrome.exe生成URL的方法。

我们将该方法称为ChromePublish

static void ChromePublish(string html, TransmitMethod transmitMethod, string filename)
{
    
}

在方法ChromePublish中,首先创建一个临时目录来保存临时文件:

string folderTemp = HttpContext.Current.Server.MapPath("~/temp/pdf");

if (!Directory.Exists(folderTemp))
{
    Directory.CreateDirectory(folderTemp);
}

然后,为两个临时文件(HTMLPDF)创建文件名和路径

Random rd = new Random();

string randomstr = rd.Next(100000000, int.MaxValue).ToString();

string fileHtml = HttpContext.Current.Server.MapPath($"~/temp/pdf/{randomstr}.html");
string filePdf = HttpContext.Current.Server.MapPath($"~/temp/pdf/{randomstr}.pdf");

生成HTML文件并将其保存在本地:

File.WriteAllText(fileHtml, html);

获取HTML文件的URL

var r = HttpContext.Current.Request.Url;
string url = $"{r.Scheme}://{r.Host}:{r.Port}/temp/pdf/{randomstr}.html";

执行我们之前创建的方法在本地服务器上生成PDF文件:

GeneratePdf(url, filePdf);

获取文件大小:

FileInfo fi = new FileInfo(filePdf);
string filelength = fi.Length.ToString();

PDF文件加载到字节数组中:

byte[] ba = File.ReadAllBytes(filePdf);

从服务器中删除(清理)临时文件,不再需要它们:

try
{
    File.Delete(filePdf);
}
catch { }

try
{
    File.Delete(fileHtml);
}
catch { }

接下来,准备要传输的PDF

清除响应的所有内容:

HttpContext.Current.Response.Clear();

在响应标头中指定Content-Disposition的类型。

在此处阅读更多内容。

if (transmitMethod == TransmitMethod.Inline)
{
    HttpContext.Current.Response.AddHeader("Content-Disposition", "inline");
}
else if (transmitMethod == TransmitMethod.Attachment)
{
    HttpContext.Current.Response.AddHeader("Content-Disposition", 
         $"attachment; filename=\"{filename}\"");
}

最后,传输数据(PDF):

HttpContext.Current.Response.ContentType = "application/pdf";
HttpContext.Current.Response.AddHeader("Content-Length", filelength);
HttpContext.Current.Response.BinaryWrite(ba);
HttpContext.Current.Response.End();

以下是完整代码:

static void ChromePublish(string html, TransmitMethod transmitMethod, string filename)
{
    string folderTemp = HttpContext.Current.Server.MapPath("~/temp/pdf");

    if (!Directory.Exists(folderTemp))
    {
        Directory.CreateDirectory(folderTemp);
    }

    Random rd = new Random();

    string randomstr = rd.Next(100000000, int.MaxValue).ToString();

    string fileHtml = HttpContext.Current.Server.MapPath($"~/temp/pdf/{randomstr}.html");
    string filePdf = HttpContext.Current.Server.MapPath($"~/temp/pdf/{randomstr}.pdf");

    File.WriteAllText(fileHtml, html);

    var r = HttpContext.Current.Request.Url;
    string url = $"{r.Scheme}://{r.Host}:{r.Port}/temp/pdf/{randomstr}.html";

    GeneratePdf(url, filePdf);

    FileInfo fi = new FileInfo(filePdf);
    string filelength = fi.Length.ToString();
    byte[] ba = File.ReadAllBytes(filePdf);

    try
    {
        File.Delete(filePdf);
    }
    catch { }

    try
    {
        File.Delete(fileHtml);
    }
    catch { }

    HttpContext.Current.Response.Clear();

    if (transmitMethod == TransmitMethod.Inline)
        HttpContext.Current.Response.AddHeader("Content-Disposition", "inline");
    else if (transmitMethod == TransmitMethod.Attachment)
        HttpContext.Current.Response.AddHeader
        ("Content-Disposition", $"attachment; filename=\"{filename}\"");

    HttpContext.Current.Response.ContentType = "application/pdf";
    HttpContext.Current.Response.AddHeader("Content-Length", filelength);
    HttpContext.Current.Response.BinaryWrite(ba);
    HttpContext.Current.Response.End();
}

最后,创建两个简单的方法来环绕方法ChromePublish()

public static void GeneratePdfInline(string html)
{
    ChromePublish(html, TransmitMethod.Inline, null);
}

public static void GeneratePdfAttachment(string html, string filenameWithPdf)
{
    ChromePublish(html, TransmitMethod.Attachment, filenameWithPdf);
}

由于生成PDF可能需要几秒钟,因此您还可以考虑在生成PDF时向用户显示Loading消息。

例如:

<div id="divLoading" class="divLoading" onclick="hideLoading();">
    <img src="loading.gif" /><br />
    Generating PDF...
</div>

设置div消息框的样式:

.divLoading {
    width: 360px;
    font-size: 20pt;
    font-style: italic;
    font-family: Arial;
    z-index: 9;
    position: fixed;
    top: calc(50vh - 150px);
    left: calc(50vw - 130px);
    border: 10px solid #7591ef;
    border-radius: 25px;
    padding: 10px;
    text-align: center;
    background: #dce5ff;
    display: none;
    font-weight: bold;
}

下面是它的外观:

下面是显示加载消息的JavaScript

<script type="text/javascript">
    function showLoading() {
        let d = document.getElementById("divLoading");
        d.style.display = "block";
        setTimeout(hideLoading, 2000);
    }

    function hideLoading() {
        let d = document.getElementById("divLoading");
        d.style.display = "none";
    }
</script>

在用户单击按钮以生成PDF时显示消息。将以下属性添加到按钮以执行javascript

OnClientClick="showLoading();"

例子:

<asp:Button ID="btGeneratePdfAttachment" runat="server"
    Text="Generate PDF (download as attachment)" 
    OnClick="btGeneratePdfAttachment_Click" 
    OnClientClick="showLoading();" />

您可以下载本文的源代码,以更好地了解整个事情的实际工作原理。

感谢您的阅读和快乐编码!!

https://www.codeproject.com/Articles/5347275/Convert-HTML-to-PDF-with-Chrome-in-ASP-NET-WebForm

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值