Creating CycleTile with remote images - windows-phone-8

I have seen a number of examples showing the CycleTile in action, but these have all used local images. Is it possible to set these images once the app is first run and point the CycleTile to the remote images? Or if I do need to save these to the phone first, how can I get the CycleTile to reference them?

CycleTileTemplate & CycleTileData only support local URIs and don't support remote web URIs. Meaning that you can only set the source of a cycle image from files installed from the XAP or from files in IsoStore.
In order to support remote images in CycleTileData, you'll need to download the images in a periodic background agent, save them to IsoStore and then update the CycleTileData with those images. Push notifications won't work here since the images need to be local and neither will ShellTileSchedule.
Make sure to save the images to IsoStore under the "/Shared/ShellContent" and set their URIs as "isostore:/Shared/Shellcontent/myImage.png" or they won't be accessible to the start screen tiles.
Let's see an example of that. First we start off by writing up a parallelized threaded algorithm that launches 9 download threads, waits for the results and then updates tiles:
private IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var threadFinishEvents = new List<WaitHandle>();
DownloadImages(threadFinishEvents);
new Thread(()=>
{
Mutex.WaitAll(threadFinishEvents.ToArray());
UpdateTiles();
isoStore.Dispose();
}).Start();
}
Next, we'll download the 9 images into IsoStore "/Shared/ShellContent". We'll take special note to add the new threading flags for each web download, and set the flag as done once the file is in IsoStore.
private void DownloadImages(List<WaitHandle> threadFinishEvents)
{
for (int i = 0; i < 9; i++)
{
var localI = i;
var threadFinish = new EventWaitHandle(false, EventResetMode.ManualReset);
threadFinishEvents.Add(threadFinish);
var request = WebRequest.CreateHttp("http://www.justinangel.net/storage/691x336.png");
request.BeginGetResponse(ir =>
{
var result = request.EndGetResponse(ir);
using (var isoStoreFile = isoStore.OpenFile("shared/shellcontent/myImage" + localI + ".png",
FileMode.Create,
FileAccess.ReadWrite))
using (var response = result.GetResponseStream())
{
var dataBuffer = new byte[1024];
while (response.Read(dataBuffer, 0, dataBuffer.Length) > 0)
{
isoStoreFile.Write(dataBuffer, 0, dataBuffer.Length);
}
}
threadFinish.Set();
}, null);
}
}
Finally, we'll update the live tile to use the new images in IsoStore.
private void UpdateTiles()
{
ShellTile.ActiveTiles
.First()
.Update(new CycleTileData()
{
Title = "Cyclical",
CycleImages = new Uri[]
{
new Uri("isostore:/Shared/ShellContent/myImage0.png", UriKind.Absolute),
new Uri("isostore:/Shared/ShellContent/myImage1.png", UriKind.Absolute),
new Uri("isostore:/Shared/ShellContent/myImage2.png", UriKind.Absolute),
new Uri("isostore:/Shared/ShellContent/myImage3.png", UriKind.Absolute),
new Uri("isostore:/Shared/ShellContent/myImage4.png", UriKind.Absolute),
new Uri("isostore:/Shared/ShellContent/myImage5.png", UriKind.Absolute),
new Uri("isostore:/Shared/ShellContent/myImage6.png", UriKind.Absolute),
new Uri("isostore:/Shared/ShellContent/myImage7.png", UriKind.Absolute),
new Uri("isostore:/Shared/ShellContent/myImage8.png", UriKind.Absolute),
}
});
}
There's a couple of interesting things to consider:
Periodic background agents only have 25 seconds to complete their operation, so it might make sense to add timer thresehold when activating Mutex.WaitAll and have it fail gracefully.
Downloading 9 images in 25 seconds might not work at all under some network conditions so it might be best to optimize for that. You can either use less images, or update only a few images every 30 minutes.
Updating the CycleTileData to the same file URIs won't trigger an update of the tile (AFAIK). So you'll need better filenames then myImage0, but rather have unique file names for the images.

