I'm trying to render an HTML document as PDF using wkhtmltopdf.exe, which I call from a C# web application.
The HTML document needs to have both a footer and header that recur on every page, which is possible with wkhtmltopdf by specifying --header-html <a path>
as an argument.
However, the footer is rendered dynamically from a Razor view and I would rather not have to store it in a temporary file on disk and use that path, but I want to use the rendered HTML that's already in memory. That's possible for the document itself, by writing to the StandardInput
stream, like so:
var wkhtml = ConfigurationManager.AppSettings["WkHtmlToPdfPath"];
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.Arguments = "-q -n --disable-smart-shrinking - -";
p.Start();
var stdin = p.StandardInput;
stdin.AutoFlush = true;
stdin.Write(template);
stdin.Dispose();
Is it possible to do the same thing for the header and footer HTML, namely to pass it in inline without having to resort to temporary files?
I've tried:
stdin.Write(string.Format("--footer-html {0} ", footer));
But of course, it just treats that as part of the document, not a footer.
The main reason I wanna render the footer and header dynamically as well is (mostly) caused by another issue. While it would be nice to have a dynamic header and footer, it's mostly to solve the problem that I have to link to images with an absolute path (ie: C:\templates\images\logo.png) because relative paths (ie: images/logo.png) don't work when you use stdin and just pass in a string blob of HTML, so I need to insert the absolute path through Razor at runtime.
For this issue, I've tried setting the working directory of the process to match the relative paths, but to no avail:
p.StartInfo.WorkingDirectory = @"C:\templates";
If I could solve that problem, that would resolve 90% of the issue as well.
Footers And Headers: Headers and footers can be added to the document by the --header-* and --footer* arguments respectively. In header and footer text string supplied to e.g. --header-left, the following variables will be substituted.
INSTRUCTIONS: Download an appropriate version of wkHTMLtoPDF library from http://wkhtmltopdf.org. If you are on Windows operating system then do install it under C:\ drive (for example c:\wkhtmltopdf). On Linux/UNIX, you can install it under /usr/local/bin and make sure wkhtmltopdf has execute permissions.
Note sure if you solved this JulianR, and I will also assume you are in MVC(?) If not you can ignore some of the initial code below, but I had a similar situation whereby I needed to stream output directly to wkhtmltopdf due to secure and logged in sections of a site.
Firstly in a controller you can pull in the View you need for the display with any applicable master page (that itself may use headers and footers):
var view = ViewEngines.Engines.FindView(ControllerContext, myViewName, myMasterPageLayout);
you then get a current of this view with any necessary ViewData, Tempdata, etc. and store this in a string (content below):
string content;
ViewData.Model = model;
using (var writer = new System.IO.StringWriter())
{
var context = new ViewContext(ControllerContext, view.View, ViewData, TempData, writer);
view.View.Render(context, writer);
writer.Flush();
content = writer.ToString();
writer.Close();
}
at this stage you could actually modify your output html in the string if needed - e.g. to change any local paths to full paths
With you output HTML you then just pass into wkhtmltopdf:
var p = new Process();
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.UseShellExecute = false;
//Other parameters as required
byte[] file;
try
{
p.Start();
byte[] buffer = new byte[32768];
using (System.IO.StreamWriter stdin = p.StandardInput)
{
stdin.AutoFlush = true;
stdin.Write(content);
}
using (MemoryStream ms = new MemoryStream())
{
ms.Position = 0;
p.StandardOutput.BaseStream.CopyTo(ms);
file = ms.ToArray();
}
p.StandardOutput.Close();
// wait or exit
p.WaitForExit(60000);
// read the exit code, close process
int returnCode = p.ExitCode;
}
you then have a byte array that contains your PDF content of the whole page.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With