Url to pdf using wkhtmltopdf.exe at server side - c#

Back few days i'm trying to make a web application that convert url to pdf. Finally i have done this with the help of wkhtmltopdf.exe
My class code is given below
public class PDFGenerator
{
public static string HtmlToPdf(string pdfOutputLocation, string outputFilenamePrefix, string[] urls,
string[] options = null,
string pdfHtmlToPdfExePath = "C:\\Program Files\\wkhtmltopdf\\bin\\wkhtmltopdf.exe")
{
string urlsSeparatedBySpaces = string.Empty;
try
{
//Determine inputs
if ((urls == null) || (urls.Length == 0))
throw new Exception("No input URLs provided for HtmlToPdf");
else
urlsSeparatedBySpaces = String.Join(" ", urls); //Concatenate URLs
string outputFolder = pdfOutputLocation;
string outputFilename = outputFilenamePrefix + "_" + DateTime.Now.ToString("yyyy-MM-dd-hh-mm-ss-fff") + ".PDF"; // assemble destination PDF file name
var p = new System.Diagnostics.Process()
{
StartInfo =
{
FileName = pdfHtmlToPdfExePath,
Arguments = ((options == null) ? "" : String.Join(" ", options)) + " " + urlsSeparatedBySpaces + " " + outputFilename,
UseShellExecute = false, // needs to be false in order to redirect output
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true, // redirect all 3, as it should be all 3 or none
WorkingDirectory = HttpContext.Current.Server.MapPath(outputFolder)
}
};
p.Start();
// read the output here...
var output = p.StandardOutput.ReadToEnd();
var errorOutput = p.StandardError.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 so return path of pdf
if ((returnCode == 0) || (returnCode == 2))
return outputFolder + outputFilename;
else
throw new Exception(errorOutput);
}
catch (Exception exc)
{
throw new Exception("Problem generating PDF from HTML, URLs: " + urlsSeparatedBySpaces + ", outputFilename: " + outputFilenamePrefix, exc);
}
}
}
and my button click event code is given below
//Create PDF from a single URL
var pdfUrl = PdfGenerator.PDFGenerator.HtmlToPdf(pdfOutputLocation: "~/PDFs/",
outputFilenamePrefix: "GeneratedPDF",
urls: new string[] { "http://news.bbc.co.uk" });
//Create PDF from multiple URLs
pdfUrl = PdfGenerator.PDFGenerator.HtmlToPdf(pdfOutputLocation: "~/PDFs/",
outputFilenamePrefix: "GeneratedPDF",
urls: new string[] { "http://www.google.co.uk", "http://news.bbc.co.uk" });
all code is working properly and saving pdf in my PDFs code. But i have 2 questions:
This code is not working when i host my website because C:\\Program Files\\wkhtmltopdf\\bin\\wkhtmltopdf.exe executing error. Is any dll library avail for wkhtmltopdf?
When i run this code pdf saved in silent mode. I want to know that is this possible to save pdf with save dialog or ask user to folder location where they want to save pdf?
Sorry for my bad english.

Place your "wkhtmltopdf.exe" in your project folder, so that it can be easily accessible to your application. There might be some access related issues if you have your .exe file in 'C' folder. Try this, it worked for me.

Related

How to force a download of a created file to a users computer c#

