调用 wkhtmltopdf 从 HTML 生成 PDF

Posted

技术标签:

【中文标题】调用 wkhtmltopdf 从 HTML 生成 PDF【英文标题】:Calling wkhtmltopdf to generate PDF from HTML 【发布时间】:2010-11-22 20:20:30 【问题描述】:

我正在尝试从 html 文件创建 PDF 文件。环顾四周后,我发现:wkhtmltopdf 是完美的。我需要从 ASP.NET 服务器调用这个 .exe。我尝试过:

    Process p = new Process();
    p.StartInfo.UseShellExecute = false;
    p.StartInfo.FileName = HttpContext.Current.Server.MapPath("wkhtmltopdf.exe");
    p.StartInfo.Arguments = "TestPDF.htm TestPDF.pdf";
    p.Start();
    p.WaitForExit();

在服务器上创建任何文件都没有成功。谁能给我一个正确方向的指针?我将 wkhtmltopdf.exe 文件放在站点的***目录中。还有其他地方应该举办吗?


编辑:如果有人有更好的解决方案从 html 动态创建 pdf 文件,请告诉我。

【问题讨论】:

您的应用程序是否会因此操作产生任何异常?命令行操作是否产生任何异常或错误? 不,它不会产生任何异常。我实际上看到命令提示符出现得非常快。如果我不输入:HttpContext.Current.Server.MapPath(),我会得到一个找不到文件的异常。 您可以使用 FileMon 或其他 sysinternals 工具查看未找到的文件。您是否也尝试过指定绝对路径? 见***.com/questions/tagged/pdf-generation。 【参考方案1】:

更新: 我在下面的回答是在磁盘上创建 pdf 文件。然后我将该文件作为下载流式传输到用户浏览器。考虑使用下面 Hath 的答案来让 wkhtml2pdf 输出到流,然后直接将其发送给用户 - 这将绕过文件权限等的许多问题。

我原来的答案: 确保您已指定 PDF 的输出路径,该路径可由服务器上运行的 IIS 的 ASP.NET 进程写入(我认为通常是 NETWORK_SERVICE)。

我的看起来像这样(并且有效):

/// <summary>
/// Convert Html page at a given URL to a PDF file using open-source tool wkhtml2pdf
/// </summary>
/// <param name="Url"></param>
/// <param name="outputFilename"></param>
/// <returns></returns>
public static bool HtmlToPdf(string Url, string outputFilename)

    // assemble destination PDF file name
    string filename = ConfigurationManager.AppSettings["ExportFilePath"] + "\\" + outputFilename + ".pdf";

    // get proj no for header
    Project project = new Project(int.Parse(outputFilename));

    var p = new System.Diagnostics.Process();
    p.StartInfo.FileName = ConfigurationManager.AppSettings["HtmlToPdfExePath"];

    string switches = "--print-media-type ";
    switches += "--margin-top 4mm --margin-bottom 4mm --margin-right 0mm --margin-left 0mm ";
    switches += "--page-size A4 ";
    switches += "--no-background ";
    switches += "--redirect-delay 100";

    p.StartInfo.Arguments = switches + " " + Url + " " + filename;

    p.StartInfo.UseShellExecute = false; // needs to be false in order to redirect output
    p.StartInfo.RedirectStandardOutput = true;
    p.StartInfo.RedirectStandardError = true;
    p.StartInfo.RedirectStandardInput = true; // redirect all 3, as it should be all 3 or none
    p.StartInfo.WorkingDirectory = StripFilenameFromFullPath(p.StartInfo.FileName);

    p.Start();

    // read the output here...
    string output = p.StandardOutput.ReadToEnd(); 

    // ...then wait n milliseconds for exit (as after exit, it can't read the output)
    p.WaitForExit(60000); 

    // read the exit code, close process
    int returnCode = p.ExitCode;
    p.Close(); 

    // if 0 or 2, it worked (not sure about other values, I want a better way to confirm this)
    return (returnCode == 0 || returnCode == 2);

【讨论】:

+1 感谢您的代码。这对我来说也很完美。你有没有发现更多关于返回码的信息? 不,我找不到关于它们的任何信息。试试 Google 代码上的 wkhtmltopdf 区域。 (如果这是您使用的答案,您可以将其作为稍后偶然发现此问题的其他有相同问题的人的答案) 'return (returnCode 我看不到这段代码在 IIS 上的工作方式。您将被拒绝访问,因为默认 IIS 用户帐户不允许执行 exe 文件。 +1 非常有帮助。非常感谢您发布此代码。不完全确定您需要 WaitForExit() 调用。 Start 永远不会立即为我返回...您在等待输出读出吗?【参考方案2】:

当我尝试将 msmq 与 Windows 服务一起使用时,我遇到了同样的问题,但由于某种原因它非常慢。 (过程部分)。

这终于奏效了:

private void DoDownload()

    var url = Request.Url.GetLeftPart(UriPartial.Authority) + "/CPCDownload.aspx?IsPDF=False?UserID=" + this.CurrentUser.UserID.ToString();
    var file = WKHtmlToPdf(url);
    if (file != null)
    
        Response.ContentType = "Application/pdf";
        Response.BinaryWrite(file);
        Response.End();
    


