25

how to pass html as a string instead of url in wkhtmltopdf using asp.net, c#?

Joel Peltonen
  • 13,025
  • 6
  • 64
  • 100
Eugene
  • 251
  • 1
  • 3
  • 3

3 Answers3

38

STDIn and STDOut have been redirected in this example, so you shouldn't need files at all.

public static class Printer
{
    public const string HtmlToPdfExePath = "wkhtmltopdf.exe";

    public static bool GeneratePdf(string commandLocation, StreamReader html, Stream pdf, Size pageSize)
    {
        Process p;
        StreamWriter stdin;
        ProcessStartInfo psi = new ProcessStartInfo();

        psi.FileName = Path.Combine(commandLocation, HtmlToPdfExePath);
        psi.WorkingDirectory = Path.GetDirectoryName(psi.FileName);

        // run the conversion utility
        psi.UseShellExecute = false;
        psi.CreateNoWindow = true;
        psi.RedirectStandardInput = true;
        psi.RedirectStandardOutput = true;
        psi.RedirectStandardError = true;

        // note: that we tell wkhtmltopdf to be quiet and not run scripts
        psi.Arguments = "-q -n --disable-smart-shrinking " + (pageSize.IsEmpty? "" : "--page-width " + pageSize.Width +  "mm --page-height " + pageSize.Height + "mm") + " - -";

        p = Process.Start(psi);

        try {
            stdin = p.StandardInput;
            stdin.AutoFlush = true;
            stdin.Write(html.ReadToEnd());
            stdin.Dispose();

            CopyStream(p.StandardOutput.BaseStream, pdf);
            p.StandardOutput.Close();
            pdf.Position = 0;

            p.WaitForExit(10000);

            return true;
        } catch {
            return false;

        } finally {
            p.Dispose();
        }
    }

    public static void CopyStream(Stream input, Stream output)
    {
        byte[] buffer = new byte[32768];
        int read;
        while ((read = input.Read(buffer, 0, buffer.Length)) > 0) {
            output.Write(buffer, 0, read);
        }
    }
}
Daniel Little
  • 16,975
  • 12
  • 69
  • 93
  • no need to **Close** and **Dispose** the Process, it does the same thing. Just dispose it, or put it in a using construct. Same goes for Streams.. – Filip Cornelissen Sep 05 '11 at 14:02
  • +1 Great post, I wrote about it recently using similar code: http://www.megustaulises.com/2012/12/mvcnet-convert-html-to-pdf-with-pechkin.html – Ulises Jan 22 '13 at 20:49
  • Great post it helped a lot – senthil May 08 '13 at 14:06
  • what is the purpose of ` - - ` at the end of the arguments ? I know it has something to do with outputting to STDOUT, but is that a wkhtmltopdf construct, or will that work for any command line? – Walter Stabosz Dec 05 '13 at 03:26
  • @Walter Stabosz see here http://code.google.com/p/wkhtmltopdf/wiki/Usage. Using - tells wkhtmltopdf to use a standard stream. – Daniel Little Dec 05 '13 at 04:07
  • I can't get this to work with referenced images and stylesheets. It that possible? When I run the commandline with files on disk it works – smerlung Feb 11 '14 at 13:48
  • @smerlung It should as long as the images resolve, https is tricky though. – Daniel Little Feb 11 '14 at 23:06
  • @Lavinski yes I have it running with absolute paths. I can't figure out which location to be relative to when running wkhtmltopdf.exe as a process through .Net – smerlung Feb 12 '14 at 16:57
  • @smerlung I used web paths such as http://asset but i'd guess the directory you need can be set with `Process.Start(psi, "PATH HERE")`. – Daniel Little Feb 14 '14 at 01:58
  • @Lavinski it is not possible to pass an additional string when passing a ProcessStartInfo to Process.Start. But you can set the working directory with psi.WorkingDirectory = "PATH HERE". Please see my question: http://stackoverflow.com/questions/21775572/wkhtmltopdf-relative-paths-in-hmtl-with-redirected-in-out-streams-wont-work – smerlung Feb 14 '14 at 09:36
  • Setting stdin.AutoFlush only has value if the last char of the html is a newLine, since flushing waits for newLine chars. Instead, remove that line, remove the call to stdin.Dispose, and replace the latter with .Close(). Close calls both Dispose and Flush, solving both problems cleanly. HTH! – pbristow May 07 '15 at 14:37
  • Causes non english letter to appear very wierd? how to fix that? – Christo S. Christov Jun 09 '15 at 21:08
  • great post , it helps me to set custom height and width. @Chris you need to add this betweeen head tags at least it works for turkish character set. – balron Feb 16 '16 at 15:22
  • Do you just add this code to the PdfConvert class? And then how do you use it? – Barry Michael Doyle Aug 16 '16 at 15:22
  • catch{} swallows exceptions, any issue in code inside won't be escalated. Better to put those exception you are awaiting. Or don't catch at all if you don't expect any of them – Evgeny Gorbovoy Mar 29 '18 at 21:59