For CycleTile, the images have to be local. You could set up a periodic task to refresh the images though, then store those images in the shared/shellcontent special folder in local/isolated storage (e.g., ms-appdata:///local/shared/shellcontent/image01.png)
Session 7 of the Windows Phone 8 Jumpstart is a good reference for this - specifically about 25:30 in.

Related

Embedding MS-Office Documents Into AutoCAD Drawings Using Design Automation

I have a need to embed MS-Office documents (Excel, Word) into AutoCAD using Design Automation. Searching around the web, it seems that this is not possible because the MS-Office applications, which would act as an OLE Client, would need to be running on the Forge Server. Could someone confirm that this is the case?
If I am correct in my above statement, my next best alternative would be to embed .EMF files created from each page of the document I want to embed; alternatively using raster images would also be acceptable. Creating the .EMF or raster files is not a problem. I just can't find a solution for embedding the file that does not involve copying them to the clipboard and using the PASTECLIP command. This approach has worked for me in the AutoCAD application using a C# AutoCAD.NET plugin, an OLE2Frame object is created, but it fails in accoreconsole (because PASTECLIP uses a UI class which is not available). This leads me to think that the same would occur while running the bundle in Design Automation.
The best I have been able achieve so far is to write a raster image files to the working directory and linking the raster images to the AutoCAD document using RasterImageDef and RasterImage (code below). Is this the only way I can do this? Can I do something similar using an EMF image, which is vector based, instead of a raster image? Or is there a way to actually embed an EMF (preferred) or raster image instead of just linking the images?
The code below fails if I use .EMF files, because RasterImageDef and RasterImage do not support the the EMF file; the EMF file being a vector format, not a raster format?
[CommandMethod("TEST")]
public void Test()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
// Get the file name of the image using the editor to prompt for the file name
// Create the prompt
PromptOpenFileOptions options = new PromptOpenFileOptions("Enter Sequence file path");
options.PreferCommandLine = true;
// Get the file name, use no quotes
PromptFileNameResult result = null;
try { result = ed.GetFileNameForOpen(options); }
catch (System.Exception ex)
{
DisplayLogMessage($"Could not get sequence file location. Exception: {ex.Message}.", ed);
return;
}
// Get the rtf filename from the results
string filename = result.StringResult;
DisplayLogMessage($"Got sequence filename: {filename}", ed);
// Load the Sequence.rtf document
Aspose.Words.Document seq;
using (FileStream st = new FileStream(filename, FileMode.Open))
{
seq = new Aspose.Words.Document(st);
st.Close();
}
DisplayLogMessage($"Aspose.Words Loaded: {filename}", ed);
Transaction trans = db.TransactionManager.StartTransaction();
// Get or create the image dictionary
ObjectId imageDictId = RasterImageDef.GetImageDictionary(db);
if (imageDictId != null)
imageDictId = RasterImageDef.CreateImageDictionary(db);
// Open the Image Dictonary
DBDictionary imageDict = (DBDictionary)trans.GetObject(imageDictId, OpenMode.ForRead);
double x = 0.0;
double y = 0.0;
try
{
// For each page in the Sequence.
for (int i = 0; i < seq.PageCount; i++)
{
DisplayLogMessage($"Starting page {i + 1}", ed);
// extract the page.
Aspose.Words.Document newSeq = seq.ExtractPages(i, 1);
Aspose.Words.Saving.ImageSaveOptions imgOptions = new Aspose.Words.Saving.ImageSaveOptions(Aspose.Words.SaveFormat.Emf);
imgOptions.Resolution = 300;
DisplayLogMessage($"Extracted page {i + 1}", ed);
string dictName = Guid.NewGuid().ToString();
filename = Path.Combine(Path.GetDirectoryName(doc.Name), dictName + ".Emf");
// Save the image
SaveOutputParameters sp = newSeq.Save(filename, imgOptions);
DisplayLogMessage($"Saved {dictName}.Emf", ed);
RasterImageDef imageDef = null;
ObjectId imageDefId;
// see if my guid is in there
if (imageDict.Contains(dictName))
imageDefId = (ObjectId)imageDict.GetAt(dictName);
else
{
// Create an image def
imageDef = new RasterImageDef();
imageDef.SourceFileName = $"./{dictName}.Emf";
// load the image
imageDef.Load();
imageDict.UpgradeOpen();
imageDefId = imageDict.SetAt(dictName, imageDef);
trans.AddNewlyCreatedDBObject(imageDef, true);
}
// create raster image to reference the definition
RasterImage image = new RasterImage();
image.ImageDefId = imageDefId;
// Prepare orientation
Vector3d uCorner = new Vector3d(8.5, 0, 0);
Vector3d vOnPlane = new Vector3d(0, 11, 0);
Point3d ptInsert = new Point3d(x, y, 0);
x += 8.5;
CoordinateSystem3d coordinateSystem = new CoordinateSystem3d(ptInsert, uCorner, vOnPlane);
image.Orientation = coordinateSystem;
// some other stuff
image.ImageTransparency = true;
image.ShowImage = true;
// Add the image to ModelSpace
BlockTable bt = (BlockTable)trans.GetObject(db.BlockTableId, OpenMode.ForRead);
BlockTableRecord btr = (BlockTableRecord)trans.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
btr.AppendEntity(image);
trans.AddNewlyCreatedDBObject(image, true);
// Create a reactor between the RasterImage
// and the RasterImageDef to avoid the "Unreferenced"
// warning the XRef palette
RasterImage.EnableReactors(true); // in the original was true
image.AssociateRasterDef(imageDef);
}
trans.Commit();
}
catch (System.Exception ex)
{
DisplayLogMessage("ERROR: " + ex.Message,ed);
trans.Abort();
}
}
Raster images are always linked. There's no way to embed them. The only way to embed an image is to use AcDbOle2Frame (C++) or Autodesk.AutoCAD.DatabaseServices.Ole2Frame (C#). In theory, it is possible to create these objects without the "OLE server" being present but I haven't tried so I don't know if enough APIs are exposed to make it happen.
You should try it and see how far you can get.
Albert
There is way to embed raster image, it is not straightforeward, you need to use C++\ObjectARX API, please refer this https://github.com/MadhukarMoogala/EmbedRasterImage/tree/EmbedRasterImageUsingDBX

