调用wkhtmltopdf从HTML生成PDF

时间:2009-08-26 01:35:56

标签: html asp.net pdf pdf-generation wkhtmltopdf

我正在尝试从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文件,请告知我们。

11 个答案:

答案 0 :(得分:51)

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

我原来的回答:
确保您已为服务器上运行的IIS的ASP.NET进程(通常为NETWORK_SERVICE)编写了可写入的PDF的输出路径。

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

/// <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 :(得分:41)

当我尝试将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;
}

感谢Graham Ambrose和其他所有人。

答案 2 :(得分:16)

好的,所以这是一个老问题,但是一个很好的问题。由于我没有找到一个好的答案,我自己制作了:) 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

答案 3 :(得分:7)

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

答案 4 :(得分:5)

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

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

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

答案 5 :(得分:5)

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

答案 6 :(得分:2)

感谢上述问题/答案/所有评论。当我为WKHTMLtoPDF编写自己的C#包装器时,我遇到了这个问题,它回答了我遇到的一些问题。我最后在博文中写了这篇文章 - 其中也包含了我的包装器(你无疑会看到上面条目中的“灵感”渗透到我的代码中......)

http://icanmakethiswork.blogspot.de/2012/04/making-pdfs-from-html-in-c-using.html

再次感谢你们!

答案 7 :(得分:2)

我对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或其他在呈现html页面时必须加载wkhtmltopdf的内容,可能会很有用:

  • 您可以使用--cookie
  • 传递身份验证cookie
  • 在html页面的标题中,您可以使用指向服务器的href设置基本标签,并且wkhtmltopdf会在需要时使用该标签

答案 8 :(得分:0)

ASP .Net进程可能没有对目录的写访问权。

尝试告诉它写入%TEMP%,看看它是否有效。

另外,让你的ASP .Net页面回显进程的stdout和stderr,并检查错误消息。

答案 9 :(得分:0)

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

答案 10 :(得分:-1)

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();
    }
}