public byte[] WKHtmlToPdf(string url)

    var fileName = " - ";
    var wkhtmlDir = "C:\\Program Files\\wkhtmltopdf\\";
    var wkhtml = "C:\\Program Files\\wkhtmltopdf\\wkhtmltopdf.exe";
    var p = new Process();

    p.StartInfo.CreateNoWindow = true;
    p.StartInfo.RedirectStandardOutput = true;
    p.StartInfo.RedirectStandardError = true;
    p.StartInfo.RedirectStandardInput = true;
    p.StartInfo.UseShellExecute = false;
    p.StartInfo.FileName = wkhtml;
    p.StartInfo.WorkingDirectory = wkhtmlDir;

    string switches = "";
    switches += "--print-media-type ";
    switches += "--margin-top 10mm --margin-bottom 10mm --margin-right 10mm --margin-left 10mm ";
    switches += "--page-size Letter ";
    p.StartInfo.Arguments = switches + " " + url + " " + fileName;
    p.Start();

    //read output
    byte[] buffer = new byte[32768];
    byte[] file;
    using(var ms = new MemoryStream())
    
        while(true)
        
            int read =  p.StandardOutput.BaseStream.Read(buffer, 0,buffer.Length);

            if(read <=0)
            
                break;
            
            ms.Write(buffer, 0, read);
        
        file = ms.ToArray();
    

    // wait or exit
    p.WaitForExit(60000);

    // read the exit code, close process
    int returnCode = p.ExitCode;
    p.Close();

    return returnCode == 0 ? file : null;

感谢格雷厄姆·安布罗斯和其他所有人。

【讨论】:

我正在尝试测试您的解决方案,如果它有效,这对我会有很大帮助..但是我想将我的 .aspx 转换为 pdf 而不是 url,是否可以采用相同的方式?所以我用这个改变了你的var:var url = HttpContext.Current.Server.MapPath("~/wkhtmltopdf/chartImage.aspx");但它没有工作 @astrocybernaute aspx 需要一个服务器来从中生成 html,因此您需要使用服务器而不是直接调用它:)【参考方案3】:

好的,这是一个古老的问题,但很好。由于我没有找到一个好的答案,我自己做了:) Also, I've posted this super simple project to GitHub.

这里是一些示例代码:

var pdfData = HtmlToXConverter.ConvertToPdf("<h1>SOO COOL!</h1>");

以下是一些要点:

没有 P/Invoke 不创建新进程 没有文件系统(全部在 RAM 中) 带有智能感知等的原生 .NET DLL 能够生成 PDF 或 PNG (HtmlToXConverter.ConvertToPng)

【讨论】:

我不确定为什么每个人都没有三重盯着您的解决方案,这是每个人都在寻找的。取原始的 c 应用程序并将其转换为在内存中运行并返回一个字节数组。出色的工作! Nuget 包总是无法安装和编译的 dll 总是给出缺少程序集或参考的错误 @LifeH2O 哪个 nuget 包?这个项目我看不到。 @LifeH2O 谢谢。我安装同样失败:( @LifeH2O Nuget 不起作用。尝试安装时出现以下错误:pastebin.com/9RVxTeB3【参考方案4】:

查看 wkhtmltopdf 库的 C# 包装器库(使用 P/Invoke):https://github.com/pruiz/WkHtmlToXSharp

【讨论】:

【参考方案5】:

这通常是一个坏主意,原因有很多。如果发生崩溃,您将如何控制生成但最终存在于内存中的可执行文件?拒绝服务攻击,或者如果恶意软件进入 TestPDF.htm 会怎样?

我的理解是 ASP.NET 用户帐户将没有本地登录的权限。它还需要具有正确的文件权限才能访问可执行文件并写入文件系统。您需要编辑本地安全策略并让 ASP.NET 用户帐户(可能是 ASPNET)在本地登录(可能默认在拒绝列表中)。然后您需要编辑 NTFS 文件系统上其他文件的权限。如果您在共享主机环境中,可能无法应用您需要的配置。

使用像这样的外部可执行文件的最佳方法是从 ASP.NET 代码中对作业进行排队,并让某种服务监控队列。如果你这样做,你将保护自己免受各种坏事的影响。在我看来,更改用户帐户的维护问题不值得付出努力,虽然设置服务或计划工作很痛苦,但它只是一个更好的设计。 ASP.NET 页面应该轮询输出的结果队列,您可以向用户显示等待页面。在大多数情况下这是可以接受的。

【讨论】:

嗨,明白。你能推荐一个更好的方法吗? MSMQ + Windows 服务是通用方法。 要跟进,要么四处搜索,要么我在这里简要描述一下:***.com/questions/1317641/… MSMQ + Windows 服务是一种特定的方法。如果您不知道如何使用 MSMQ 或不想依赖它,您通常可以使用 SQL Server 实现某些东西。一般要寻找的是排队系统,MSMQ 只是其中之一。 您可能不应该给 ASP.NET 用户帐户任何额外的权限,这可能是一个安全问题。如果可能的话,您应该只为这个操作模拟,创建一个权限非常有限的特殊帐户。【参考方案6】:

您可以通过指定“-”作为输出文件来告诉 wkhtmltopdf 将其输出发送到 sout。 然后,您可以将进程的输出读入响应流,并避免写入文件系统的权限问题。

【讨论】:

【参考方案7】:

我对 2018 年的事情的看法。

我正在使用异步。我正在与 wkhtmltopdf 进行流式传输。我创建了一个新的 StreamWriter,因为 wkhtmltopdf 默认需要 utf-8,但在进程启动时它被设置为其他值。

我没有包含很多参数,因为这些参数因用户而异。您可以使用 additionalArgs 添加您需要的内容。

我删除了 p.WaitForExit(...),因为如果它失败了我没有处理它,它无论如何都会挂在 await tStandardOutput 上。如果需要超时,那么您必须使用取消令牌或超时在不同的任务上调用 Wait(...) 并进行相应处理。

public async Task<byte[]> GeneratePdf(string html, string additionalArgs)

    ProcessStartInfo psi = new ProcessStartInfo
    
        FileName = @"C:\Program Files\wkhtmltopdf\wkhtmltopdf.exe",
        UseShellExecute = false,
        CreateNoWindow = true,
        RedirectStandardInput = true,
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        Arguments = "-q -n " + additionalArgs + " - -";
    ;

    using (var p = Process.Start(psi))
    using (var pdfSream = new MemoryStream())
    using (var utf8Writer = new StreamWriter(p.StandardInput.BaseStream, 
                                             Encoding.UTF8))
    
        await utf8Writer.WriteAsync(html);
        utf8Writer.Close();
        var tStdOut = p.StandardOutput.BaseStream.CopyToAsync(pdfSream);
        var tStdError = p.StandardError.ReadToEndAsync();

        await tStandardOutput;
        string errors = await tStandardError;

        if (!string.IsNullOrEmpty(errors))  /* deal/log with errors */ 

        return pdfSream.ToArray();
    

我没有包含在其中的东西,但如果你有图像、css 或其他东西,wkhtmltopdf 在呈现 html 页面时必须加载:

您可以使用 --cookie 传递身份验证 cookie 在html页面的头部,可以设置base标签,href指向服务器,wkhtmltopdf会根据需要使用

【讨论】:

【参考方案8】:

感谢以上所有 cmets 的问题/回答。当我为 WKHTMLtoPDF 编写自己的 C# 包装器时,我遇到了这个问题,它回答了我遇到的几个问题。我最终在一篇博文中写到了这一点——其中还包含了我的包装(毫无疑问,你会从上面的条目中看到“灵感”渗入我的代码......)

Making PDFs from HTML in C# using WKHTMLtoPDF

再次感谢大家!

【讨论】:

【参考方案9】:

ASP .Net 进程可能没有对该目录的写入权限。

试着告诉它写信给%TEMP%,看看它是否有效。

另外,让您的 ASP .Net 页面回显进程的标准输出和标准错误,并检查错误消息。

【讨论】:

不确定,不是我。不过感谢您的信息,将对其进行测试。看来我应该采用不同的方式从 html 创建 pdf 文件。 有 .NET 包装器,csharp-source.net/open-source/pdf-libraries 来自快速谷歌搜索【参考方案10】:

如果pdf文件创建正确且正确,通常返回代码=0。如果没有创建,则该值在-ve范围内。

【讨论】:

【参考方案11】:
using System;
using System.Diagnostics;
using System.Web;

public partial class pdftest : System.Web.UI.Page

    protected void Page_Load(object sender, EventArgs e)
    

    
    private void fn_test()
    
        try
        
            string url = HttpContext.Current.Request.Url.AbsoluteUri;
            Response.Write(url);
            ProcessStartInfo startInfo = new ProcessStartInfo();
            startInfo.FileName = 
                @"C:\PROGRA~1\WKHTML~1\wkhtmltopdf.exe";//"wkhtmltopdf.exe";
            startInfo.Arguments = url + @" C:\test"
                 + Guid.NewGuid().ToString() + ".pdf";
            Process.Start(startInfo);
        
        catch (Exception ex)
        
            string xx = ex.Message.ToString();
            Response.Write("<br>" + xx);
        
    
    protected void btn_test_Click(object sender, EventArgs e)
    
        fn_test();
    

【讨论】:

以上是关于调用 wkhtmltopdf 从 HTML 生成 PDF的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 wkhtmltopdf 获取 html 生成的 pdf 中的页码

C# html生成PDF遇到的问题,从iTextSharp到wkhtmltopdf

《html转pdf-----wkhtmltopdf踩坑总结》

Java操作wkhtmltopdf实现Html转PDF

WKHTMLTOPDF 从左边距截断文本

java调用wkhtmltopdf生成pdf文件,美观,省事