I am looking to allow a person to to export journal entries into a text file. I can create a file with all the data but rather strictly saving the file somewhere specific I want to allow a user to download and save the file where they want on their computer. How to I force a download of a file after I create it with StreamWriter. I currently have the following code:
string fileName = "Journal.txt";
using (StreamWriter journalExport = new StreamWriter(fileName))
{
foreach (JournalEntryView entry in journalEnteries)
{
//write each journal entery to file/document
journalExport.WriteLine(entry.timestamp + " - " + entry.author + " (" + entry.authorRole + ")");
journalExport.WriteLine(entry.text);
journalExport.WriteLine("");
journalExport.WriteLine("");
}
}
I am also trying to put this into an ActionResult and return the file.
EDIT:
The following code is my new current code and the direction I am looking to go in, but when I use an ActionLink to call this method, i just get redirected to a new page rather than downloading the file.
string fileName = "Journal.txt";
string filepath = ConfigurationManager.AppSettings["DocumentRoot"] + "\\" + id + "\\" + fileName;
using (StreamWriter journalExport = new StreamWriter(filepath))
{
foreach (JournalEntryView entry in journalEnteries)
{
//write each journal entery to file/document
journalExport.WriteLine(entry.timestamp + " - " + entry.author + " (" + entry.authorRole + ")");
journalExport.WriteLine(entry.text);
journalExport.WriteLine("");
journalExport.WriteLine("");
}
}
byte[] fileData = System.IO.File.ReadAllBytes(filepath);
string contentType = MimeMapping.GetMimeMapping(filepath);
var cd = new System.Net.Mime.ContentDisposition
{
FileName = fileName,
Inline = true,
};
Response.AppendHeader("Content-Disposition", cd.ToString());
return File(fileData, contentType);
This might be what you are looking for:
public ActionResult GetFile()
{
...processing stuff...
return File("/files/file.pdf", "application/pdf");
//or
return File("/files/file.pdf", "application/force-download", "donwloadname.pdf");
}

"Process cannot access the file because it is being used by another process"

I need to upload a CSV file to an ASP.NET application, on an Azure server. Although it works fine on my local machine, when uploading it to the server the following error is thrown:
"Process cannot access the file
'C:\inetpub\wwwroot\ImportFiles\9_11.csv' because it is being used by
another process"
My code:
string fileName = DateTime.Now.ToString().Replace(':', '_').Replace('/', '_').Replace(' ', '_') + Convert.ToString((new Random()).Next(0, 999) * (new Random()).Next(0, 999));
string path = Server.MapPath("ImportFiles") + "\\" + fileName + "" + FileImport.FileName.Substring(FileImport.FileName.IndexOf('.'));
FileImport.SaveAs(path);
string pathforSeconStream = path;
try
{
Response.Write("<script> alert('In Try Block');</script>");
bool flag = true;
int visiblemessageCount = 0;
int rollNo = 1;
StreamReader ColLine = new StreamReader(path);
string ColInputline = ColLine.ReadLine();
String[] ColsInput = ColInputline.Split(',');
ColLine.Close();
ColLine.Dispose();
string preFix = "", RollNumber = "";
StreamReader sr = new StreamReader(pathforSeconStream);
}
catch(Exception ex)
{
}
The code to generate a unique filename is wrong. Use Path.GetTempFileName.
PS never eat an exceptiion. Please remove catch (Exception ex) {};
Revision
Instead of FileImport.Save(...) just save the request in a MemoryStream and then work on it.

Output not proper when calling batch file programatically

