web-dev-qa-db-ja.com

wkhtmltopdfを呼び出して、HTMLからPDF

私は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ファイルを動的に作成するためのより良い解決策を持っているなら、私に知らせてください。

52
Sean

更新:
以下の私の答えは、ディスク上にpdfファイルを作成します。次に、そのファイルをダウンロードとしてユーザーのブラウザーにストリーミングしました。以下のHathの答えのようなものを使用して、代わりにwkhtml2pdfをストリームに出力し、それをユーザーに直接送信することを検討してください-ファイル許可などの多くの問題を回避します。

私の元の答え:
PDFの出力パスを指定したことを確認してください。このパスは、サーバーで実行されているIIS (通常は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);
}
51
MGOwen

Windowsサービスでmsmqを使用しようとしたときに同じ問題が発生しましたが、何らかの理由で非常に低速でした。 (プロセス部分)。

これが最終的に機能したものです:

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

グラハム・アンブローズと他の皆さんに感謝します。

41
Hath

OK、これは古い質問ですが、素晴らしい質問です。そして、私は良い答えを見つけられなかったので、自分で作成しました:) また、この非常にシンプルなプロジェクトをGitHubに投稿しました。

以下にサンプルコードを示します。

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

重要なポイントを次に示します。

  • P /呼び出しなし
  • 新しいプロセスの作成なし
  • ファイルシステムなし(すべてRAM内)
  • ネイティブ.NET DLL intellisenseなどを使用).
  • PDFまたはPNG(HtmlToXConverter.ConvertToPng
16
Timothy Khouri

WkhtmltopdfライブラリのC#ラッパーライブラリ(P/Invokeを使用)を確認します。 https://github.com/pruiz/WkHtmlToXSharp

7
Jason S

出力ファイルとして「-」を指定すると、wkhtmltopdfに出力をsoutに送信するように指示できます。その後、出力をプロセスから応答ストリームに読み取って、ファイルシステムへの書き込みに関する権限の問題を回避できます。

5
Graham Ambrose

これが一般に悪い考えである多くの理由があります。生成された実行可能ファイルをどのように制御しますが、クラッシュが発生した場合にメモリ内に残りますか?サービス拒否攻撃、または悪意のあるものがTestPDF.htmに侵入した場合はどうでしょうか?

私の理解では、ASP.Netユーザーアカウントにはローカルでログオンする権利がありません。また、実行可能ファイルにアクセスし、ファイルシステムに書き込むには、適切なファイル権限が必要です。ローカルセキュリティポリシーを編集し、ASP.Netユーザーアカウント(おそらくASPNET)がローカルにログオンできるようにする必要があります(既定では拒否リストに含まれている場合があります)。次に、他のファイルのNTFSファイルシステムのアクセス許可を編集する必要があります。共有ホスティング環境にいる場合、必要な構成を適用できない場合があります。

このような外部実行可能ファイルを使用する最良の方法は、ASP.NETコードからジョブをキューに入れ、何らかのサービスでキューを監視することです。これを行うと、あらゆる種類の悪い出来事から身を守ることができます。ユーザーアカウントの変更に伴うメンテナンスの問題は、私の意見では努力する価値はありません。また、サービスまたはスケジュールされたジョブのセットアップは苦痛ですが、それはまさに優れた設計です。 ASP.NETページは出力の結果キューをポーリングする必要があり、ユーザーに待機ページを提示できます。ほとんどの場合、これで問題ありません。

5
Brian Lyttle

2018年のものでこれについて私は考えます。

非同期を使用しています。 wkhtmltopdfとの間でストリーミングを行っています。 wkhtmltopdfはデフォルトでutf-8を想定しているが、プロセスの開始時に別の値に設定されるため、新しいStreamWriterを作成しました。

これらはユーザーごとに異なるため、多くの引数は含めませんでした。 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();
    }
}
_

私はそこに含まれていませんが、htmlページをレンダリングするときにwkhtmltopdfがロードする必要のある画像、CSSまたは他のものがある場合に役立つ可能性があります:

  • --cookieを使用して認証Cookieを渡すことができます
  • htmlページのヘッダーで、サーバーを指すhrefでベースタグを設定できます。wkhtmltopdfは必要に応じてそれを使用します
2
Yepeekai

質問/回答/上記のすべてのコメントをありがとう。 WKHTMLtoPDF用の独自のC#ラッパーを書いていたときにこれに出会い、それが私が抱えていたいくつかの問題に答えました。私はこれについてブログ投稿で書きました-これにはラッパーも含まれています(上記のエントリーから「インスピレーション」が私のコードに浸透しているのは間違いないでしょう...)

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

どうもありがとう!

2
John Reilly

一般に、pdfファイルが適切かつ正しく作成されている場合、戻りコード= 0が返されます。作成されていない場合、値は-veの範囲にあります。

0
Sukanya

ASP .Netプロセスには、ディレクトリへの書き込みアクセス権がない可能性があります。

%TEMP%への書き込みを指示してみて、動作するかどうかを確認してください。

また、ASP .Netページでプロセスのstdoutとstderrをエコーし​​、エラーメッセージを確認します。

0
SLaks