16

Redirecting STDIN is probably the easiest way to accomplish what you're trying to do.

One method of redirecting STDIN with wkhtmltopdf (in ASP.Net) is as follows:

    private void WritePDF(string HTML)
    {
        string inFileName,
                outFileName,
                tempPath;
        Process p;
        System.IO.StreamWriter stdin;
        ProcessStartInfo psi = new ProcessStartInfo();

        tempPath = Request.PhysicalApplicationPath + "temp\\";
        inFileName = Session.SessionID + ".htm";
        outFileName = Session.SessionID + ".pdf";

        // run the conversion utility
        psi.UseShellExecute = false;
        psi.FileName = "c:\\Program Files (x86)\\wkhtmltopdf\\wkhtmltopdf.exe";
        psi.CreateNoWindow = true;
        psi.RedirectStandardInput = true;
        psi.RedirectStandardOutput = true;
        psi.RedirectStandardError = true;

        // note that we tell wkhtmltopdf to be quiet and not run scripts
        // NOTE: I couldn't figure out a way to get both stdin and stdout redirected so we have to write to a file and then clean up afterwards
        psi.Arguments = "-q -n - " + tempPath + outFileName;

        p = Process.Start(psi);

        try
        {
            stdin = p.StandardInput;
            stdin.AutoFlush = true;

            stdin.Write(HTML);
            stdin.Close();

            if (p.WaitForExit(15000))
            {
                // NOTE: the application hangs when we use WriteFile (due to the Delete below?); this works
                Response.BinaryWrite(System.IO.File.ReadAllBytes(tempPath + outFileName));
                //Response.WriteFile(tempPath + outFileName);
            }
        }
        finally
        {
            p.Close();
            p.Dispose();
        }

        // delete the pdf
        System.IO.File.Delete(tempPath + outFileName);
    }

Note that the code above assumes that there's a temp directory available in your application directory. Also, you must explicitly enable write access to that directory for the user account used when running the IIS process.

Michael Todd
  • 16,679
  • 4
  • 49
  • 69
  • I can't get this to work with referenced images and stylesheets. It that possible? When I run the commandline with files on disk it works. – smerlung Feb 11 '14 at 13:47
  • @smerlung Make sure that the web app has access to the files that you're referencing. When running from the command line, you're running as your login; when running from the web server, you're running as the web server login which may not have access to the files you're using. Also, make sure that the path to the files makes sense from that location as well. For example, if you're referencing `..\styles\style.css` then there needs to be a `styles` directory, 'above' where you're running the program, that contains the `style.css` file. – Michael Todd Feb 11 '14 at 15:27
  • I got it working by using absolute paths. But that isn't very nice. I understand what your saying, but my problem is that I don't understand where the process is "running". I've tried different options for WorkingDirectory but couldn't figure it out? – smerlung Feb 11 '14 at 16:48
3

I know this is an older post, but I want future developers to have this option. I had the same need, and the idea of having to start a background process just to get a PDF inside of a web app is terrible.

Here's another option: https://github.com/TimothyKhouri/WkHtmlToXDotNet

It's a .NET native wrapper around wkhtmltopdf.

Sample code here:

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

Note, it's not thread-safe as of right now - I'm working on that. So just use a monitor or something or a lock.

Timothy Khouri
  • 31,315
  • 21
  • 88
  • 128