Forum How do I...?

Retrieve HTML page and render PDF in Response via ASP.NET

kman
Does anyone have any example code in .NET (VB or C# doesn't matter) that would show how one would use Prince to dynamically convert an HTML page and return the rendered PDF in the response?

I know the following code has issues, but i'm hoping it will at least convey what i'm trying to do:
Prince prince = new Prince(@"C:\Program Files (x86)\Prince\Engine\Bin\Prince.exe");
prince.SetLog(@"C:\princelog.log");

HttpWebRequest request = WebRequest.Create("http://localhost/whatever/default.aspx") as HttpWebRequest;
request.Credentials = CredentialCache.DefaultCredentials;

HttpWebResponse response = request.GetResponse() as HttpWebResponse;

// instantiate some kind of output stream  ???
Stream outputPdf;   //  obviously not exactly right...

prince.Convert(response.GetResponseStream(), outputPdf);

HttpContext.Current.Response.Write(outputPdf);  // obviously this isn't exactly right either..
HttpContext.Current.Response.End();

The code above might be used, for example, upon a button click event or something.

Any help would be greatly appreciated-
mikeday
If HttpContext.Current.Response is a stream, perhaps you can just pass it directly as the second argument to Convert(), so that Prince can write the PDF output directly to the response stream?
kman
Thanks for the response, Mike.

I think the code is very close now, but for some reason I can't get Prince to actually output *anything*. No log file, no error, no pdf, no anything.

Prince prince = new Prince(@"C:\Program Files (x86)\Prince\Engine\bin\prince.exe");
prince.SetLog(@"C:\princelog.log");

HttpWebRequest request = WebRequest.Create("http://www.google.com") as HttpWebRequest;

HttpWebResponse response = request.GetResponse() as HttpWebResponse;

Stream responseStream = response.GetResponseStream();

Response.AddHeader("Content-Type", "application/pdf");

bool converted = prince.Convert(responseStream, Response.OutputStream);

The 'converted' variable is simply always false. I know the response is good because I can see the HTML in it just fine.

I must be missing something obvious here.

I even fired up Sysinternal's Process Monitor watching for file system errors because I thought it was file permissions. I also dug through the Window's Event Logs as well as the IIS log files... but I can't find anything.

Maybe some IIS-specific setting?

Any ideas would be appreciated-
kman
OK apparently I was overloooking the error in Process Monitor the other day. It turns out there was an Access Denied error when trying to create the log file.

I had to give access on that folder to "IIS AppPool\DefaultAppPool" (IIS7)

Maybe now that I've got the log output I can figure out why it's not rendering the pdf.

Thanks-
mikeday
I would suggest calling setHTML(), as your document is probably not XML.
kman
Just wanted to share my results for the benefit of others. Final solution code and some comments:

HttpWebRequest request = WebRequest.Create("http://localhost/whatever/page.aspx") as HttpWebRequest;

// ASP.NET does not render everything if there's no UserAgent set (for ex. Widths on Labels for some bizarre reason) - use Chrome's useragent:
request.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.92 Safari/535.2";

// If site is using Windows Authentication, you're going to have to get valid credentials into the Request..
// I enabled Impersonation in the web.config, and then set the DefaultCredentials:
request.Credentials = CredentialCache.DefaultCredentials;

HttpWebResponse response = request.GetResponse() as HttpWebResponse;
Stream responseStream = response.GetResponseStream();

Response.AddHeader("Content-Type", "application/pdf");

// AppPool's identity (i.e. default IIS7 is "IIS AppPool\DefaultAppPool") will need access to these folders:
Prince prince = new Prince(@"C:\Program Files (x86)\Prince\Engine\bin\prince.exe");
// Only for debugging
//prince.SetLog(@"C:\princelog.log");

// Stream results can't be automatically determined - set explicitly
prince.SetHTML(true);

prince.SetEmbedFonts(true);

// prince must be able to access referenced resources.  since site was using Windows Authentication, I also enabled Basic Authentication
//  and set user + password here - could be local or domain. (be aware of security risks if site isn't SSL):
prince.SetHttpUser(ConfigurationManager.AppSettings["PrinceUser"].ToString());
prince.SetHttpPassword(ConfigurationManager.AppSettings["PrincePass"].ToString());

// this allows relative references in HTML to be resolved
prince.SetBaseURL(String.Format("{0}://{1}/yourAppName", HttpContext.Current.Request.Url.Scheme, HttpContext.Current.Request.Url.Host));

bool converted = prince.Convert(responseStream, Response.OutputStream);


So that's it. Seems to work great.

The only thing I don't understand is if prince is running under the AppPool's security context (IIS AppPool\DefaultAppPool in my case) - then you'd think if I switched the app pool to use a domain account - that I wouldn't then need to do the SetHttpUser and SetHttpPassword steps.. but I didn't find that to be the case.

It would be awesome if prince could somehow support Windows authentication. Even if you had to do something like setting the credentials via the CredentialCache.DefaultCredentials like on the Request.

Comments welcomed-
Thanks-
mikeday
Is there any reason why you need to perform the initial HTTP request yourself, and pass the stream to Prince, instead of just pointing Prince at the URL and letting it make the request? Is it the lack of Windows authentication?
kman
Sorry, I should have elaborated more regarding that initial HTTP request. The short answer is yes, it's partly of Windows Authentication, but it also has to be the actual current user's credentials.

Basically the page I'm requesting does some security checks (using the credentials) to ensure the user has access to the data requested.. so I have to use impersonation/delegation to pass their credentials along on the initial HTTP request.

But then later when Prince simply needs to access css, images, javascript, etc - he can just use a generic local or domain account (hence the explicit SetHttpUser/Password).

Hope that makes sense.

I wish there was some way I could tell Prince to use the current user's (Windows) credentials as well (then I wouldn't have to worry about any of it).

But it works fine this way too.

Thanks for the reply. BTW, I'm very impressed with what I've seen so far. I've started the ball rolling for my company to purchase a copy.
mikeday
For the record, Prince 10 now has support for Windows authentication.