Вызов wkhtmltopdf для генерации PDF из HTML - PullRequest
52 голосов
/ 26 августа 2009

Я пытаюсь создать файл PDF из файла HTML. Посмотрев немного, я нашел: wkhtmltopdf идеально. Мне нужно вызвать этот .exe с сервера ASP.NET. Я пытался:

    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 в каталог верхнего уровня сайта. Где-нибудь еще это должно быть проведено?


Редактировать: Если у кого-то есть лучшие решения для динамического создания PDF-файлов из HTML, пожалуйста, дайте мне знать.

Ответы [ 11 ]

51 голосов
/ 09 ноября 2009

Обновление:
Мой ответ ниже, создает файл PDF на диске. Затем я передал этот файл в браузер пользователей для загрузки. Попробуйте использовать что-то вроде ответа Хата ниже, чтобы вместо этого получить wkhtml2pdf для вывода в поток, а затем отправить его непосредственно пользователю - это обойдет множество проблем с правами доступа к файлам и т. Д.

Мой оригинальный ответ:
Убедитесь, что вы указали выходной путь для PDF-файла, который доступен для записи процессом ASP.NET 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);
}
41 голосов
/ 10 сентября 2010

У меня была такая же проблема, когда я пытался использовать 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;
}

Спасибо Грэму Амброузу и всем остальным.

16 голосов
/ 23 декабря 2014

ОК, так что это старый вопрос, но отличный. И поскольку я не нашел хорошего ответа, я сделал свой :) Кроме того, я разместил этот супер простой проект на GitHub.

Вот пример кода:

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

Вот несколько ключевых моментов:

  • Нет P / Invoke
  • Нет создания нового процесса
  • Нет файловой системы (все в оперативной памяти)
  • Собственная .NET DLL с intellisense и т. Д.
  • Возможность создания PDF или PNG (HtmlToXConverter.ConvertToPng)
7 голосов
/ 05 апреля 2011

Проверьте библиотеку оболочки C # (используя P / Invoke) для библиотеки wkhtmltopdf: https://github.com/pruiz/WkHtmlToXSharp

5 голосов
/ 04 марта 2010

Вы можете указать wkhtmltopdf отправлять вывод в sout, указав "-" в качестве выходного файла. Затем вы можете прочитать выходные данные процесса в поток ответов и избежать проблем с разрешениями при записи в файловую систему.

5 голосов
/ 26 августа 2009

Есть много причин, почему это вообще плохая идея. Как вы собираетесь контролировать исполняемые файлы, которые порождаются, но остаются в памяти, если происходит сбой? А как насчет атак типа «отказ в обслуживании» или если в TestPDF.htm попало что-то вредоносное?

Насколько я понимаю, учетная запись пользователя ASP.NET не будет иметь права на локальный вход. Он также должен иметь правильные права доступа к файлу для доступа к исполняемому файлу и записи в файловую систему. Вам нужно отредактировать локальную политику безопасности и разрешить локальной учетной записи пользователя ASP.NET (может быть, ASPNET) входить в систему (она может быть в списке запретов по умолчанию). Затем вам нужно отредактировать разрешения для файловой системы NTFS для других файлов. Если вы находитесь в среде общего хостинга, может оказаться невозможным применить необходимую конфигурацию.

Лучший способ использовать внешний исполняемый файл, подобный этому, состоит в том, чтобы ставить задания в очередь из кода ASP.NET и иметь какую-то службу для отслеживания очереди. Если вы сделаете это, вы защитите себя от всевозможных неприятностей. По моему мнению, проблемы с обслуживанием, связанные с изменением учетной записи пользователя, не стоят усилий, и хотя настройка службы или запланированной работы - это боль, это просто лучший дизайн. Страница ASP.NET должна опросить очередь результатов для вывода, и вы можете предоставить пользователю страницу ожидания. Это приемлемо в большинстве случаев.

2 голосов
/ 28 ноября 2018

Мой взгляд на вещи с 2018 года.

Я использую асинхронный. Я транслирую в и из wkhtmltopdf. Я создал новый StreamWriter, потому что wkhtmltopdf ожидает utf-8 по умолчанию, но при запуске процесса ему присваивается другое значение.

Я не включил много аргументов, поскольку они варьируются от пользователя к пользователю. Вы можете добавить то, что вам нужно, используя дополнительные Args.

Я удалил 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
  • в заголовке html-страницы вы можете установить базовый тег с помощью href, указывающего на сервер, и wkhtmltopdf будет использовать его, если необходимо,
2 голосов
/ 05 апреля 2012

Спасибо за вопрос / ответ / за все комментарии выше. Я столкнулся с этим, когда писал свою собственную оболочку на C # для WKHTMLtoPDF, и это помогло мне решить пару проблем. Я закончил тем, что написал об этом в сообщении в блоге, которое также содержит мою обертку (вы, несомненно, увидите «вдохновение» из записей выше, просачивающихся в мой код ...)

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

Еще раз спасибо, ребята!

0 голосов
/ 17 декабря 2011

Обычно возвращается код возврата = 0, если файл PDF создан правильно и правильно. Если он не создан, значение находится в диапазоне -ve.

0 голосов
/ 06 февраля 2010
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();
    }
}
...