converting a file path to a URI

When using the secondaryTitle in c++, I have to enter a URI that points to the logo. The URI fails if I try to point it to any file outside of the app's package. What I tried to is have the user select the file using a filepicker
void App3::MainPage::FindLogo(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
FileOpenPicker^ openPicker = ref new FileOpenPicker();
openPicker->ViewMode = PickerViewMode::Thumbnail;
openPicker->SuggestedStartLocation = PickerLocationId::PicturesLibrary;
openPicker->FileTypeFilter->Append(".jpg");
openPicker->FileTypeFilter->Append(".jpeg");
openPicker->FileTypeFilter->Append(".png");
create_task(openPicker->PickSingleFileAsync()).then([this](Windows::Storage::StorageFile^ file)
{
if (file)
{
StorageFolder^ folder;
auto ur = ref new Uri("ms-appx:///Assets//");
String^ s = Windows::ApplicationModel::Package::Current->InstalledLocation->Path;
create_task(StorageFolder::GetFolderFromPathAsync(s)).then([=](StorageFolder^ folder){
create_task(file->CopyAsync(folder, file->Name, NameCollisionOption::ReplaceExisting)).then([this, file](task<StorageFile^> task)
{
logoFile = ref new Uri("ms-appdata:///local//App3//Assets//StoreLogo.scale-100.png");
});
});
}
});
}
then copy that file and save it in the app directory. It still fails when using a uri to point to the new copy.
void App3::MainPage::kk(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
text = url->Text->ToString();
ids = id->Text->ToString();
auto test = ref new Windows::UI::StartScreen::SecondaryTile(ids, "hi", text, logoFile, Windows::UI::StartScreen::TileSize::Square150x150); // Breaks right here
// error: logofile is 0x05fcc1d0
num++;
test->RequestCreateAsync();
//auto uri = ref new Windows::Foundation::Uri("http://www.google.com");
//concurrency::task<bool> launchUriOperation(Windows::System::Launcher::LaunchUriAsync(uri));
}
UPDATED
create_task(openPicker->PickSingleFileAsync()).then([this](Windows::Storage::StorageFile^ file)
{
if (file)
{
StorageFolder^ folder = ApplicationData::Current->LocalFolder;
create_task(file->CopyAsync(folder, file->Name, NameCollisionOption::ReplaceExisting)).then([this, file](task<StorageFile^> task)
{
String^ path = "ms-appdata:///local/" + file->Name;
logoFile = ref new Uri(path);
});
}
});
You're attempting to copy the picked file into the app package location (InstalledLocation), rather than into an app data folder. The package location is read-only, so CopyAsync should be failing. Use StorageFolder^ localFolder = ApplicationData::Current->LocalFolder; instead.
Also, you do need the /// in ms-appdata:///local because it's a shorthand to omit the package id, but you need only a single / elsewhere in the URI.
Finally, be aware that tile images must be 200KB or smaller and 1024x1024 or smaller, or they won't appear at all. If you're using photographic images, use a JPEG compression; vector images compress best with PNG. For more on dealing with this, see Chapter 16 of my free ebook, Programming Windows Store Apps with HTML, CSS, and JavaScript, 2nd Edition, specifically "Basic Tile Updates" starting on page 887 and the sidebar on page 899. The content is applicable to apps written in all languages, and it's a free book so there's no risk.

