调用wkhtmltopdf从HTML生成PDF

Sea*_*ean 53 html pdf asp.net 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();
Run Code Online (Sandbox Code Playgroud)

没有成功在服务器上创建任何文件.任何人都可以给我指向正确的方向吗?我将wkhtmltopdf.exe文件放在站点的顶级目录中.应该举行其他任何地方吗?


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

MGO*_*wen 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);
}
Run Code Online (Sandbox Code Playgroud)

  • 'return(returnCode <= 2)'应该是'return(returnCode == 0 || returnCode == 2)',因为如果输出文件已经存在,你将收到'1',所以在执行进程之前检查. (3认同)

Hat*_*ath 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;
}
Run Code Online (Sandbox Code Playgroud)

谢谢格雷厄姆安布罗斯和其他所有人.

  • @astrocybernaute aspx需要一个服务器来生成html所以你需要使用服务器而不是直接调用它:) (2认同)

Tim*_*uri 17

好的,所以这是一个老问题,但是一个很好的问题.由于我没有找到一个好的答案,我自己做了:) 另外,我已经将这个超级简单的项目发布到了GitHub.

以下是一些示例代码:

var pdfData = HtmlToXConverter.ConvertToPdf("<h1>SOO COOL!</h1>");
Run Code Online (Sandbox Code Playgroud)

以下是一些要点:

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

  • 我不确定为什么每个人都不会盯着你的解决方案,这是每个人都在寻找的.获取原始的c应用程序并将其转换为在内存中运行并返回一个字节数组.优秀作品! (2认同)

小智 7

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


Bri*_*tle 5

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

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

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


Gra*_*ose 5

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


SLa*_*aks 0

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

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

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