Подтвердить что ты не робот

Вызов wkhtmltopdf для создания PDF из HTML

Я пытаюсь создать файл 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, пожалуйста, дайте мне знать.

4b9b3361

Ответ 1

Update:
Мой ответ ниже, создает файл pdf на диске. Затем я передал этот файл в браузер пользователей в качестве загрузки. Подумайте, используя что-то вроде Hath ниже, чтобы получить 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);
}

Ответ 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;
}

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

Ответ 3

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

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

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

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

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

Создание PDF файлов из HTML в С# с использованием WKHTMLtoPDF

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

Ответ 8

Мой взгляд на вещи с 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 будет использовать его в случае необходимости

Ответ 9

Процесс ASP. Net, вероятно, не имеет права на запись в каталог.

Попробуйте записать его в %TEMP% и посмотрите, работает ли он.

Кроме того, сделайте страницу ASP.Net повторите процесс stdout и stderr и проверьте наличие сообщений об ошибках.

Ответ 10

Как правило, возвращается код = 0, если файл pdf создается правильно и правильно. Если он не создан, значение находится в диапазоне -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();
    }
}