Windows Phone 8 - Saving Microphone File as .wav

I am using following method to save a recording of microphone in WP8 to a file:
private void SaveToIsolatedStorage()
{
// first, we grab the current apps isolated storage handle
IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication();
// we give our file a filename
string strSaveName = "myFile.wav";
// if that file exists...
if (isf.FileExists(strSaveName))
{
// then delete it
isf.DeleteFile(strSaveName);
}
// now we set up an isolated storage stream to point to store our data
IsolatedStorageFileStream isfStream =
new IsolatedStorageFileStream(strSaveName,
FileMode.Create, IsolatedStorageFile.GetUserStoreForApplication());
isfStream.Write(stream.ToArray(), 0, stream.ToArray().Length);
// ok, done with isolated storage... so close it
isfStream.Close();
}
The file is saved. However, I do not know where does it save it, and how can I access it.
I wish to permanently save it to the device so I can access it from outside the app (Let's say from a file explorer app, or from the music player app).
Thanks
Use this code to get saved file name from isolated storage and use this to read this file from stored loacation:
IsolatedStorageFile myIsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication();
String[] filenames=myIsolatedStorage.GetFileNames();

How would you simplify this process?

I have a bunch (over 1000) HTML files with just simple text. It's just a combination of text within a <table>. It's an internal batch of documents, not for web production.
The job we have is to convert them into JPEG files using Photoshop and the old copy paste method. It's tedious.
Is there a way you would do this process to make it more efficient/easier/simple?
I thought about trying to convert the HTML into Excel and then mail merging it into Word to print as JGEG. But I can't find (and rightly so) anything to convert HTML to XLSX.
Thoughts? Or is this just a manual job?
Here's a little something I created to convert a single html file to jpeg. It's not pretty (to say the least), but it works fine with a table larger than my screen. Put it inside a windows forms project. You can add more checks and call this program in a loop, or refactor it to work on multiple html files.
Ideas and techniques taken from -
Finding the needed size - http://social.msdn.microsoft.com/Forums/ie/en-US/f6f0c641-43bd-44cc-8be0-12b40fbc4c43/webbrowser-object-use-to-find-the-width-of-a-web-page
Creating the graphics - http://cplus.about.com/od/learnc/a/How-To-Save-Web-Page-Screen-Grab-csharp.htm
A table for example - copy-paste enlarged version of http://www.w3schools.com/html/html_tables.asp
static class Program
{
static WebBrowser webBrowser = new WebBrowser();
private static string m_fileName;
[STAThread]
static void Main(string[] args)
{
if (args.Length != 1)
{
MessageBox.Show("Usage: [fileName]");
return;
}
m_fileName = args[0];
webBrowser.DocumentCompleted += (a, b) => webBrowser_DocumentCompleted();
webBrowser.ScrollBarsEnabled = false; // Don't want them rendered
webBrowser.Navigate(new Uri(m_fileName));
Application.Run();
}
static void webBrowser_DocumentCompleted()
{
// Get the needed size of the control
webBrowser.Width = webBrowser.Document.Body.ScrollRectangle.Width + webBrowser.Margin.Horizontal;
webBrowser.Height = webBrowser.Document.Body.ScrollRectangle.Height + webBrowser.Margin.Vertical;
// Create the graphics and save the image
using (var graphics = webBrowser.CreateGraphics())
{
var bitmap = new Bitmap(webBrowser.Size.Width, webBrowser.Size.Height, graphics);
webBrowser.DrawToBitmap(bitmap, webBrowser.ClientRectangle);
string newFileName = Path.ChangeExtension(m_fileName, ".jpg");
bitmap.Save(newFileName, ImageFormat.Jpeg);
}
// Shamefully exit the application
Application.ExitThread();
}
}
You can load all files in one page and use this lib html2canvas to covert.
You can running in the background use nodejs with node-canvas or make it a desk app with node-webkit
In case anyone was looking for answer that works, I ended up using a program called Prince: https://www.princexml.com
It works amazingly, and just have to target the HTML with CSS or JS to make it match your output!

Calling wkhtmltopdf to generate PDF from HTML

I'm attempting to create a PDF file from an HTML file. After looking around a little I've found: wkhtmltopdf to be perfect. I need to call this .exe from the ASP.NET server. I've attempted:
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();
With no success of any files being created on the server. Can anyone give me a pointer in the right direction? I put the wkhtmltopdf.exe file at the top level directory of the site. Is there anywhere else it should be held?
Edit: If anyone has better solutions to dynamically create pdf files from html, please let me know.
Update:
My answer below, creates the pdf file on the disk. I then streamed that file to the users browser as a download. Consider using something like Hath's answer below to get wkhtml2pdf to output to a stream instead and then send that directly to the user - that will bypass lots of issues with file permissions etc.
My original answer:
Make sure you've specified an output path for the PDF that is writeable by the ASP.NET process of IIS running on your server (usually NETWORK_SERVICE I think).
Mine looks like this (and it works):
/// <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);
}
I had the same problem when i tried using msmq with a windows service but it was very slow for some reason. (the process part).
This is what finally worked:
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;
}
Thanks Graham Ambrose and everyone else.
OK, so this is an old question, but an excellent one. And since I did not find a good answer, I made my own :) Also, I've posted this super simple project to GitHub.
Here is some sample code:
var pdfData = HtmlToXConverter.ConvertToPdf("<h1>SOO COOL!</h1>");
Here are some key points:
No P/Invoke
No creating of a new process
No file-system (all in RAM)
Native .NET DLL with intellisense, etc.
Ability to generate a PDF or PNG (HtmlToXConverter.ConvertToPng)
Check out the C# wrapper library (using P/Invoke) for the wkhtmltopdf library: https://github.com/pruiz/WkHtmlToXSharp
There are many reason why this is generally a bad idea. How are you going to control the executables that get spawned off but end up living on in memory if there is a crash? What about denial-of-service attacks, or if something malicious gets into TestPDF.htm?
My understanding is that the ASP.NET user account will not have the rights to logon locally. It also needs to have the correct file permissions to access the executable and to write to the file system. You need to edit the local security policy and let the ASP.NET user account (maybe ASPNET) logon locally (it may be in the deny list by default). Then you need to edit the permissions on the NTFS filesystem for the other files. If you are in a shared hosting environment it may be impossible to apply the configuration you need.
The best way to use an external executable like this is to queue jobs from the ASP.NET code and have some sort of service monitor the queue. If you do this you will protect yourself from all sorts of bad things happening. The maintenance issues with changing the user account are not worth the effort in my opinion, and whilst setting up a service or scheduled job is a pain, its just a better design. The ASP.NET page should poll a result queue for the output and you can present the user with a wait page. This is acceptable in most cases.
You can tell wkhtmltopdf to send it's output to sout by specifying "-" as the output file.
You can then read the output from the process into the response stream and avoid the permissions issues with writing to the file system.
My take on this with 2018 stuff.
I am using async. I am streaming to and from wkhtmltopdf. I created a new StreamWriter because wkhtmltopdf is expecting utf-8 by default but it is set to something else when the process starts.
I didn't include a lot of arguments since those varies from user to user. You can add what you need using additionalArgs.
I removed p.WaitForExit(...) since I wasn't handling if it fails and it would hang anyway on await tStandardOutput. If timeout is needed, then you would have to call Wait(...) on the different tasks with a cancellationtoken or timeout and handle accordingly.
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();
}
}
Things I haven't included in there but could be useful if you have images, css or other stuff that wkhtmltopdf will have to load when rendering the html page:
you can pass the authentication cookie using --cookie
in the header of the html page, you can set the base tag with href pointing to the server and wkhtmltopdf will use that if need be
Thanks for the question / answer / all the comments above. I came upon this when I was writing my own C# wrapper for WKHTMLtoPDF and it answered a couple of the problems I had. I ended up writing about this in a blog post - which also contains my wrapper (you'll no doubt see the "inspiration" from the entries above seeping into my code...)
Making PDFs from HTML in C# using WKHTMLtoPDF
Thanks again guys!
The ASP .Net process probably doesn't have write access to the directory.
Try telling it to write to %TEMP%, and see if it works.
Also, make your ASP .Net page echo the process's stdout and stderr, and check for error messages.
Generally return code =0 is coming if the pdf file is created properly and correctly.If it's not created then the value is in -ve range.
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();
}
}