I was trying to automate server patch installation for my product and I came to know about Wix Toolset.
I was hoping to get the JBoss Version in my installer. The command to get the same is standalone.bat --version from cmd.
So from my installer I created one CustomAction where I tried to run it and get the output.
public static string runRunnableBatch(string path){
Process exploitVersionService = new Process();
string runnableBinPath = path;
exploitVersionService.StartInfo.WorkingDirectory = path + "bin";
exploitVersionService.StartInfo.FileName = path + "bin\\standalone.bat";
exploitVersionService.StartInfo.CreateNoWindow = false;
exploitVersionService.StartInfo.Arguments = string.Format("--version");
exploitVersionService.StartInfo.UseShellExecute = false;
exploitVersionService.StartInfo.RedirectStandardOutput = true;
exploitVersionService.StartInfo.RedirectStandardInput = false;
exploitVersionService.Start();
exploitVersionService.WaitForExit();
// /*
string opt = "";
while (!exploitVersionService.StandardOutput.EndOfStream){
opt += exploitVersionService.StandardOutput.ReadLine();
}
// */
//using (StreamWriter writer = new StreamWriter("D:\\_log.txt"))
//using (StreamReader reader = exploitVersionService.StandardOutput){
// writer.AutoFlush = true;
// for (; ; ){
// string textLine = reader.ReadLine();
// if (textLine == null)
// break;
// writer.WriteLine(textLine);
// }
//}
//StreamReader exploitVersionFeed = exploitVersionService.StandardOutput;
//string output = exploitVersionFeed.ReadToEnd();
return opt;
}
When I was doing that, all I got as output was the first line of the whole output string.
I needed the whole string in my code so that from regular expression I could extract the version.
Also tried with
public static string runRunnableBatch(string path){
string executableBinPath = path + "bin";
string executableBinPath_BatchCmd = "cd " + "\"" + executableBinPath + "\"";
string outputFileName = "TempVerInfoHolder.txt";
string outputFilePath = executableBinPath+#"\TempVerInfoHolder1.txt";
string versionRetriever_BatchCmd = #"standalone.bat --version > " + "\""+outputFilePath+"\"";
string partitionName_BatchCmd = #Strings.Utils.getPartitionFromPath(path);
// Creating command sequence
SortedList<int, string> commandSequence = new SortedList<int, string>();
// ~ d:
commandSequence.Add(1, partitionName_BatchCmd);
// ~ cd %path%
commandSequence.Add(2, executableBinPath_BatchCmd);
// ~ standalone.bat --version > %filename%
commandSequence.Add(3, versionRetriever_BatchCmd);
runCommandFromSequence(commandSequence);
// Run together
return "";
}
private static void runCommandFromSequence(SortedList<int, string> commandSequence){
Process seqCmdExecHost = new Process();
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "cmd.exe";
psi.RedirectStandardInput = true;
psi.RedirectStandardOutput = true;
psi.UseShellExecute = false;
psi.CreateNoWindow = false;
seqCmdExecHost.StartInfo = psi;
seqCmdExecHost.Start();
using (StreamWriter writer = seqCmdExecHost.StandardInput) {
if (writer.BaseStream.CanWrite) {
foreach (int item in commandSequence.Keys){
MessageBox.Show(seqCmdExecHost.Id.ToString());
MessageBox.Show(commandSequence[item]);
writer.WriteLine(commandSequence[item]);
}
}
string opt = "";
while (!seqCmdExecHost.StandardOutput.EndOfStream){
opt += seqCmdExecHost.StandardOutput.ReadLine();
}
MessageBox.Show("Exited? " + seqCmdExecHost.HasExited);
MessageBox.Show("O/P? " + opt);
}
}
I have tried some other way as well. Switching the commented code of the above function was one of them.
Output getting while doing it from code level
Calling "D:\Servers\VA\XYZ\JBoss-7.1.1-Final\bin\standalone.conf.bat
Output while running the same command manually from cmd
Calling D:\Servers\VA\XYZ\JBoss-7.1.1-Final\bin\standalone.conf.bat
======================================================================
JBoss Bootstrap Environment
JBOSS_HOME: D:\Servers\VA\XYZ\JBoss-7.1.1-Final
JAVA: C:\Program Files\Java\jdk1.7.0_67\bin\java
JAVA_OPTS
======================================================================
Listening for transport dt_socket at address: 8787
19:08:08,333 INFO [org.jboss.modules] JBoss Modules version 1.1.1.GA
JBoss AS 7.1.1.Final "Brontes"
Press any key to continue . . .
My observation is, the stream is getting closed once the nested standalone.conf.bat is getting called from standalone.bat.
If any workaround available to get the full output in string/buffer/stream, would be appreciated.
Thanks
What you could do is call the Command line Application instead of calling the batch file
exploitVersionService.StartInfo.WorkingDirectory = path + "bin";
exploitVersionService.StartInfo.FileName = "cmd.exe";
exploitVersionService.StartInfo.CreateNoWindow = false;
exploitVersionService.StartInfo.Arguments = string.Format(" /c \"{0}\" --version",path + "bin\\standalone.bat");
I found one work around to do achieve this.
I created the batch file programmatically and ran it with cmd.
public static void createBatchToGetVersion(string path)
{
CustomLogger.getInstance().debug("Started creating batch file");
BatchOps.executableBinPath = path + "bin";
CustomLogger.getInstance().debug("Ëxecutable bin path: " + BatchOps.executableBinPath);
BatchOps.tempBatchFileName = "JBossVerCmd.bat";
BatchOps.holderFileName = #"JBossVerHolder.txt";
BatchOps.absoluteHolderPath = Strings.Utils.normalize(executableBinPath) + holderFileName;
CustomLogger.getInstance().debug("Normalized absoluteHolderPath: " + BatchOps.absoluteHolderPath);
CustomLogger.getInstance().debug("Checking if JBOSS_HOME entered by user actuallty points to JBOSS");
if (!File.Exists(Strings.Utils.normalize(executableBinPath) + "standalone.bat"))
{
CustomLogger.getInstance().error("standalone.bat not found. JBOSS_HOME Dir is not correctly entered");
throw new CustomExceptions.DirectoryNotAcceptableException("Bad directory is assigned to JBOSS_HOME or JBOSS_HOME structure corrupted");
}
/*
* Batch file formation.
* Contains:
* Start file
* D:
* cd D:\Fusion Server\jboss 7.1.1\bin
* #echo | call standalone.bat --version > sample.txt
* #echo Done
* End file
* #echo is required here because it exits the cmd when completed whithout having the user pressing any key
*/
string changePartition_cmd = Strings.Utils.getPartitionFromPath(path);
string changeDirectory_cmd = #"cd " + BatchOps.executableBinPath;
string getVersion_cmd = #"#echo | call standalone.bat --version > " + holderFileName;
string exitCmd = #"#echo Done";
CustomLogger.getInstance().debug("Command to be written on batch file");
CustomLogger.getInstance().debug("\r\n" + changePartition_cmd + "\r\n" + changeDirectory_cmd + "\r\n" + getVersion_cmd + "\r\n" + exitCmd);
SortedList<int, string> commandSequence = new SortedList<int, string>();
CustomLogger.getInstance().debug("Initializing command sequence.");
commandSequence.Add(1, changePartition_cmd);
commandSequence.Add(2, changeDirectory_cmd);
commandSequence.Add(3, getVersion_cmd);
commandSequence.Add(4, exitCmd);
// Will create one if file never existed and open one delete the content and set the pointer to the begnning
// if already existed
StreamWriter batchFileWriter = null;
try
{
CustomLogger.getInstance().debug("Establishing stream to and from temp batch file");
batchFileWriter = new StreamWriter(tempBatchFileName);
CustomLogger.getInstance().debug("Command sequence ready to be written on temp batch file.");
Perform.writeToStreamFromSequence(batchFileWriter, commandSequence);
CustomLogger.getInstance().debug("Command sequence successfully written");
}
catch (IOException ex)
{
CustomLogger.getInstance().error("Error while writing command sequence.\n" + ex.ToString());
// throw exception to CustomAction
throw new IOException("Error while writing commandSequence");
}
finally
{
// Not required. Stream already closed in writeToStreamFromSequence
}
}
public static void runTempBatchFile()
{
Process seqCmdExecHost = new Process();
ProcessStartInfo procStartInfo = new ProcessStartInfo("cmd", #"/c " + BatchOps.tempBatchFileName);
procStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
seqCmdExecHost.StartInfo = procStartInfo;
seqCmdExecHost.Start();
seqCmdExecHost.WaitForExit();
seqCmdExecHost.Close();
}
I found the moment i was doing
procStartInfo.UseShellExecute=true;
procStartInfo.RedirectStandardOutput=true
It was giving the first line of output. Dont know why?. If anyone has any idea about it please explain.
Thanks

PDF generation using HTML Doc Software in asp.net

I am converting a website from 1.1 framework to 4.0. The website has functionality to Generate Pdf of the content entered in TextEditor. this website has used "HTMLDoc Software" to convert content to pdf using the below code:
string url, pdfFile, exeFile;
string response = "";
url = GetWebUrl() + "/PDFSpeechDetails.aspx?ArticleId=" + ValueOfQueryString;
pdfFile = HttpContext.Current.Request.PhysicalApplicationPath + "pdfs\\articles\\" + ValueOfQueryString + ".pdf";
exeFile = HttpContext.Current.Request.PhysicalApplicationPath + "html2pdf\\htmldoc.exe";
response = gPDF.ShowPDF(url, pdfFile, exeFile);
and ShowPDF Method is:
public string ShowPDF(string url, string pdfFile, string exeFile)
{
try
{
Process p = new Process();
p.StartInfo.FileName = exeFile;
pdfFile = "\"" + pdfFile + "\"";
string args = " --webpage -f " + pdfFile + " " + url;
p.StartInfo.Arguments = args;
p.Start();
p.WaitForExit();
return "1";
}
catch (Exception ex)
{
return (ex.ToString());
}
}
What the above code is doing:(as the previous developer used)
I have placed a folder named HTML2PDf has an exe file. i am just passing it to argument and running this exe file.
This code is working fine on my local machine as well as on server(I took remote of the server and checked there.), but when i tried to generate the PDF from Outer world means using website's domain name, it does not work. I am not getting any clue why it is not working.
please help me how can i make it working on website.

Images in HTML not displaying in PDF in WKHTMLTOPDF

Hi am using Wkhtmltopdf to create PDF from Html page using c#, Asp.net. PDF are created smoothly, but when I add images to HTML it does not get displayed on PDF.
<div id="Image" class="right" style="background:#333;height:15%;width:25%;">
<img width="100%" height="100%" src="../TempLogo/chafa.jpg" />
I have used "file:///E:/AMS/AMS/AMS.WEB/TempLogo/chafa.jpg" & "src="localhost:5822/AMS.WEB/TempLogo/chafa.jpg"; also but it does not work. I also tried to change Version from 0.11 to 0.9 but it also not succeed
public static string HtmlToPdf(string pdfOutputLocation, string outputFilenamePrefix, string[] urls,
string[] options = null,
// string pdfHtmlToPdfExePath = "C:\\Program Files (x86)\\wkhtmltopdf\\wkhtmltopdf.exe")
string pdfHtmlToPdfExePath = "C:\\Program Files\\wkhtmltopdf\\wkhtmltopdf.exe")
{
string urlsSeparatedBySpaces = string.Empty;
try
{
//Determine inputs
if ((urls == null) || (urls.Length == 0))
throw new Exception("No input URLs provided for HtmlToPdf");
else
urlsSeparatedBySpaces = String.Join(" ", urls); //Concatenate URLs
string outputFolder = pdfOutputLocation;
string outputFilename = outputFilenamePrefix + "_" + DateTime.Now.ToString("yyyy-MM-dd-hh-mm-ss-fff") + ".PDF"; // assemble destination PDF file name
var p = new System.Diagnostics.Process()
{
StartInfo =
{
FileName = pdfHtmlToPdfExePath,
Arguments = ((options == null) ? "" : String.Join(" ", options)) + " " + urlsSeparatedBySpaces + " " + outputFilename,
UseShellExecute = false, // needs to be false in order to redirect output
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true, // redirect all 3, as it should be all 3 or none
WorkingDirectory = HttpContext.Current.Server.MapPath(outputFolder),
CreateNoWindow = true
}
};
p.Start();
// read the output here...
var output = p.StandardOutput.ReadToEnd();
var errorOutput = p.StandardError.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 so return path of pdf
if ((returnCode == 0) || (returnCode == 2))
return outputFolder + outputFilename;
else
throw new Exception(errorOutput);
}
catch (Exception exc)
{
throw new Exception("Problem generating PDF from HTML, URLs: " + urlsSeparatedBySpaces + ", outputFilename: " + outputFilenamePrefix, exc);
}
}

Categories

Resources